Repository: rrweb-io/rrweb Branch: master Commit: 3ae040ddf605 Files: 679 Total size: 8.1 MB Directory structure: gitextract_di9qjh7g/ ├── .cache/ │ └── .gitkeep ├── .changeset/ │ ├── README.md │ ├── angry-turtles-provide.md │ ├── attribute-text-reductions.md │ ├── beige-olives-roll.md │ ├── blank-cherries-laugh.md │ ├── blank-dev-changset.md │ ├── brave-numbers-joke.md │ ├── brave-walls-shine.md │ ├── breezy-cats-heal.md │ ├── breezy-mice-breathe.md │ ├── bright-socks-clap.md │ ├── calm-bulldogs-speak.md │ ├── calm-oranges-sin.md │ ├── chatty-cherries-train.md │ ├── chilled-penguins-sin.md │ ├── clean-plants-play.md │ ├── clean-shrimps-lay.md │ ├── cold-eyes-hunt.md │ ├── cold-hounds-teach.md │ ├── config.json │ ├── controller-finish-flag.md │ ├── cool-grapes-hug.md │ ├── cool-horses-bow.md │ ├── cuddly-bikes-fail.md │ ├── cuddly-dolphins-approve.md │ ├── cuddly-readers-warn.md │ ├── curvy-apples-lay.md │ ├── curvy-balloons-brake.md │ ├── date-now-guard.md │ ├── dirty-pets-fly.md │ ├── dirty-rules-dress.md │ ├── docs-install-guidance.md │ ├── efficiently-splitCssText-1603.md │ ├── efficiently-splitCssText-1640.md │ ├── eight-terms-hunt.md │ ├── eight-years-hope.md │ ├── eighty-teachers-smash.md │ ├── eleven-bobcats-peel.md │ ├── eleven-toys-vanish.md │ ├── empty-bikes-cheer.md │ ├── empty-devonly-template.md │ ├── eslint-camelcase-devonly.md │ ├── event-single-wrap.md │ ├── fair-dragons-greet.md │ ├── fair-ducks-clean.md │ ├── famous-bobcats-push.md │ ├── fast-chefs-smell.md │ ├── fast-pets-exist.md │ ├── few-rockets-travel.md │ ├── few-turkeys-reflect.md │ ├── five-peas-lay.md │ ├── fix-adapt-css.md │ ├── fluffy-planes-retire.md │ ├── format-head-prettier.md │ ├── forty-elephants-attack.md │ ├── four-panthers-fly.md │ ├── fresh-cars-impress.md │ ├── fresh-spoons-drive.md │ ├── friendly-numbers-leave.md │ ├── fuzzy-mugs-march.md │ ├── giant-rats-chew.md │ ├── gold-apples-joke.md │ ├── gold-experts-type.md │ ├── gold-terms-look.md │ ├── great-cows-camp.md │ ├── grumpy-ways-own.md │ ├── happy-carrots-hide.md │ ├── healthy-glasses-shout.md │ ├── hip-worms-relax.md │ ├── hungry-dodos-taste.md │ ├── inlineImage-maybeNot-crossOrigin.md │ ├── itchy-dryers-double.md │ ├── itchy-tables-compete.md │ ├── khaki-dots-bathe.md │ ├── kind-kids-design.md │ ├── large-ants-prove.md │ ├── last-jest-to-vitest.md │ ├── lazy-squids-draw.md │ ├── lazy-toes-confess.md │ ├── lemon-lamps-switch.md │ ├── light-beans-destroy.md │ ├── light-fireants-exercise.md │ ├── little-radios-thank.md │ ├── little-suits-leave.md │ ├── loud-seals-raise.md │ ├── lovely-files-sparkle.md │ ├── lovely-pears-cross.md │ ├── lovely-students-boil.md │ ├── lucky-donuts-hammer.md │ ├── lucky-trainers-joke.md │ ├── mean-tips-impress.md │ ├── metal-mugs-mate.md │ ├── mighty-ads-worry.md │ ├── mighty-bulldogs-begin.md │ ├── mighty-frogs-sparkle.md │ ├── modern-doors-watch.md │ ├── moody-dots-refuse.md │ ├── moody-experts-build.md │ ├── nasty-scissors-reply.md │ ├── nervous-actors-jam.md │ ├── nervous-buses-pump.md │ ├── nervous-kiwis-nail.md │ ├── nervous-mirrors-perform.md │ ├── nervous-poets-grin.md │ ├── nervous-tables-travel.md │ ├── new-snakes-call.md │ ├── nice-pugs-reply.md │ ├── no-neg-lookbehind.md │ ├── odd-onions-brush.md │ ├── old-dryers-hide.md │ ├── perfect-bulldogs-punch.md │ ├── perfect-dolls-grab.md │ ├── polite-olives-wave.md │ ├── pre.json │ ├── pretty-meals-flash.md │ ├── pretty-plums-rescue.md │ ├── pretty-schools-remember.md │ ├── proud-clocks-hope.md │ ├── proud-experts-jam.md │ ├── purple-carrots-film.md │ ├── quiet-actors-mate.md │ ├── rare-adults-sneeze.md │ ├── real-masks-explode.md │ ├── real-trains-switch.md │ ├── red-peaches-explode.md │ ├── rich-crews-protect.md │ ├── rich-dots-lay.md │ ├── rich-jars-remember.md │ ├── rich-scissors-hide.md │ ├── rotten-spies-enjoy.md │ ├── serious-ants-juggle.md │ ├── serious-eggs-greet.md │ ├── shadow-dom-unbusify.md │ ├── short-hounds-confess.md │ ├── shy-countries-rhyme.md │ ├── silent-plants-perform.md │ ├── silly-knives-chew.md │ ├── silver-pots-sit.md │ ├── silver-windows-float.md │ ├── simplifify-hover-replacement.md │ ├── single-style-capture.md │ ├── six-llamas-brush.md │ ├── sixty-impalas-laugh.md │ ├── skip-mask-check-on-leaf-elements.md │ ├── slimdom-defaults-refactor.md │ ├── slimy-eagles-grow.md │ ├── small-hats-kneel.md │ ├── small-olives-arrive.md │ ├── smart-ears-refuse.md │ ├── smart-geckos-cover.md │ ├── smooth-papayas-boil.md │ ├── smooth-poems-bake.md │ ├── soft-worms-tan.md │ ├── spotty-bees-destroy.md │ ├── spotty-emus-listen.md │ ├── stupid-ghosts-help.md │ ├── swift-dancers-rest.md │ ├── swift-peas-film.md │ ├── swift-pots-search.md │ ├── tender-foxes-rest.md │ ├── textarea-inner-html.md │ ├── thin-vans-applaud.md │ ├── thirty-baboons-punch.md │ ├── thirty-shirts-grow.md │ ├── three-baboons-bow.md │ ├── tidy-swans-repair.md │ ├── tidy-yaks-joke.md │ ├── tiny-buckets-love.md │ ├── tiny-candles-whisper.md │ ├── tiny-chairs-build.md │ ├── title-deanimate-option.md │ ├── tricky-panthers-guess.md │ ├── twenty-flies-attend.md │ ├── twenty-goats-kneel.md │ ├── twenty-lies-switch.md │ ├── twenty-planets-repeat.md │ ├── two-boats-boil.md │ ├── unlucky-mirrors-invite.md │ ├── violet-melons-itch.md │ ├── violet-zebras-cry.md │ ├── wet-bottles-flash.md │ ├── wicked-dolphins-tie.md │ ├── wicked-lions-return.md │ ├── wise-spiders-jog.md │ ├── witty-kids-talk.md │ ├── yellow-mails-cheat.md │ └── young-timers-grow.md ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ └── feature_request.yml │ ├── config.yml │ ├── scripts/ │ │ ├── measure-bundle-sizes.js │ │ └── render-bundle-size-comment.js │ └── workflows/ │ ├── ci-cd.yml │ ├── eslint-check.yml │ ├── pr-checks-privileged.yml │ ├── release.yml │ └── style-check.yml ├── .gitignore ├── .markdownlint.yml ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .puppeteerrc.cjs ├── .release-it.json ├── .vscode/ │ └── rrweb-monorepo.code-workspace ├── .yarn/ │ └── releases/ │ └── yarn-1.23.0-20220130.1630.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README.zh_CN.md ├── SECURITY.md ├── docs/ │ ├── development/ │ │ └── coding-style.md │ ├── observer.md │ ├── observer.zh_CN.md │ ├── recipes/ │ │ ├── canvas.md │ │ ├── canvas.zh_CN.md │ │ ├── console.md │ │ ├── console.zh_CN.md │ │ ├── cross-origin-iframes.md │ │ ├── cross-origin-iframes.zh_CN.md │ │ ├── custom-event.md │ │ ├── custom-event.zh_CN.md │ │ ├── customize-replayer.md │ │ ├── customize-replayer.zh_CN.md │ │ ├── dive-into-event.md │ │ ├── dive-into-event.zh_CN.md │ │ ├── export-to-video.md │ │ ├── export-to-video.zh_CN.md │ │ ├── index.md │ │ ├── index.zh_CN.md │ │ ├── interaction.md │ │ ├── interaction.zh_CN.md │ │ ├── live-mode.md │ │ ├── live-mode.zh_CN.md │ │ ├── optimize-storage.md │ │ ├── optimize-storage.zh_CN.md │ │ ├── pagination.md │ │ ├── pagination.zh_CN.md │ │ ├── plugin.md │ │ ├── plugin.zh_CN.md │ │ ├── record-and-replay.md │ │ └── record-and-replay.zh_CN.md │ ├── replay.md │ ├── replay.zh_CN.md │ ├── sandbox.md │ ├── sandbox.zh_CN.md │ ├── serialization.md │ ├── serialization.zh_CN.md │ └── styleguide.md ├── guide.md ├── guide.zh_CN.md ├── package.json ├── packages/ │ ├── all/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── test/ │ │ │ ├── __snapshots__/ │ │ │ │ └── cross-origin-iframe-packer.test.ts.snap │ │ │ ├── cross-origin-iframe-packer.test.ts │ │ │ ├── html/ │ │ │ │ └── blank.html │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ ├── vite.config.ts │ │ └── vitest.config.ts │ ├── packer/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── pack/ │ │ │ └── package.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── base.ts │ │ │ ├── index.ts │ │ │ ├── pack.ts │ │ │ └── unpack.ts │ │ ├── test/ │ │ │ ├── __snapshots__/ │ │ │ │ └── packer.test.ts.snap │ │ │ └── packer.test.ts │ │ ├── tsconfig.json │ │ ├── unpack/ │ │ │ └── package.json │ │ └── vite.config.ts │ ├── plugins/ │ │ ├── rrweb-plugin-canvas-webrtc-record/ │ │ │ ├── CHANGELOG.md │ │ │ ├── Readme.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── index.ts │ │ │ │ ├── simple-peer-light.d.ts │ │ │ │ └── types.ts │ │ │ ├── tsconfig.json │ │ │ └── vite.config.ts │ │ ├── rrweb-plugin-canvas-webrtc-replay/ │ │ │ ├── CHANGELOG.md │ │ │ ├── Readme.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── index.ts │ │ │ │ ├── simple-peer-light.d.ts │ │ │ │ └── types.ts │ │ │ ├── tsconfig.json │ │ │ └── vite.config.ts │ │ ├── rrweb-plugin-console-record/ │ │ │ ├── CHANGELOG.md │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── error-stack-parser.ts │ │ │ │ ├── index.ts │ │ │ │ └── stringify.ts │ │ │ ├── test/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.ts.snap │ │ │ │ ├── html/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── log.html │ │ │ │ ├── index.test.ts │ │ │ │ └── stringify.test.ts │ │ │ ├── tsconfig.json │ │ │ ├── vite.config.ts │ │ │ └── vitest.config.ts │ │ ├── rrweb-plugin-console-replay/ │ │ │ ├── CHANGELOG.md │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ ├── tsconfig.json │ │ │ └── vite.config.ts │ │ ├── rrweb-plugin-sequential-id-record/ │ │ │ ├── CHANGELOG.md │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ ├── tsconfig.json │ │ │ └── vite.config.ts │ │ └── rrweb-plugin-sequential-id-replay/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── record/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── test/ │ │ │ └── record.test.ts │ │ ├── tsconfig.json │ │ ├── vite.config.ts │ │ └── vitest.config.ts │ ├── replay/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── test/ │ │ │ └── replay.test.ts │ │ ├── tsconfig.json │ │ ├── vite.config.ts │ │ └── vitest.config.ts │ ├── rrdom/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── src/ │ │ │ ├── diff.ts │ │ │ ├── document.ts │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── test/ │ │ │ ├── __snapshots__/ │ │ │ │ └── virtual-dom.test.ts.snap │ │ │ ├── diff/ │ │ │ │ └── dialog.test.ts │ │ │ ├── diff.test.ts │ │ │ ├── document.test.ts │ │ │ ├── html/ │ │ │ │ ├── iframe.html │ │ │ │ ├── main.html │ │ │ │ └── shadow-dom.html │ │ │ └── virtual-dom.test.ts │ │ ├── tsconfig.json │ │ ├── vite.config.js │ │ └── vitest.config.ts │ ├── rrdom-nodejs/ │ │ ├── .gitignore │ │ ├── .vscode/ │ │ │ ├── extensions.json │ │ │ └── launch.json │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── document-nodejs.ts │ │ │ ├── index.ts │ │ │ └── polyfill.ts │ │ ├── test/ │ │ │ ├── document-nodejs.test.ts │ │ │ └── polyfill.test.ts │ │ ├── tsconfig.json │ │ ├── vite.config.js │ │ └── vitest.config.ts │ ├── rrvideo/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── README.zh_CN.md │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── rrvideo.config.example.json │ │ ├── src/ │ │ │ ├── cli.ts │ │ │ └── index.ts │ │ ├── test/ │ │ │ ├── cli.test.ts │ │ │ ├── events/ │ │ │ │ └── example.ts │ │ │ └── tsconfig.json │ │ └── tsconfig.json │ ├── rrweb/ │ │ ├── .gitignore │ │ ├── .release-it.json │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── rrweb-record/ │ │ │ └── package.json │ │ ├── rrweb-replay/ │ │ │ └── package.json │ │ ├── scripts/ │ │ │ ├── repl.js │ │ │ ├── stream.js │ │ │ └── utils.js │ │ ├── src/ │ │ │ ├── entries/ │ │ │ │ ├── record.ts │ │ │ │ └── replay.ts │ │ │ ├── index.ts │ │ │ ├── record/ │ │ │ │ ├── cross-origin-iframe-mirror.ts │ │ │ │ ├── error-handler.ts │ │ │ │ ├── iframe-manager.ts │ │ │ │ ├── index.ts │ │ │ │ ├── mutation.ts │ │ │ │ ├── observer.ts │ │ │ │ ├── observers/ │ │ │ │ │ └── canvas/ │ │ │ │ │ ├── 2d.ts │ │ │ │ │ ├── canvas-manager.ts │ │ │ │ │ ├── canvas.ts │ │ │ │ │ ├── serialize-args.ts │ │ │ │ │ └── webgl.ts │ │ │ │ ├── processed-node-manager.ts │ │ │ │ ├── shadow-dom-manager.ts │ │ │ │ ├── stylesheet-manager.ts │ │ │ │ └── workers/ │ │ │ │ ├── image-bitmap-data-url-worker.ts │ │ │ │ └── tsconfig.json │ │ │ ├── replay/ │ │ │ │ ├── canvas/ │ │ │ │ │ ├── 2d.ts │ │ │ │ │ ├── deserialize-args.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── webgl.ts │ │ │ │ ├── dialog/ │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── machine.ts │ │ │ │ ├── media/ │ │ │ │ │ └── index.ts │ │ │ │ ├── smoothscroll.ts │ │ │ │ ├── styles/ │ │ │ │ │ ├── inject-style.ts │ │ │ │ │ └── style.css │ │ │ │ └── timer.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── test/ │ │ │ ├── __snapshots__/ │ │ │ │ ├── integration.test.ts.snap │ │ │ │ ├── record.test.ts.snap │ │ │ │ └── replayer.test.ts.snap │ │ │ ├── benchmark/ │ │ │ │ ├── dom-mutation.test.ts │ │ │ │ └── replay-fast-forward.test.ts │ │ │ ├── e2e/ │ │ │ │ └── webgl.test.ts │ │ │ ├── events/ │ │ │ │ ├── adopted-style-sheet-empty-replace.ts │ │ │ │ ├── adopted-style-sheet-modification.ts │ │ │ │ ├── adopted-style-sheet.ts │ │ │ │ ├── bad-style.ts │ │ │ │ ├── bad-textarea.ts │ │ │ │ ├── canvas-in-iframe.ts │ │ │ │ ├── custom-element-define-class.ts │ │ │ │ ├── dialog-playback.ts │ │ │ │ ├── document-replacement.ts │ │ │ │ ├── hover.ts │ │ │ │ ├── iframe-shadowdom-hover.ts │ │ │ │ ├── iframe.ts │ │ │ │ ├── input.ts │ │ │ │ ├── nested-style-declaration.ts │ │ │ │ ├── ordering.ts │ │ │ │ ├── scroll-with-parent-styles.ts │ │ │ │ ├── scroll.ts │ │ │ │ ├── selection.ts │ │ │ │ ├── shadow-dom.ts │ │ │ │ ├── style-declaration-missing-rule.ts │ │ │ │ ├── style-sheet-rule-events.ts │ │ │ │ ├── style-sheet-text-mutation.ts │ │ │ │ ├── video-playback-on-full-snapshot.ts │ │ │ │ ├── video-playback.ts │ │ │ │ └── webgl.ts │ │ │ ├── html/ │ │ │ │ ├── assets/ │ │ │ │ │ ├── bunny-video.webm │ │ │ │ │ ├── style.css │ │ │ │ │ └── webgl-utils.js │ │ │ │ ├── audio.html │ │ │ │ ├── benchmark-dom-mutation-add-and-move.html │ │ │ │ ├── benchmark-dom-mutation-add-and-remove.html │ │ │ │ ├── benchmark-dom-mutation-attributes.html │ │ │ │ ├── benchmark-dom-mutation-deep-nested.html │ │ │ │ ├── benchmark-dom-mutation-multiple-descendant-add.html │ │ │ │ ├── benchmark-dom-mutation.html │ │ │ │ ├── blank.html │ │ │ │ ├── block.html │ │ │ │ ├── blocked-unblocked.html │ │ │ │ ├── canvas-webgl-image.html │ │ │ │ ├── canvas-webgl-shader.html │ │ │ │ ├── canvas-webgl-square.html │ │ │ │ ├── canvas-webgl.html │ │ │ │ ├── canvas.html │ │ │ │ ├── dialog.html │ │ │ │ ├── empty.html │ │ │ │ ├── form.html │ │ │ │ ├── frame-image-blob-url.html │ │ │ │ ├── frame1.html │ │ │ │ ├── frame2.html │ │ │ │ ├── hello-world.html │ │ │ │ ├── ignore.html │ │ │ │ ├── image-blob-url.html │ │ │ │ ├── link.html │ │ │ │ ├── main.html │ │ │ │ ├── mask-text.html │ │ │ │ ├── move-node.html │ │ │ │ ├── mutation-observer.html │ │ │ │ ├── password.html │ │ │ │ ├── polyfilled-shadowdom-mutation.html │ │ │ │ ├── react-styled-components.html │ │ │ │ ├── select2.html │ │ │ │ ├── shadow-dom.html │ │ │ │ ├── shuffle.html │ │ │ │ ├── style.html │ │ │ │ └── video.html │ │ │ ├── integration.test.ts │ │ │ ├── machine.test.ts │ │ │ ├── record/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ ├── cross-origin-iframes.test.ts.snap │ │ │ │ │ ├── cross-origin-iframes.test.ts.snap.extra │ │ │ │ │ ├── dialog.test.ts.snap │ │ │ │ │ └── webgl.test.ts.snap │ │ │ │ ├── cross-origin-iframes.test.ts │ │ │ │ ├── dialog.test.ts │ │ │ │ ├── error-handler.test.ts │ │ │ │ ├── serialize-args.test.ts │ │ │ │ └── webgl.test.ts │ │ │ ├── record.test.ts │ │ │ ├── replay/ │ │ │ │ ├── 2d-mutation.test.ts │ │ │ │ ├── deserialize-args.test.ts │ │ │ │ ├── dialog.test.ts │ │ │ │ ├── hover.test.ts │ │ │ │ ├── preload-all-images.test.ts │ │ │ │ ├── video.test.ts │ │ │ │ ├── webgl-mutation.test.ts │ │ │ │ └── webgl.test.ts │ │ │ ├── replayer.test.ts │ │ │ ├── rrdom.test.ts │ │ │ ├── util.test.ts │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ ├── vite.config.entries.js │ │ ├── vite.config.js │ │ └── vitest.config.ts │ ├── rrweb-player/ │ │ ├── .eslintignore │ │ ├── .eslintrc.cjs │ │ ├── .gitignore │ │ ├── .prettierignore │ │ ├── .release-it.json │ │ ├── .svelte-kit/ │ │ │ ├── ambient.d.ts │ │ │ ├── generated/ │ │ │ │ └── client/ │ │ │ │ ├── app.js │ │ │ │ ├── matchers.js │ │ │ │ └── nodes/ │ │ │ │ ├── 0.js │ │ │ │ └── 1.js │ │ │ ├── non-ambient.d.ts │ │ │ └── tsconfig.json │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── public/ │ │ │ ├── events.js │ │ │ └── global.css │ │ ├── src/ │ │ │ ├── Controller.svelte │ │ │ ├── Player.svelte │ │ │ ├── components/ │ │ │ │ └── Switch.svelte │ │ │ ├── main.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── svelte.config.js │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ ├── vite-env.d.ts │ │ └── vite.config.ts │ ├── rrweb-snapshot/ │ │ ├── .gitignore │ │ ├── .release-it.json │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── jsr.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── css.ts │ │ │ ├── index.ts │ │ │ ├── rebuild.ts │ │ │ ├── snapshot.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── test/ │ │ │ ├── __snapshots__/ │ │ │ │ └── integration.test.ts.snap │ │ │ ├── alt-css/ │ │ │ │ └── alt-style.css │ │ │ ├── css/ │ │ │ │ ├── benchmark.css │ │ │ │ ├── style-with-import.css │ │ │ │ └── style.css │ │ │ ├── css.test.ts │ │ │ ├── html/ │ │ │ │ ├── about-mozilla.html │ │ │ │ ├── background-clip-text.html │ │ │ │ ├── basic.html │ │ │ │ ├── block-element.html │ │ │ │ ├── compat-mode.html │ │ │ │ ├── cors-style-sheet.html │ │ │ │ ├── dialog.html │ │ │ │ ├── dynamic-stylesheet.html │ │ │ │ ├── form-fields.html │ │ │ │ ├── hover.html │ │ │ │ ├── iframe-inner.html │ │ │ │ ├── iframe.html │ │ │ │ ├── invalid-attribute.html │ │ │ │ ├── invalid-doctype.html │ │ │ │ ├── invalid-tagname.html │ │ │ │ ├── mask-text.html │ │ │ │ ├── monkey-patched-elements.html │ │ │ │ ├── picture-blob-in-frame.html │ │ │ │ ├── picture-blob.html │ │ │ │ ├── picture-in-frame.html │ │ │ │ ├── picture-with-inline-onload.html │ │ │ │ ├── picture.html │ │ │ │ ├── preload.html │ │ │ │ ├── shadow-dom.html │ │ │ │ ├── svg.html │ │ │ │ ├── video.html │ │ │ │ ├── with-relative-res.html │ │ │ │ ├── with-script.html │ │ │ │ ├── with-style-sheet-with-import.html │ │ │ │ └── with-style-sheet.html │ │ │ ├── iframe-html/ │ │ │ │ ├── frame1.html │ │ │ │ ├── frame2.html │ │ │ │ └── main.html │ │ │ ├── integration.test.ts │ │ │ ├── js/ │ │ │ │ └── a.js │ │ │ ├── rebuild.test.ts │ │ │ ├── snapshot.test.ts │ │ │ ├── stringify-stylesheet.bench.ts │ │ │ ├── utils.test.ts │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ ├── vite.config.js │ │ └── vitest.config.ts │ ├── types/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── vite.config.js │ ├── utils/ │ │ ├── CHANGELOG.md │ │ ├── Readme.md │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── vite.config.js │ └── web-extension/ │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src/ │ │ ├── background/ │ │ │ └── index.ts │ │ ├── components/ │ │ │ ├── CircleButton.tsx │ │ │ └── SidebarWithHeader.tsx │ │ ├── content/ │ │ │ ├── index.ts │ │ │ └── inject.ts │ │ ├── manifest.json │ │ ├── options/ │ │ │ ├── App.tsx │ │ │ ├── index.html │ │ │ └── index.tsx │ │ ├── pages/ │ │ │ ├── App.tsx │ │ │ ├── Player.tsx │ │ │ ├── SessionList.tsx │ │ │ ├── index.html │ │ │ └── index.tsx │ │ ├── popup/ │ │ │ ├── App.tsx │ │ │ ├── Timer.tsx │ │ │ ├── index.tsx │ │ │ └── popup.html │ │ ├── types.ts │ │ └── utils/ │ │ ├── channel.ts │ │ ├── index.ts │ │ └── storage.ts │ ├── tsconfig.json │ └── vite.config.ts ├── scripts/ │ └── lint-packages.sh ├── tsconfig.base.json ├── tsconfig.eslint.json ├── tsconfig.json ├── turbo.json ├── vite.config.default.ts ├── vitest.config.ts └── vitest.workspace.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cache/.gitkeep ================================================ ================================================ FILE: .changeset/README.md ================================================ # Changesets Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) ================================================ FILE: .changeset/angry-turtles-provide.md ================================================ --- "rrweb-snapshot": patch --- Handle exceptions thrown from postcss when calling adaptCssForReplay ================================================ FILE: .changeset/attribute-text-reductions.md ================================================ --- 'rrweb': patch --- Don't include redundant data from text/attribute mutations on just-added nodes ================================================ FILE: .changeset/beige-olives-roll.md ================================================ --- "rrweb-snapshot": patch "rrweb": patch --- Fix that the optional `maskInputFn` was being accidentally ignored during the creation of the full snapshot ================================================ FILE: .changeset/blank-cherries-laugh.md ================================================ --- --- ================================================ FILE: .changeset/blank-dev-changset.md ================================================ --- --- ================================================ FILE: .changeset/brave-numbers-joke.md ================================================ --- --- ================================================ FILE: .changeset/brave-walls-shine.md ================================================ --- "@rrweb/record": major "@rrweb/replay": major --- BREAKING CHANGE: Rename UMD global names from `rrweb` to `rrwebRecord` for the recorder and `rrwebReplay` for the replayer. This avoids conflicts when both are loaded on the same page. ================================================ FILE: .changeset/breezy-cats-heal.md ================================================ --- 'rrweb': patch --- fix: createImageBitmap throws DOMException if source is 0 width or height ================================================ FILE: .changeset/breezy-mice-breathe.md ================================================ --- 'rrweb': patch --- safely capture BigInt values with the console log plugin" ================================================ FILE: .changeset/bright-socks-clap.md ================================================ --- --- ================================================ FILE: .changeset/calm-bulldogs-speak.md ================================================ --- --- ================================================ FILE: .changeset/calm-oranges-sin.md ================================================ --- 'rrweb': patch --- fix: Fix checking for `patchTarget` in `initAdoptedStyleSheetObserver` ================================================ FILE: .changeset/chatty-cherries-train.md ================================================ --- 'rrweb': patch --- Fix the statement which is getting changed by Microbundle ================================================ FILE: .changeset/chilled-penguins-sin.md ================================================ --- "rrdom": patch --- Ignore invalid DOM attributes when diffing ================================================ FILE: .changeset/clean-plants-play.md ================================================ --- 'rrweb': patch '@rrweb/types': patch --- Compact style mutation fixes and improvements - fixes when style updates contain a 'var()' on a shorthand property #1246 - further ensures that style mutations are compact by reverting to string method if it is shorter ================================================ FILE: .changeset/clean-shrimps-lay.md ================================================ --- 'rrweb': patch --- feat: Add `ignoreSelector` option Similar to ignoreClass, but accepts a CSS selector so that you can use any CSS selector. ================================================ FILE: .changeset/cold-eyes-hunt.md ================================================ --- 'rrdom': patch --- Fix: rrdom bugs 1. Fix a bug in the diff algorithm. 2. Omit the 'srcdoc' attribute of iframes to avoid overwriting content. ================================================ FILE: .changeset/cold-hounds-teach.md ================================================ --- --- ================================================ FILE: .changeset/config.json ================================================ { "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", "changelog": ["@changesets/changelog-github", { "repo": "rrweb-io/rrweb" }], "commit": false, "fixed": [ [ "rrweb", "rrweb-snapshot", "rrdom", "rrdom-nodejs", "rrweb-player", "@rrweb/all", "@rrweb/replay", "@rrweb/record", "@rrweb/types", "@rrweb/packer", "@rrweb/utils", "@rrweb/web-extension", "rrvideo", "@rrweb/rrweb-plugin-console-record", "@rrweb/rrweb-plugin-console-replay", "@rrweb/rrweb-plugin-sequential-id-record", "@rrweb/rrweb-plugin-sequential-id-replay", "@rrweb/rrweb-plugin-canvas-webrtc-record", "@rrweb/rrweb-plugin-canvas-webrtc-replay" ] ], "linked": [], "access": "public", "baseBranch": "master", "updateInternalDependencies": "patch", "ignore": [] } ================================================ FILE: .changeset/controller-finish-flag.md ================================================ --- 'rrweb-player': patch 'rrweb': patch --- Reset the finished flag in Controller `goto` instead of `handleProgressClick` so that it is properly handled if `goto` is called directly. ================================================ FILE: .changeset/cool-grapes-hug.md ================================================ --- 'rrdom': patch --- Support `loop` in `RRMediaElement` ================================================ FILE: .changeset/cool-horses-bow.md ================================================ --- "@rrweb/rrweb-plugin-canvas-webrtc-record": patch "@rrweb/rrweb-plugin-canvas-webrtc-replay": patch "@rrweb/rrweb-plugin-sequential-id-record": patch "@rrweb/rrweb-plugin-sequential-id-replay": patch "@rrweb/rrweb-plugin-console-record": patch "@rrweb/rrweb-plugin-console-replay": patch "@rrweb/packer": patch "@rrweb/record": patch "@rrweb/replay": patch "@rrweb/all": patch --- Keep package version in sync with other packages ================================================ FILE: .changeset/cuddly-bikes-fail.md ================================================ --- "rrweb-snapshot": patch "rrweb": patch --- fix: duplicate textContent for style elements cause incremental style mutations to be invalid ================================================ FILE: .changeset/cuddly-dolphins-approve.md ================================================ --- "@rrweb/all": patch "@rrweb/packer": patch "@rrweb/record": patch "rrweb-snapshot": patch "rrweb": patch "@rrweb/web-extension": patch --- Drop base64 inlined worker source from all bundles ================================================ FILE: .changeset/cuddly-readers-warn.md ================================================ --- --- ================================================ FILE: .changeset/curvy-apples-lay.md ================================================ --- 'rrweb-snapshot': patch 'rrweb': patch --- Extend to run fixBrowserCompatibilityIssuesInCSS over inline stylesheets ================================================ FILE: .changeset/curvy-balloons-brake.md ================================================ --- --- ================================================ FILE: .changeset/date-now-guard.md ================================================ --- 'rrweb': patch --- Guard against presence of older 3rd party javascript libraries which redefine Date.now() ================================================ FILE: .changeset/dirty-pets-fly.md ================================================ --- --- ================================================ FILE: .changeset/dirty-rules-dress.md ================================================ --- 'rrweb-snapshot': minor --- Video and Audio elements now also capture `playbackRate`, `muted`, `loop`, `volume`. ================================================ FILE: .changeset/docs-install-guidance.md ================================================ --- --- Docs-only update: clarify package recommendation order (`@rrweb/record` + `@rrweb/replay` first, `@rrweb/all` as convenience), and fix example typos. ================================================ FILE: .changeset/efficiently-splitCssText-1603.md ================================================ --- "rrweb-snapshot": patch "rrweb": patch --- Improve performance of splitCssText for '); // old document with elements that need removing const rrDocument = new RRDocument(); const docType = rrDocument.createDocumentType('html', '', ''); rrDocument.mirror.add(docType, getDefaultSN(docType, 1)); rrDocument.appendChild(docType); const htmlEl = rrDocument.createElement('html'); rrDocument.mirror.add(htmlEl, getDefaultSN(htmlEl, 2)); rrDocument.appendChild(htmlEl); diff(document, rrDocument, replayer); expect(document.childNodes.length).toBe(2); const element = document.childNodes[0] as HTMLElement; expect(element.nodeType).toBe(element.DOCUMENT_TYPE_NODE); expect(mirror.getId(element)).toEqual(1); }); it('should remove children from document before adding new nodes 2', () => { document.write(''); const iframe = document.querySelector('iframe')!; // Remove everthing from the iframe but the root html element // `buildNodeWithSn` injects docType elements to trigger compatMode in iframes iframe.contentDocument!.write( '', ); replayer.mirror.add(iframe.contentDocument!, { id: 1, type: 0, childNodes: [ { id: 2, rootId: 1, type: 2, tagName: 'html', childNodes: [], attributes: {}, }, ], } as serializedNodeWithId); replayer.mirror.add(iframe.contentDocument!.childNodes[0], { id: 2, rootId: 1, type: 2, tagName: 'html', childNodes: [], attributes: {}, } as serializedNodeWithId); const rrDocument = new RRDocument(); rrDocument.mirror.add(rrDocument, getDefaultSN(rrDocument, 1)); const docType = rrDocument.createDocumentType('html', '', ''); rrDocument.mirror.add(docType, getDefaultSN(docType, 2)); rrDocument.appendChild(docType); const htmlEl = rrDocument.createElement('html'); rrDocument.mirror.add(htmlEl, getDefaultSN(htmlEl, 3)); rrDocument.appendChild(htmlEl); const styleEl = rrDocument.createElement('style'); rrDocument.mirror.add(styleEl, getDefaultSN(styleEl, 4)); htmlEl.appendChild(styleEl); const headEl = rrDocument.createElement('head'); rrDocument.mirror.add(headEl, getDefaultSN(headEl, 5)); htmlEl.appendChild(headEl); const bodyEl = rrDocument.createElement('body'); rrDocument.mirror.add(bodyEl, getDefaultSN(bodyEl, 6)); htmlEl.appendChild(bodyEl); diff(iframe.contentDocument!, rrDocument, replayer); expect(iframe.contentDocument!.childNodes.length).toBe(2); const element = iframe.contentDocument!.childNodes[0] as HTMLElement; expect(element.nodeType).toBe(element.DOCUMENT_TYPE_NODE); expect(mirror.getId(element)).toEqual(2); }); it('should remove children from document before adding new nodes 3', () => { document.write(''); const iframeInDom = document.querySelector('iframe')!; replayer.mirror.add(iframeInDom, { id: 3, type: 2, rootId: 1, tagName: 'iframe', childNodes: [], attributes: {}, } as serializedNodeWithId); replayer.mirror.add(iframeInDom.contentDocument!, { id: 4, type: 0, childNodes: [], } as serializedNodeWithId); const rrDocument = new RRDocument(); const rrIframeEl = rrDocument.createElement('iframe'); rrDocument.mirror.add(rrIframeEl, getDefaultSN(rrIframeEl, 3)); rrDocument.appendChild(rrIframeEl); rrDocument.mirror.add( rrIframeEl.contentDocument!, getDefaultSN(rrIframeEl.contentDocument!, 4), ); const rrDocType = rrDocument.createDocumentType('html', '', ''); rrIframeEl.contentDocument.appendChild(rrDocType); const rrHtmlEl = rrDocument.createElement('html'); rrDocument.mirror.add(rrHtmlEl, getDefaultSN(rrHtmlEl, 6)); rrIframeEl.contentDocument.appendChild(rrHtmlEl); const rrHeadEl = rrDocument.createElement('head'); rrDocument.mirror.add(rrHeadEl, getDefaultSN(rrHeadEl, 8)); rrHtmlEl.appendChild(rrHeadEl); const bodyEl = rrDocument.createElement('body'); rrDocument.mirror.add(bodyEl, getDefaultSN(bodyEl, 9)); rrHtmlEl.appendChild(bodyEl); diff(iframeInDom, rrIframeEl, replayer); expect(iframeInDom.contentDocument!.childNodes.length).toBe(2); const element = iframeInDom.contentDocument!.childNodes[0] as HTMLElement; expect(element.nodeType).toBe(element.DOCUMENT_TYPE_NODE); expect(mirror.getId(element)).toEqual(-1); }); it('should remove children from document before adding new nodes 4', () => { /** * This case aims to test whether the diff function can remove all the old doctype and html element from the document before adding new doctype and html element. * If not, the diff function will throw errors or warnings. */ // Mock the original console.warn function to make the test fail once console.warn is called. const warn = vi.spyOn(global.console, 'warn'); document.write(''); const rrdom = new RRDocument(); /** * Make the structure of document and RRDom look like this: * -2 Document * -3 DocumentType * -4 HTML * -5 HEAD * -6 BODY */ buildFromDom(document, mirror, rrdom); expect(mirror.getId(document)).toBe(-2); expect(mirror.getId(document.body)).toBe(-6); expect(rrdom.mirror.getId(rrdom)).toBe(-2); expect(rrdom.mirror.getId(rrdom.body)).toBe(-6); while (rrdom.firstChild) rrdom.removeChild(rrdom.firstChild); /** * Rebuild the rrdom and make it looks like this: * -7 RRDocument * -8 RRDocumentType * -9 HTML * -10 HEAD * -11 BODY */ buildFromDom(document, undefined, rrdom); // Keep the ids of real document unchanged. expect(mirror.getId(document)).toBe(-2); expect(mirror.getId(document.body)).toBe(-6); expect(rrdom.mirror.getId(rrdom)).toBe(-7); expect(rrdom.mirror.getId(rrdom.body)).toBe(-11); // Diff the document with the new rrdom. diff(document, rrdom, replayer); // Check that warn was not called (fail on warning) expect(warn).not.toHaveBeenCalled(); // Check that the old nodes are removed from the NodeMirror. [-2, -3, -4, -5, -6].forEach((id) => expect(mirror.getNode(id)).toBeNull(), ); expect(mirror.getId(document)).toBe(-7); expect(mirror.getId(document.doctype)).toBe(-8); expect(mirror.getId(document.documentElement)).toBe(-9); expect(mirror.getId(document.head)).toBe(-10); expect(mirror.getId(document.body)).toBe(-11); warn.mockRestore(); }); it('selectors should be case-sensitive for matching in iframe dom', async () => { /** * If the selector match is case insensitive, it will cause some CSS style problems in the replayer. * This test result executed in JSDom is different from that in real browser so we use puppeteer as test environment. */ const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'], }); const page = await browser.newPage(); await page.goto('about:blank'); try { const code = fs.readFileSync( path.resolve(__dirname, '../dist/rrdom.umd.cjs'), 'utf8', ); await page.evaluate(code); const className = 'case-sensitive'; // To show the selector match pattern (case sensitive) in normal dom. const caseInsensitiveInNormalDom = await page.evaluate((className) => { document.write( '', ); const htmlEl = document.documentElement; htmlEl.className = className.toLowerCase(); return htmlEl.matches(`.${className.toUpperCase()}`); }, className); expect(caseInsensitiveInNormalDom).toBeFalsy(); // To show the selector match pattern (case insensitive) in auto mounted iframe dom. const caseInsensitiveInDefaultIFrameDom = await page.evaluate( (className) => { const iframeEl = document.querySelector('iframe'); const htmlEl = iframeEl?.contentDocument?.documentElement; if (htmlEl) { htmlEl.className = className.toLowerCase(); return htmlEl.matches(`.${className.toUpperCase()}`); } }, className, ); expect(caseInsensitiveInDefaultIFrameDom).toBeTruthy(); const iframeElId = 3, iframeDomId = 4, htmlElId = 5; const result = await page.evaluate(` const iframeEl = document.querySelector('iframe'); // Construct a virtual dom tree. const rrDocument = new rrdom.RRDocument(); const rrIframeEl = rrDocument.createElement('iframe'); rrDocument.mirror.add(rrIframeEl, rrdom.getDefaultSN(rrIframeEl, ${iframeElId})); rrDocument.appendChild(rrIframeEl); rrDocument.mirror.add( rrIframeEl.contentDocument, rrdom.getDefaultSN(rrIframeEl.contentDocument, ${iframeDomId}), ); const rrDocType = rrDocument.createDocumentType('html', '', ''); rrIframeEl.contentDocument.appendChild(rrDocType); const rrHtmlEl = rrDocument.createElement('html'); rrDocument.mirror.add(rrHtmlEl, rrdom.getDefaultSN(rrHtmlEl, ${htmlElId})); rrIframeEl.contentDocument.appendChild(rrHtmlEl); const replayer = { mirror: rrdom.createMirror(), applyCanvas: () => {}, applyInput: () => {}, applyScroll: () => {}, applyStyleSheetMutation: () => {}, }; rrdom.diff(iframeEl, rrIframeEl, replayer); iframeEl.contentDocument.documentElement.className = '${className.toLowerCase()}'; iframeEl.contentDocument.childNodes.length === 2 && replayer.mirror.getId(iframeEl.contentDocument.documentElement) === ${htmlElId} && // To test whether the selector match of the updated iframe document is case sensitive or not. !iframeEl.contentDocument.documentElement.matches( '.${className.toUpperCase()}', ); `); // IFrame document has two children, mirror id of documentElement is ${htmlElId}, and selectors should be case-sensitive for matching in iframe dom (consistent with the normal dom). expect(result).toBeTruthy(); } finally { await page.close(); await browser.close(); } }); }); describe('afterAppend callback', () => { it('should call afterAppend callback', () => { const afterAppendFn = vi.spyOn(replayer, 'afterAppend'); const node = createTree( { tagName: 'div', id: 1, }, undefined, mirror, ) as Node; const rrdom = new RRDocument(); const rrNode = createTree( { tagName: 'div', id: 1, children: [ { tagName: 'span', id: 2, }, ], }, rrdom, ) as RRNode; diff(node, rrNode, replayer); expect(afterAppendFn).toHaveBeenCalledTimes(1); expect(afterAppendFn).toHaveBeenCalledWith(node.childNodes[0], 2); afterAppendFn.mockRestore(); }); it('should diff without afterAppend callback', () => { replayer.afterAppend = undefined; const rrdom = buildFromDom(document); document.open(); diff(document, rrdom, replayer); replayer.afterAppend = () => {}; }); it('should call afterAppend callback in the post traversal order', () => { const afterAppendFn = vi.spyOn(replayer, 'afterAppend'); document.open(); const rrdom = new RRDocument(); rrdom.mirror.add(rrdom, getDefaultSN(rrdom, 1)); const rrNode = createTree( { tagName: 'html', id: 1, children: [ { tagName: 'head', id: 2, }, { tagName: 'body', id: 3, children: [ { tagName: 'span', id: 4, children: [ { tagName: 'li', id: 5, }, { tagName: 'li', id: 6, }, ], }, { tagName: 'p', id: 7, }, { tagName: 'p', id: 8, children: [ { tagName: 'li', id: 9, }, { tagName: 'li', id: 10, }, ], }, ], }, ], }, rrdom, ) as RRNode; diff(document, rrNode, replayer); expect(afterAppendFn).toHaveBeenCalledTimes(10); // the correct traversal order [2, 5, 6, 4, 7, 9, 10, 8, 3, 1].forEach((id, index) => { expect((mirror.getNode(id) as HTMLElement).tagName).toEqual( (rrdom.mirror.getNode(id) as IRRElement).tagName, ); expect(afterAppendFn).toHaveBeenNthCalledWith( index + 1, mirror.getNode(id), id, ); }); }); it('should only call afterAppend for newly created nodes', () => { const afterAppendFn = vi.spyOn(replayer, 'afterAppend'); const rrdom = buildFromDom(document, replayer.mirror) as RRDocument; // Append 3 nodes to rrdom. const rrNode = createTree( { tagName: 'span', id: 1, children: [ { tagName: 'li', id: 2, }, { tagName: 'li', id: 3, }, ], }, rrdom, ) as RRNode; rrdom.body?.appendChild(rrNode); diff(document, rrdom, replayer); expect(afterAppendFn).toHaveBeenCalledTimes(3); // Should only call afterAppend for 3 newly appended nodes. [2, 3, 1].forEach((id, index) => { expect((mirror.getNode(id) as HTMLElement).tagName).toEqual( (rrdom.mirror.getNode(id) as IRRElement).tagName, ); expect(afterAppendFn).toHaveBeenNthCalledWith( index + 1, mirror.getNode(id), id, ); }); afterAppendFn.mockClear(); }); }); describe('create or get a Node', () => { it('create a real HTML element from RRElement', () => { const rrDocument = new RRDocument(); const rrNode = rrDocument.createElement('DIV'); const sn2 = Object.assign({}, elementSn, { id: 0 }); rrDocument.mirror.add(rrNode, sn2); let result = createOrGetNode(rrNode, mirror, rrDocument.mirror); expect(result).toBeInstanceOf(HTMLElement); expect(mirror.getId(result)).toBe(0); expect((result as Node as HTMLElement).tagName).toBe('DIV'); }); it('create a node from RRNode', () => { const rrDocument = new RRDocument(); rrDocument.mirror.add(rrDocument, getDefaultSN(rrDocument, 0)); let result = createOrGetNode(rrDocument, mirror, rrDocument.mirror); expect(result).toBeInstanceOf(Document); expect(mirror.getId(result)).toBe(0); const textContent = 'Text Content'; let rrNode: RRNode = rrDocument.createTextNode(textContent); rrDocument.mirror.add(rrNode, getDefaultSN(rrNode, 1)); result = createOrGetNode(rrNode, mirror, rrDocument.mirror); expect(result).toBeInstanceOf(Text); expect(mirror.getId(result)).toBe(1); expect((result as Node as Text).textContent).toBe(textContent); rrNode = rrDocument.createComment(textContent); rrDocument.mirror.add(rrNode, getDefaultSN(rrNode, 2)); result = createOrGetNode(rrNode, mirror, rrDocument.mirror); expect(result).toBeInstanceOf(Comment); expect(mirror.getId(result)).toBe(2); expect((result as Node as Comment).textContent).toBe(textContent); rrNode = rrDocument.createCDATASection(''); rrDocument.mirror.add(rrNode, getDefaultSN(rrNode, 3)); expect(() => createOrGetNode(rrNode, mirror, rrDocument.mirror), ).toThrowErrorMatchingInlineSnapshot(`DOMException {}`); }); it('create a DocumentType from RRDocumentType', () => { const rrDocument = new RRDocument(); const publicId = '-//W3C//DTD XHTML 1.0 Transitional//EN'; let rrNode: RRNode = rrDocument.createDocumentType('html', publicId, ''); rrDocument.mirror.add(rrNode, getDefaultSN(rrNode, 0)); let result = createOrGetNode(rrNode, mirror, rrDocument.mirror); expect(result).toBeInstanceOf(DocumentType); expect(mirror.getId(result)).toBe(0); expect((result as Node as DocumentType).name).toEqual('html'); expect((result as Node as DocumentType).publicId).toEqual(publicId); expect((result as Node as DocumentType).systemId).toEqual(''); }); it('can get a node if it already exists', () => { const rrDocument = new RRDocument(); const textContent = 'Text Content'; const text = document.createTextNode(textContent); const sn: serializedNodeWithId = { id: 0, type: RRNodeType.Text, textContent: 'text of the existed node', }; // Add the text node to the mirror to make it look like already existing. mirror.add(text, sn); const rrNode: RRNode = rrDocument.createTextNode(textContent); rrDocument.mirror.add(rrNode, getDefaultSN(rrNode, 0)); let result = createOrGetNode(rrNode, mirror, rrDocument.mirror); expect(result).toBeInstanceOf(Text); expect(mirror.getId(result)).toBe(0); expect((result as Node as Text).textContent).toBe(textContent); expect(result).toEqual(text); // To make sure the existed text node is used. expect(mirror.getMeta(result)).toEqual(mirror.getMeta(text)); }); }); describe('test sameNodeType function', () => { const rrdom = new RRDocument(); it('should return true when two elements have same tagNames', () => { const div1 = document.createElement('div'); const div2 = rrdom.createElement('div'); expect(sameNodeType(div1, div2)).toBeTruthy(); }); it('should return false when two elements have different tagNames', () => { const div1 = document.createElement('div'); const div2 = rrdom.createElement('span'); expect(sameNodeType(div1, div2)).toBeFalsy(); }); it('should return false when two nodes have the same node type', () => { let node1: Node = new Document(); let node2: IRRNode = new RRDocument(); expect(sameNodeType(node1, node2)).toBeTruthy(); node1 = document.implementation.createDocumentType('html', '', ''); node2 = rrdom.createDocumentType('', '', ''); expect(sameNodeType(node1, node2)).toBeTruthy(); node1 = document.createTextNode('node1'); node2 = rrdom.createTextNode('node2'); expect(sameNodeType(node1, node2)).toBeTruthy(); node1 = document.createComment('node1'); node2 = rrdom.createComment('node2'); expect(sameNodeType(node1, node2)).toBeTruthy(); }); it('should return false when two nodes have different node types', () => { let node1: Node = new Document(); let node2: IRRNode = rrdom.createDocumentType('', '', ''); expect(sameNodeType(node1, node2)).toBeFalsy(); node1 = document.implementation.createDocumentType('html', '', ''); node2 = new RRDocument(); expect(sameNodeType(node1, node2)).toBeFalsy(); node1 = document.createTextNode('node1'); node2 = rrdom.createComment('node2'); expect(sameNodeType(node1, node2)).toBeFalsy(); node1 = document.createComment('node1'); node2 = rrdom.createTextNode('node2'); expect(sameNodeType(node1, node2)).toBeFalsy(); }); }); describe('test nodeMatching function', () => { const rrdom = new RRDocument(); const NodeMirror = createMirror(); const rrdomMirror = new RRNodeMirror(); beforeEach(() => { NodeMirror.reset(); rrdomMirror.reset(); }); it('should return false when two nodes have different Ids', () => { const node1 = document.createElement('div'); const node2 = rrdom.createElement('div'); NodeMirror.add(node1, getDefaultSN(node2, 1)); rrdomMirror.add(node2, getDefaultSN(node2, 2)); expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy(); }); it('should return false when two nodes have same Ids but different node types', () => { // Compare an element with a comment node let node1: Node = document.createElement('div'); NodeMirror.add(node1, getDefaultSN(rrdom.createElement('div'), 1)); let node2: IRRNode = rrdom.createComment('test'); rrdomMirror.add(node2, getDefaultSN(node2, 1)); expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy(); // Compare an element node with a text node node2 = rrdom.createTextNode(''); rrdomMirror.add(node2, getDefaultSN(node2, 1)); expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy(); // Compare a document with a text node node1 = new Document(); NodeMirror.add(node1, getDefaultSN(rrdom, 1)); expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy(); // Compare a document with a document type node node2 = rrdom.createDocumentType('', '', ''); rrdomMirror.add(node2, getDefaultSN(node2, 1)); expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy(); }); it('should compare two elements', () => { // Compare two elements with different tagNames let node1 = document.createElement('div'); let node2 = rrdom.createElement('span'); NodeMirror.add(node1, getDefaultSN(rrdom.createElement('div'), 1)); rrdomMirror.add(node2, getDefaultSN(node2, 1)); expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy(); // Compare two elements with same tagNames but different attributes node2 = rrdom.createElement('div'); node2.setAttribute('class', 'test'); rrdomMirror.add(node2, getDefaultSN(node2, 1)); expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeTruthy(); // Should return false when two elements have same tagNames and attributes but different children rrdomMirror.add(node2, getDefaultSN(node2, 2)); expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy(); }); }); }); ================================================ FILE: packages/rrdom/test/document.test.ts ================================================ /** * @jest-environment jsdom */ import { NodeType as RRNodeType } from '@rrweb/types'; import { BaseRRDocument, BaseRRDocumentType, BaseRRMediaElement, BaseRRNode, IRRDocumentType, IRRNode, } from '../src/document'; describe('Basic RRDocument implementation', () => { const RRNode = class extends BaseRRNode { public textContent: string | null; }; const RRDocument = BaseRRDocument; const RRDocumentType = BaseRRDocumentType; class RRMediaElement extends BaseRRMediaElement {} describe('Basic RRNode implementation', () => { it('should have basic properties', () => { const node = new RRNode(); expect(node.parentNode).toEqual(null); expect(node.parentElement).toEqual(null); expect(node.childNodes).toBeInstanceOf(Array); expect(node.childNodes.length).toBe(0); expect(node.ownerDocument).toBeUndefined(); expect(node.textContent).toBeUndefined(); expect(node.RRNodeType).toBeUndefined(); expect(node.nodeType).toBeUndefined(); expect(node.nodeName).toBeUndefined(); expect(node.ELEMENT_NODE).toBe(document.ELEMENT_NODE); expect(node.TEXT_NODE).toBe(document.TEXT_NODE); expect(node.firstChild).toBeNull(); expect(node.lastChild).toBeNull(); expect(node.previousSibling).toBeNull(); expect(node.nextSibling).toBeNull(); expect(node.contains).toBeDefined(); expect(node.appendChild).toBeDefined(); expect(node.insertBefore).toBeDefined(); expect(node.removeChild).toBeDefined(); expect(node.toString()).toEqual('RRNode'); }); it('can get and set first child node', () => { const parentNode = new RRNode(); const childNode1 = new RRNode(); expect(parentNode.firstChild).toBeNull(); parentNode.firstChild = childNode1; expect(parentNode.firstChild).toBe(childNode1); parentNode.firstChild = null; expect(parentNode.firstChild).toBeNull(); }); it('can get and set last child node', () => { const parentNode = new RRNode(); const childNode1 = new RRNode(); expect(parentNode.lastChild).toBeNull(); parentNode.lastChild = childNode1; expect(parentNode.lastChild).toBe(childNode1); parentNode.lastChild = null; expect(parentNode.lastChild).toBeNull(); }); it('can get and set preSibling', () => { const node1 = new RRNode(); const node2 = new RRNode(); expect(node1.previousSibling).toBeNull(); node1.previousSibling = node2; expect(node1.previousSibling).toBe(node2); node1.previousSibling = null; expect(node1.previousSibling).toBeNull(); }); it('can get and set nextSibling', () => { const node1 = new RRNode(); const node2 = new RRNode(); expect(node1.nextSibling).toBeNull(); node1.nextSibling = node2; expect(node1.nextSibling).toBe(node2); node1.nextSibling = null; expect(node1.nextSibling).toBeNull(); }); it('can get childNodes', () => { const parentNode = new RRNode(); expect(parentNode.childNodes).toBeInstanceOf(Array); expect(parentNode.childNodes.length).toBe(0); const childNode1 = new RRNode(); parentNode.firstChild = childNode1; parentNode.lastChild = childNode1; expect(parentNode.childNodes).toEqual([childNode1]); const childNode2 = new RRNode(); parentNode.lastChild = childNode2; childNode1.nextSibling = childNode2; childNode2.previousSibling = childNode1; expect(parentNode.childNodes).toEqual([childNode1, childNode2]); const childNode3 = new RRNode(); parentNode.lastChild = childNode3; childNode2.nextSibling = childNode3; childNode3.previousSibling = childNode2; expect(parentNode.childNodes).toEqual([ childNode1, childNode2, childNode3, ]); }); it('should return whether the node contains another node', () => { const parentNode = new RRNode(); expect(parentNode.contains(parentNode)).toBeTruthy(); expect(parentNode.contains(null as unknown as IRRNode)).toBeFalsy(); expect(parentNode.contains(undefined as unknown as IRRNode)).toBeFalsy(); expect(parentNode.contains({} as unknown as IRRNode)).toBeFalsy(); expect( parentNode.contains(new RRDocument().createElement('div')), ).toBeFalsy(); const childNode1 = new RRNode(); const childNode2 = new RRNode(); parentNode.firstChild = childNode1; parentNode.lastChild = childNode1; childNode1.parentNode = parentNode; expect(parentNode.contains(childNode1)).toBeTruthy(); expect(parentNode.contains(childNode2)).toBeFalsy(); parentNode.lastChild = childNode2; childNode1.nextSibling = childNode2; childNode2.previousSibling = childNode1; childNode2.parentNode = childNode1; expect(parentNode.contains(childNode1)).toBeTruthy(); expect(parentNode.contains(childNode2)).toBeTruthy(); const childNode3 = new RRNode(); expect(parentNode.contains(childNode3)).toBeFalsy(); childNode2.firstChild = childNode3; childNode2.lastChild = childNode3; childNode3.parentNode = childNode2; expect(parentNode.contains(childNode3)).toBeTruthy(); }); it('should not implement appendChild', () => { const parentNode = new RRNode(); const childNode = new RRNode(); expect(() => parentNode.appendChild(childNode)).toThrowError( `RRDomException: Failed to execute 'appendChild' on 'RRNode': This RRNode type does not support this method.`, ); }); it('should not implement insertBefore', () => { const parentNode = new RRNode(); const childNode = new RRNode(); expect(() => parentNode.insertBefore(childNode, null)).toThrowError( `RRDomException: Failed to execute 'insertBefore' on 'RRNode': This RRNode type does not support this method.`, ); }); it('should not implement removeChild', () => { const parentNode = new RRNode(); const childNode = new RRNode(); expect(() => parentNode.removeChild(childNode)).toThrowError( `RRDomException: Failed to execute 'removeChild' on 'RRNode': This RRNode type does not support this method.`, ); }); }); describe('Basic RRDocument implementation', () => { it('should have basic properties', () => { const node = new RRDocument(); expect(node.toString()).toEqual('RRDocument'); expect(node.parentNode).toEqual(null); expect(node.parentElement).toEqual(null); expect(node.childNodes).toBeInstanceOf(Array); expect(node.childNodes.length).toBe(0); expect(node.ownerDocument).toBe(node); expect(node.textContent).toBeNull(); expect(node.RRNodeType).toBe(RRNodeType.Document); expect(node.nodeType).toBe(document.nodeType); expect(node.nodeName).toBe('#document'); expect(node.compatMode).toBe('CSS1Compat'); expect(node.ELEMENT_NODE).toBe(document.ELEMENT_NODE); expect(node.TEXT_NODE).toBe(document.TEXT_NODE); expect(node.firstChild).toBeNull(); expect(node.lastChild).toBeNull(); expect(node.previousSibling).toBeNull(); expect(node.nextSibling).toBeNull(); expect(node.contains).toBeDefined(); expect(node.appendChild).toBeDefined(); expect(node.insertBefore).toBeDefined(); expect(node.removeChild).toBeDefined(); expect(node.documentElement).toBeNull(); expect(node.body).toBeNull(); expect(node.head).toBeNull(); expect(node.implementation).toBe(node); expect(node.firstElementChild).toBeNull(); expect(node.createDocument).toBeDefined(); expect(node.createDocumentType).toBeDefined(); expect(node.createElement).toBeDefined(); expect(node.createElementNS).toBeDefined(); expect(node.createTextNode).toBeDefined(); expect(node.createComment).toBeDefined(); expect(node.createCDATASection).toBeDefined(); expect(node.open).toBeDefined(); expect(node.close).toBeDefined(); expect(node.write).toBeDefined(); expect(node.toString()).toEqual('RRDocument'); }); it('can get documentElement', () => { const node = new RRDocument(); expect(node.documentElement).toBeNull(); const element = node.createElement('html'); node.appendChild(element); expect(node.documentElement).toBe(element); }); it('can get head', () => { const node = new RRDocument(); expect(node.head).toBeNull(); const element = node.createElement('html'); node.appendChild(element); expect(node.head).toBeNull(); const head = node.createElement('head'); element.appendChild(head); expect(node.head).toBe(head); }); it('can get body', () => { const node = new RRDocument(); expect(node.body).toBeNull(); const element = node.createElement('html'); node.appendChild(element); expect(node.body).toBeNull(); const body = node.createElement('body'); element.appendChild(body); expect(node.body).toBe(body); const head = node.createElement('head'); element.appendChild(head); expect(node.body).toBe(body); }); it('can get firstElementChild', () => { const node = new RRDocument(); expect(node.firstElementChild).toBeNull(); const element = node.createElement('html'); node.appendChild(element); expect(node.firstElementChild).toBe(element); }); it('can append child', () => { const node = new RRDocument(); expect(node.firstElementChild).toBeNull(); const documentType = node.createDocumentType('html', '', ''); expect(node.appendChild(documentType)).toBe(documentType); expect(node.childNodes[0]).toEqual(documentType); expect(documentType.parentElement).toBeNull(); expect(documentType.parentNode).toBe(node); expect(() => node.appendChild(documentType)).toThrowError( `RRDomException: Failed to execute 'appendChild' on 'RRNode': Only one RRDoctype on RRDocument allowed.`, ); const element = node.createElement('html'); expect(node.appendChild(element)).toBe(element); expect(node.childNodes[1]).toEqual(element); expect(element.parentElement).toBeNull(); expect(element.parentNode).toBe(node); const div = node.createElement('div'); expect(() => node.appendChild(div)).toThrowError( `RRDomException: Failed to execute 'appendChild' on 'RRNode': Only one RRElement on RRDocument allowed.`, ); }); it('can insert new child before an existing child', () => { const node = new RRDocument(); const docType = node.createDocumentType('', '', ''); expect(() => node.insertBefore(node, docType)).toThrowError( `Failed to execute 'insertBefore' on 'RRNode': The RRNode before which the new node is to be inserted is not a child of this RRNode.`, ); expect(node.insertBefore(docType, null)).toBe(docType); expect(() => node.insertBefore(docType, null)).toThrowError( `RRDomException: Failed to execute 'insertBefore' on 'RRNode': Only one RRDoctype on RRDocument allowed.`, ); node.removeChild(docType); const documentElement = node.createElement('html'); expect(() => node.insertBefore(documentElement, docType)).toThrowError( `Failed to execute 'insertBefore' on 'RRNode': The RRNode before which the new node is to be inserted is not a child of this RRNode.`, ); expect(node.insertBefore(documentElement, null)).toBe(documentElement); expect(() => node.insertBefore(documentElement, null)).toThrowError( `RRDomException: Failed to execute 'insertBefore' on 'RRNode': Only one RRElement on RRDocument allowed.`, ); expect(node.insertBefore(docType, documentElement)).toBe(docType); expect(node.childNodes[0]).toBe(docType); expect(node.childNodes[1]).toBe(documentElement); expect(docType.parentElement).toBeNull(); expect(documentElement.parentElement).toBeNull(); expect(docType.parentNode).toBe(node); expect(documentElement.parentNode).toBe(node); }); it('can remove an existing child', () => { const node = new RRDocument(); const documentType = node.createDocumentType('html', '', ''); const documentElement = node.createElement('html'); node.appendChild(documentType); node.appendChild(documentElement); expect(documentType.parentNode).toBe(node); expect(documentElement.parentNode).toBe(node); expect(() => node.removeChild(node.createElement('div')), ).toThrowErrorMatchingInlineSnapshot( `[Error: Failed to execute 'removeChild' on 'RRNode': The RRNode to be removed is not a child of this RRNode.]`, ); expect(node.removeChild(documentType)).toBe(documentType); expect(documentType.parentNode).toBeNull(); expect(node.removeChild(documentElement)).toBe(documentElement); expect(documentElement.parentNode).toBeNull(); }); it('should implement create node functions', () => { const node = new RRDocument(); expect(node.createDocument(null, '', null).RRNodeType).toEqual( RRNodeType.Document, ); expect(node.createDocumentType('', '', '').RRNodeType).toEqual( RRNodeType.DocumentType, ); expect(node.createElement('html').RRNodeType).toEqual(RRNodeType.Element); expect(node.createElementNS('', 'html').RRNodeType).toEqual( RRNodeType.Element, ); expect(node.createTextNode('text').RRNodeType).toEqual(RRNodeType.Text); expect(node.createComment('comment').RRNodeType).toEqual( RRNodeType.Comment, ); expect(node.createCDATASection('data').RRNodeType).toEqual( RRNodeType.CDATA, ); }); it('can close and open a RRDocument', () => { const node = new RRDocument(); const documentType = node.createDocumentType('html', '', ''); node.appendChild(documentType); expect(node.childNodes[0]).toBe(documentType); expect(node.close()); expect(node.open()); expect(node.childNodes.length).toEqual(0); }); it('can cover the usage of write() in rrweb-snapshot', () => { const node = new RRDocument(); node.write( '', ); expect(node.childNodes.length).toBe(1); let doctype = node.childNodes[0] as IRRDocumentType; expect(doctype.RRNodeType).toEqual(RRNodeType.DocumentType); expect(doctype.parentNode).toEqual(node); expect(doctype.name).toEqual('html'); expect(doctype.publicId).toEqual( '-//W3C//DTD XHTML 1.0 Transitional//EN', ); expect(doctype.systemId).toEqual(''); node.write( '', ); expect(node.childNodes.length).toBe(1); doctype = node.childNodes[0] as IRRDocumentType; expect(doctype.RRNodeType).toEqual(RRNodeType.DocumentType); expect(doctype.parentNode).toEqual(node); expect(doctype.name).toEqual('html'); expect(doctype.publicId).toEqual('-//W3C//DTD HTML 4.0 Transitional//EN'); expect(doctype.systemId).toEqual(''); }); }); describe('Basic RRDocumentType implementation', () => { it('should have basic properties', () => { const name = 'name', publicId = 'publicId', systemId = 'systemId'; const node = new RRDocumentType(name, publicId, systemId); expect(node.parentNode).toEqual(null); expect(node.parentElement).toEqual(null); expect(node.childNodes).toBeInstanceOf(Array); expect(node.childNodes.length).toBe(0); expect(node.ownerDocument).toBeUndefined(); expect(node.textContent).toBeNull(); expect(node.RRNodeType).toBe(RRNodeType.DocumentType); expect(node.nodeType).toBe(document.DOCUMENT_TYPE_NODE); expect(node.nodeName).toBe(name); expect(node.ELEMENT_NODE).toBe(document.ELEMENT_NODE); expect(node.TEXT_NODE).toBe(document.TEXT_NODE); expect(node.firstChild).toBeNull(); expect(node.lastChild).toBeNull(); expect(node.previousSibling).toBeNull(); expect(node.nextSibling).toBeNull(); expect(node.contains).toBeDefined(); expect(node.appendChild).toBeDefined(); expect(node.insertBefore).toBeDefined(); expect(node.removeChild).toBeDefined(); expect(node.name).toBe(name); expect(node.publicId).toBe(publicId); expect(node.systemId).toBe(systemId); expect(node.toString()).toEqual('RRDocumentType'); }); }); describe('Basic RRElement implementation', () => { const document = new RRDocument(); it('should have basic properties', () => { const node = document.createElement('div'); node.scrollLeft = 100; node.scrollTop = 200; node.attributes.id = 'id'; node.attributes.class = 'className'; expect(node.parentNode).toEqual(null); expect(node.parentElement).toEqual(null); expect(node.childNodes).toBeInstanceOf(Array); expect(node.childNodes.length).toBe(0); expect(node.ownerDocument).toBe(document); expect(node.textContent).toEqual(''); expect(node.RRNodeType).toBe(RRNodeType.Element); expect(node.nodeType).toBe(document.ELEMENT_NODE); expect(node.nodeName).toBe('DIV'); expect(node.ELEMENT_NODE).toBe(document.ELEMENT_NODE); expect(node.TEXT_NODE).toBe(document.TEXT_NODE); expect(node.firstChild).toBeNull(); expect(node.lastChild).toBeNull(); expect(node.previousSibling).toBeNull(); expect(node.nextSibling).toBeNull(); expect(node.contains).toBeDefined(); expect(node.appendChild).toBeDefined(); expect(node.insertBefore).toBeDefined(); expect(node.removeChild).toBeDefined(); expect(node.tagName).toEqual('DIV'); expect(node.attributes).toEqual({ id: 'id', class: 'className' }); expect(node.shadowRoot).toBeNull(); expect(node.scrollLeft).toEqual(100); expect(node.scrollTop).toEqual(200); expect(node.id).toEqual('id'); expect(node.className).toEqual('className'); expect(node.classList).toBeDefined(); expect(node.style).toBeDefined(); expect(node.getAttribute).toBeDefined(); expect(node.setAttribute).toBeDefined(); expect(node.setAttributeNS).toBeDefined(); expect(node.removeAttribute).toBeDefined(); expect(node.attachShadow).toBeDefined(); expect(node.dispatchEvent).toBeDefined(); expect(node.dispatchEvent(null as unknown as Event)).toBeTruthy(); expect(node.toString()).toEqual('DIV id="id" class="className" '); }); it('can get textContent', () => { const node = document.createElement('div'); node.appendChild(document.createTextNode('text1 ')); node.appendChild(document.createTextNode('text2')); expect(node.textContent).toEqual('text1 text2'); }); it('can set textContent', () => { const node = document.createElement('div'); node.appendChild(document.createTextNode('text1 ')); node.appendChild(document.createTextNode('text2')); expect(node.textContent).toEqual('text1 text2'); node.textContent = 'new text'; expect(node.textContent).toEqual('new text'); }); it('can get id', () => { const node = document.createElement('div'); expect(node.id).toEqual(''); node.attributes.id = 'idName'; expect(node.id).toEqual('idName'); }); it('can get className', () => { const node = document.createElement('div'); expect(node.className).toEqual(''); node.attributes.class = 'className'; expect(node.className).toEqual('className'); }); it('can get classList', () => { const node = document.createElement('div'); const classList = node.classList; expect(classList.add).toBeDefined(); expect(classList.remove).toBeDefined(); }); it('classList can add class name', () => { const node = document.createElement('div'); expect(node.className).toEqual(''); const classList = node.classList; classList.add('c1'); expect(node.className).toEqual('c1'); classList.add('c2'); expect(node.className).toEqual('c1 c2'); classList.add('c2'); expect(node.className).toEqual('c1 c2'); }); it('classList can remove class name', () => { const node = document.createElement('div'); expect(node.className).toEqual(''); const classList = node.classList; classList.add('c1', 'c2', 'c3'); expect(node.className).toEqual('c1 c2 c3'); classList.remove('c2'); expect(node.className).toEqual('c1 c3'); classList.remove('c3'); expect(node.className).toEqual('c1'); classList.remove('c1'); expect(node.className).toEqual(''); classList.remove('c1'); expect(node.className).toEqual(''); }); it('classList can remove duplicate class names', () => { const node = document.createElement('div'); expect(node.className).toEqual(''); node.setAttribute('class', 'c1 c1 c1'); expect(node.className).toEqual('c1 c1 c1'); const classList = node.classList; classList.remove('c1'); expect(node.className).toEqual(''); }); it('can get CSS style declaration', () => { const node = document.createElement('div'); const style = node.style; expect(style).toBeDefined(); expect(style.setProperty).toBeDefined(); expect(style.removeProperty).toBeDefined(); node.attributes.style = 'color: blue; background-color: red; width: 78%; height: 50vh !important;'; expect(node.style.color).toBe('blue'); expect(node.style.backgroundColor).toBe('red'); expect(node.style.width).toBe('78%'); expect(node.style.height).toBe('50vh !important'); }); it('can set CSS property', () => { const node = document.createElement('div'); const style = node.style; style.setProperty('color', 'red'); expect(node.attributes.style).toEqual('color: red;'); // camelCase style is unacceptable style.setProperty('backgroundColor', 'blue'); expect(node.attributes.style).toEqual('color: red;'); style.setProperty('height', '50vh', 'important'); expect(node.attributes.style).toEqual( 'color: red; height: 50vh !important;', ); // kebab-case style.setProperty('background-color', 'red'); expect(node.attributes.style).toEqual( 'color: red; height: 50vh !important; background-color: red;', ); // remove the property style.setProperty('background-color', null); expect(node.attributes.style).toEqual( 'color: red; height: 50vh !important;', ); }); it('can remove CSS property', () => { const node = document.createElement('div'); node.attributes.style = 'color: blue; background-color: red; width: 78%; height: 50vh !important;'; const style = node.style; expect(style.removeProperty('color')).toEqual('blue'); expect(node.attributes.style).toEqual( 'background-color: red; width: 78%; height: 50vh !important;', ); expect(style.removeProperty('height')).toEqual('50vh !important'); expect(node.attributes.style).toEqual( 'background-color: red; width: 78%;', ); // kebab-case expect(style.removeProperty('background-color')).toEqual('red'); expect(node.attributes.style).toEqual('width: 78%;'); style.setProperty('background-color', 'red'); expect(node.attributes.style).toEqual( 'width: 78%; background-color: red;', ); expect(style.removeProperty('backgroundColor')).toEqual(''); expect(node.attributes.style).toEqual( 'width: 78%; background-color: red;', ); // remove a non-exist property expect(style.removeProperty('margin')).toEqual(''); }); it('can parse more inline styles correctly', () => { const node = document.createElement('div'); // general node.attributes.style = 'display: inline-block; margin: 0 auto; border: 5px solid #BADA55; font-size: .75em; position:absolute;width: 33.3%; z-index:1337; font-family: "Goudy Bookletter 1911", Gill Sans Extrabold, sans-serif;'; let style = node.style; expect(style.display).toEqual('inline-block'); expect(style.margin).toEqual('0 auto'); expect(style.border).toEqual('5px solid #BADA55'); expect(style.fontSize).toEqual('.75em'); expect(style.position).toEqual('absolute'); expect(style.width).toEqual('33.3%'); expect(style.zIndex).toEqual('1337'); expect(style.fontFamily).toEqual( '"Goudy Bookletter 1911", Gill Sans Extrabold, sans-serif', ); // multiple of same property node.attributes.style = 'color: rgba(0,0,0,1);color:white'; style = node.style; expect(style.color).toEqual('white'); // url node.attributes.style = 'background-image: url("http://example.com/img.png")'; expect(node.style.backgroundImage).toEqual( 'url("http://example.com/img.png")', ); // vendor prefixes node.attributes.style = ` -moz-border-radius: 10px 5px; -webkit-border-top-left-radius: 10px; -webkit-border-bottom-left-radius: 5px; border-radius: 10px 5px; `; style = node.style; expect(style.MozBorderRadius).toEqual('10px 5px'); expect(style.WebkitBorderTopLeftRadius).toEqual('10px'); expect(style.WebkitBorderBottomLeftRadius).toEqual('5px'); expect(style.borderRadius).toEqual('10px 5px'); // comment node.attributes.style = 'top: 0; /* comment1 */ bottom: /* comment2 */42rem;'; expect(node.style.top).toEqual('0'); expect(node.style.bottom).toEqual('42rem'); // empty comment node.attributes.style = 'top: /**/0;'; expect(node.style.top).toEqual('0'); // custom property (variable) node.attributes.style = '--custom-property: value'; expect(node.style['--custom-property']).toEqual('value'); // incomplete node.attributes.style = 'overflow:'; expect(node.style.overflow).toBeUndefined(); }); it('can get attribute', () => { const node = document.createElement('div'); node.attributes.class = 'className'; expect(node.getAttribute('class')).toEqual('className'); expect(node.getAttribute('id')).toEqual(null); node.attributes.id = 'id'; expect(node.getAttribute('id')).toEqual('id'); }); it('can set attribute', () => { const node = document.createElement('div'); expect(node.getAttribute('class')).toEqual(null); node.setAttribute('class', 'className'); expect(node.getAttribute('class')).toEqual('className'); expect(node.getAttribute('id')).toEqual(null); node.setAttribute('id', 'id'); expect(node.getAttribute('id')).toEqual('id'); }); it('can setAttributeNS', () => { const node = document.createElement('div'); expect(node.getAttribute('class')).toEqual(null); node.setAttributeNS('namespace', 'class', 'className'); expect(node.getAttribute('class')).toEqual('className'); expect(node.getAttribute('id')).toEqual(null); node.setAttributeNS('namespace', 'id', 'id'); expect(node.getAttribute('id')).toEqual('id'); }); it('can remove attribute', () => { const node = document.createElement('div'); node.setAttribute('class', 'className'); expect(node.getAttribute('class')).toEqual('className'); node.removeAttribute('class'); expect(node.getAttribute('class')).toEqual(null); node.removeAttribute('id'); expect(node.getAttribute('id')).toEqual(null); }); it('can attach shadow dom', () => { const node = document.createElement('div'); expect(node.shadowRoot).toBeNull(); node.attachShadow({ mode: 'open' }); expect(node.shadowRoot).not.toBeNull(); expect(node.shadowRoot!.RRNodeType).toBe(RRNodeType.Element); expect(node.shadowRoot!.tagName).toBe('SHADOWROOT'); expect(node.parentNode).toBeNull(); }); it('can append child', () => { const node = document.createElement('div'); expect(node.childNodes.length).toBe(0); const child1 = document.createElement('span'); expect(node.appendChild(child1)).toBe(child1); expect(node.childNodes[0]).toBe(child1); expect(node.firstChild).toBe(child1); expect(node.lastChild).toBe(child1); expect(child1.previousSibling).toBeNull(); expect(child1.nextSibling).toBeNull(); expect(child1.parentElement).toBe(node); expect(child1.parentNode).toBe(node); expect(child1.ownerDocument).toBe(document); expect(node.contains(child1)).toBeTruthy(); const child2 = document.createElement('p'); expect(node.appendChild(child2)).toBe(child2); expect(node.childNodes[1]).toBe(child2); expect(node.firstChild).toBe(child1); expect(node.lastChild).toBe(child2); expect(child1.previousSibling).toBeNull(); expect(child1.nextSibling).toBe(child2); expect(child2.previousSibling).toBe(child1); expect(child2.nextSibling).toBeNull(); expect(child2.parentElement).toBe(node); expect(child2.parentNode).toBe(node); expect(child2.ownerDocument).toBe(document); expect(node.contains(child1)).toBeTruthy(); expect(node.contains(child2)).toBeTruthy(); }); it('can append a child with parent node', () => { const node = document.createElement('div'); const child = document.createElement('span'); expect(node.appendChild(child)).toBe(child); expect(node.childNodes).toEqual([child]); expect(node.appendChild(child)).toBe(child); expect(node.childNodes).toEqual([child]); expect(child.parentNode).toBe(node); const node1 = document.createElement('div'); expect(node1.appendChild(child)).toBe(child); expect(node1.childNodes).toEqual([child]); expect(child.parentNode).toBe(node1); expect(node.childNodes).toEqual([]); }); it('can insert new child before an existing child', () => { const node = document.createElement('div'); const child1 = document.createElement('h1'); const child2 = document.createElement('h2'); const child3 = document.createElement('h3'); expect(() => node.insertBefore(node, child1), ).toThrowErrorMatchingInlineSnapshot( `[Error: Failed to execute 'insertBefore' on 'RRNode': The RRNode before which the new node is to be inserted is not a child of this RRNode.]`, ); expect(node.insertBefore(child1, null)).toBe(child1); expect(node.childNodes[0]).toBe(child1); expect(node.childNodes.length).toBe(1); expect(node.firstChild).toBe(child1); expect(node.lastChild).toBe(child1); expect(child1.previousSibling).toBeNull(); expect(child1.nextSibling).toBeNull(); expect(child1.parentNode).toBe(node); expect(child1.parentElement).toBe(node); expect(child1.ownerDocument).toBe(document); expect(node.contains(child1)).toBeTruthy(); expect(node.insertBefore(child2, child1)).toBe(child2); expect(node.childNodes).toEqual([child2, child1]); expect(node.firstChild).toBe(child2); expect(node.lastChild).toBe(child1); expect(child1.previousSibling).toBe(child2); expect(child1.nextSibling).toBeNull(); expect(child2.previousSibling).toBeNull(); expect(child2.nextSibling).toBe(child1); expect(child2.parentNode).toBe(node); expect(child2.parentElement).toBe(node); expect(child2.ownerDocument).toBe(document); expect(node.contains(child2)).toBeTruthy(); expect(node.contains(child1)).toBeTruthy(); expect(node.insertBefore(child3, child1)).toBe(child3); expect(node.childNodes).toEqual([child2, child3, child1]); expect(node.firstChild).toBe(child2); expect(node.lastChild).toBe(child1); expect(child1.previousSibling).toBe(child3); expect(child1.nextSibling).toBeNull(); expect(child3.previousSibling).toBe(child2); expect(child3.nextSibling).toBe(child1); expect(child2.previousSibling).toBeNull(); expect(child2.nextSibling).toBe(child3); expect(child3.parentNode).toBe(node); expect(child3.parentElement).toBe(node); expect(child3.ownerDocument).toBe(document); expect(node.contains(child2)).toBeTruthy(); expect(node.contains(child3)).toBeTruthy(); expect(node.contains(child1)).toBeTruthy(); }); it('can insert a child with parent node', () => { const node = document.createElement('div'); const child1 = document.createElement('h1'); expect(node.insertBefore(child1, null)).toBe(child1); expect(node.childNodes).toEqual([child1]); expect(node.insertBefore(child1, child1)).toBe(child1); expect(node.childNodes).toEqual([child1]); expect(child1.parentNode).toEqual(node); const node2 = document.createElement('div'); const child2 = document.createElement('h2'); expect(node2.insertBefore(child2, null)).toBe(child2); expect(node2.childNodes).toEqual([child2]); expect(node2.insertBefore(child1, child2)).toBe(child1); expect(node2.childNodes).toEqual([child1, child2]); expect(child1.parentNode).toEqual(node2); expect(node.childNodes).toEqual([]); }); it('can remove an existing child', () => { const node = document.createElement('div'); const child1 = document.createElement('h1'); const child2 = document.createElement('h2'); const child3 = document.createElement('h3'); node.appendChild(child1); node.appendChild(child2); node.appendChild(child3); expect(node.childNodes).toEqual([child1, child2, child3]); expect(() => node.removeChild(document.createElement('div')), ).toThrowErrorMatchingInlineSnapshot( `[Error: Failed to execute 'removeChild' on 'RRNode': The RRNode to be removed is not a child of this RRNode.]`, ); // Remove the middle child. expect(node.removeChild(child2)).toBe(child2); expect(node.childNodes).toEqual([child1, child3]); expect(node.contains(child2)).toBeFalsy(); expect(node.firstChild).toBe(child1); expect(node.lastChild).toBe(child3); expect(child1.previousSibling).toBeNull(); expect(child1.nextSibling).toBe(child3); expect(child3.previousSibling).toBe(child1); expect(child3.nextSibling).toBeNull(); expect(child2.previousSibling).toBeNull(); expect(child2.nextSibling).toBeNull(); expect(child2.parentNode).toBeNull(); expect(child2.parentElement).toBeNull(); // Remove the previous child. expect(node.removeChild(child1)).toBe(child1); expect(node.childNodes).toEqual([child3]); expect(node.contains(child1)).toBeFalsy(); expect(node.firstChild).toBe(child3); expect(node.lastChild).toBe(child3); expect(child3.previousSibling).toBeNull(); expect(child3.nextSibling).toBeNull(); expect(child1.previousSibling).toBeNull(); expect(child1.nextSibling).toBeNull(); expect(child1.parentNode).toBeNull(); expect(child1.parentElement).toBeNull(); node.insertBefore(child1, child3); expect(node.childNodes).toEqual([child1, child3]); // Remove the next child. expect(node.removeChild(child3)).toBe(child3); expect(node.childNodes).toEqual([child1]); expect(node.contains(child3)).toBeFalsy(); expect(node.contains(child1)).toBeTruthy(); expect(node.firstChild).toBe(child1); expect(node.lastChild).toBe(child1); expect(child1.previousSibling).toBeNull(); expect(child1.nextSibling).toBeNull(); expect(child3.previousSibling).toBeNull(); expect(child3.nextSibling).toBeNull(); expect(child3.parentNode).toBeNull(); expect(child3.parentElement).toBeNull(); // Remove all children. expect(node.removeChild(child1)).toBe(child1); expect(node.childNodes).toEqual([]); expect(node.contains(child1)).toBeFalsy(); expect(node.contains(child2)).toBeFalsy(); expect(node.contains(child3)).toBeFalsy(); expect(node.firstChild).toBeNull(); expect(node.lastChild).toBeNull(); expect(child1.previousSibling).toBeNull(); expect(child1.nextSibling).toBeNull(); expect(child1.parentNode).toBeNull(); expect(child1.parentElement).toBeNull(); }); }); describe('Basic RRText implementation', () => { const dom = new RRDocument(); it('should have basic properties', () => { const node = dom.createTextNode('text'); expect(node.parentNode).toEqual(null); expect(node.parentElement).toEqual(null); expect(node.childNodes).toBeInstanceOf(Array); expect(node.childNodes.length).toBe(0); expect(node.ownerDocument).toBe(dom); expect(node.textContent).toEqual('text'); expect(node.RRNodeType).toBe(RRNodeType.Text); expect(node.nodeType).toBe(document.TEXT_NODE); expect(node.nodeName).toBe('#text'); expect(node.ELEMENT_NODE).toBe(document.ELEMENT_NODE); expect(node.TEXT_NODE).toBe(document.TEXT_NODE); expect(node.firstChild).toBeNull(); expect(node.lastChild).toBeNull(); expect(node.previousSibling).toBeNull(); expect(node.nextSibling).toBeNull(); expect(node.contains).toBeDefined(); expect(node.appendChild).toBeDefined(); expect(node.insertBefore).toBeDefined(); expect(node.removeChild).toBeDefined(); expect(node.toString()).toEqual('RRText text="text"'); }); it('can set textContent', () => { const node = dom.createTextNode('text'); expect(node.textContent).toEqual('text'); node.textContent = 'new text'; expect(node.textContent).toEqual('new text'); }); }); describe('Basic RRComment implementation', () => { const dom = new RRDocument(); it('should have basic properties', () => { const node = dom.createComment('comment'); expect(node.parentNode).toEqual(null); expect(node.parentElement).toEqual(null); expect(node.childNodes).toBeInstanceOf(Array); expect(node.childNodes.length).toBe(0); expect(node.ownerDocument).toBe(dom); expect(node.textContent).toEqual('comment'); expect(node.RRNodeType).toBe(RRNodeType.Comment); expect(node.nodeType).toBe(document.COMMENT_NODE); expect(node.nodeName).toBe('#comment'); expect(node.ELEMENT_NODE).toBe(document.ELEMENT_NODE); expect(node.TEXT_NODE).toBe(document.TEXT_NODE); expect(node.firstChild).toBeNull(); expect(node.lastChild).toBeNull(); expect(node.previousSibling).toBeNull(); expect(node.nextSibling).toBeNull(); expect(node.contains).toBeDefined(); expect(node.appendChild).toBeDefined(); expect(node.insertBefore).toBeDefined(); expect(node.removeChild).toBeDefined(); expect(node.toString()).toEqual('RRComment text="comment"'); }); it('can set textContent', () => { const node = dom.createComment('comment'); expect(node.textContent).toEqual('comment'); node.textContent = 'new comment'; expect(node.textContent).toEqual('new comment'); }); }); describe('Basic RRCDATASection implementation', () => { const dom = new RRDocument(); it('should have basic properties', () => { const node = dom.createCDATASection('data'); expect(node.parentNode).toEqual(null); expect(node.parentElement).toEqual(null); expect(node.childNodes).toBeInstanceOf(Array); expect(node.childNodes.length).toBe(0); expect(node.ownerDocument).toBe(dom); expect(node.textContent).toEqual('data'); expect(node.RRNodeType).toBe(RRNodeType.CDATA); expect(node.nodeType).toBe(document.CDATA_SECTION_NODE); expect(node.nodeName).toBe('#cdata-section'); expect(node.ELEMENT_NODE).toBe(document.ELEMENT_NODE); expect(node.TEXT_NODE).toBe(document.TEXT_NODE); expect(node.firstChild).toBeNull(); expect(node.lastChild).toBeNull(); expect(node.previousSibling).toBeNull(); expect(node.nextSibling).toBeNull(); expect(node.contains).toBeDefined(); expect(node.appendChild).toBeDefined(); expect(node.insertBefore).toBeDefined(); expect(node.removeChild).toBeDefined(); expect(node.toString()).toEqual('RRCDATASection data="data"'); }); it('can set textContent', () => { const node = dom.createCDATASection('data'); expect(node.textContent).toEqual('data'); node.textContent = 'new data'; expect(node.textContent).toEqual('new data'); }); }); describe('Basic RRMediaElement implementation', () => { it('should have basic properties', () => { const node = new RRMediaElement('video'); node.scrollLeft = 100; node.scrollTop = 200; expect(node.parentNode).toEqual(null); expect(node.parentElement).toEqual(null); expect(node.childNodes).toBeInstanceOf(Array); expect(node.childNodes.length).toBe(0); expect(node.ownerDocument).toBeUndefined(); expect(node.textContent).toEqual(''); expect(node.RRNodeType).toBe(RRNodeType.Element); expect(node.nodeType).toBe(document.ELEMENT_NODE); expect(node.ELEMENT_NODE).toBe(document.ELEMENT_NODE); expect(node.TEXT_NODE).toBe(document.TEXT_NODE); expect(node.firstChild).toBeNull(); expect(node.previousSibling).toBeNull(); expect(node.nextSibling).toBeNull(); expect(node.contains).toBeDefined(); expect(node.appendChild).toBeDefined(); expect(node.insertBefore).toBeDefined(); expect(node.removeChild).toBeDefined(); expect(node.tagName).toEqual('VIDEO'); expect(node.attributes).toEqual({}); expect(node.shadowRoot).toBeNull(); expect(node.scrollLeft).toEqual(100); expect(node.scrollTop).toEqual(200); expect(node.id).toEqual(''); expect(node.className).toEqual(''); expect(node.classList).toBeDefined(); expect(node.style).toBeDefined(); expect(node.getAttribute).toBeDefined(); expect(node.setAttribute).toBeDefined(); expect(node.setAttributeNS).toBeDefined(); expect(node.removeAttribute).toBeDefined(); expect(node.attachShadow).toBeDefined(); expect(node.dispatchEvent).toBeDefined(); expect(node.currentTime).toBeUndefined(); expect(node.volume).toBeUndefined(); expect(node.paused).toBeUndefined(); expect(node.muted).toBeUndefined(); expect(node.playbackRate).toBeUndefined(); expect(node.loop).toBeUndefined(); expect(node.play).toBeDefined(); expect(node.pause).toBeDefined(); expect(node.toString()).toEqual('VIDEO '); }); it('can play and pause the media', () => { const node = new RRMediaElement('video'); expect(node.paused).toBeUndefined(); node.play(); expect(node.paused).toBeFalsy(); node.pause(); expect(node.paused).toBeTruthy(); node.play(); expect(node.paused).toBeFalsy(); }); it('should not support attachShadow function', () => { const node = new RRMediaElement('video'); expect(() => node.attachShadow({ mode: 'open' })).toThrowError( `RRDomException: Failed to execute 'attachShadow' on 'RRElement': This RRElement does not support attachShadow`, ); }); }); }); ================================================ FILE: packages/rrdom/test/html/iframe.html ================================================ Iframe ================================================ FILE: packages/rrdom/test/html/main.html ================================================ Main

This is a h1 heading

This is a h1 heading with styles

Text 1

This is a paragraph

Text 2
This is an image
================================================ FILE: packages/rrdom/test/html/shadow-dom.html ================================================ shadow dom
================================================ FILE: packages/rrdom/test/virtual-dom.test.ts ================================================ /** * @jest-environment jsdom */ import * as fs from 'fs'; import * as path from 'path'; import * as puppeteer from 'puppeteer'; import { vi } from 'vitest'; import { JSDOM } from 'jsdom'; import { buildNodeWithSN, Mirror } from 'rrweb-snapshot'; import { cdataNode, commentNode, documentNode, documentTypeNode, elementNode, NodeType, NodeType as RRNodeType, textNode, } from '@rrweb/types'; import { buildFromDom, buildFromNode, createMirror, getDefaultSN, RRCanvasElement, RRDocument, RRElement, BaseRRNode as RRNode, } from '../src'; const printRRDomCode = ` /** * Print the RRDom as a string. * @param rootNode the root node of the RRDom tree * @returns printed string */ function printRRDom(rootNode, mirror) { return walk(rootNode, mirror, ''); } function walk(node, mirror, blankSpace) { let printText = \`\${blankSpace}\${mirror.getId(node)} \${node.toString()}\n\`; if(node instanceof rrdom.RRElement && node.shadowRoot) printText += walk(node.shadowRoot, mirror, blankSpace + ' '); for (const child of node.childNodes) printText += walk(child, mirror, blankSpace + ' '); if (node instanceof rrdom.RRIFrameElement) printText += walk(node.contentDocument, mirror, blankSpace + ' '); return printText; } `; describe('RRDocument for browser environment', () => { vi.setConfig({ testTimeout: 60_000 }); let mirror: Mirror; beforeEach(() => { mirror = new Mirror(); }); describe('create a RRNode from a real Node', () => { it('should support quicksmode documents', () => { // separate jsdom document as changes to the document would otherwise bleed into other tests const dom = new JSDOM(); const document = dom.window.document; expect(document.doctype).toBeNull(); // confirm compatMode is 'BackCompat' in JSDOM const rrdom = new RRDocument(); let rrNode = buildFromNode(document, rrdom, mirror)!; expect((rrNode as RRDocument).compatMode).toBe('BackCompat'); }); it('can patch serialized ID for an unserialized node', () => { // build from document expect(mirror.getMeta(document)).toBeNull(); const rrdom = new RRDocument(); let rrNode = buildFromNode(document, rrdom, mirror)!; expect(mirror.getMeta(document)).toBeDefined(); expect(mirror.getId(document)).toEqual(-2); expect(rrNode).not.toBeNull(); expect(rrdom.mirror.getMeta(rrNode)).toBeDefined(); expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Document); expect(rrdom.mirror.getId(rrNode)).toEqual(-2); expect(rrNode).toBe(rrdom); // build from document type expect(mirror.getMeta(document.doctype!)).toBeNull(); rrNode = buildFromNode(document.doctype!, rrdom, mirror)!; expect(mirror.getMeta(document.doctype!)).toBeDefined(); expect(mirror.getId(document.doctype)).toEqual(-3); expect(rrNode).not.toBeNull(); expect(rrdom.mirror.getMeta(rrNode)).toBeDefined(); expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual( RRNodeType.DocumentType, ); expect(rrdom.mirror.getId(rrNode)).toEqual(-3); // build from element expect(mirror.getMeta(document.documentElement)).toBeNull(); rrNode = buildFromNode( document.documentElement as unknown as Node, rrdom, mirror, )!; expect(mirror.getMeta(document.documentElement)).toBeDefined(); expect(mirror.getId(document.documentElement)).toEqual(-4); expect(rrNode).not.toBeNull(); expect(rrdom.mirror.getMeta(rrNode)).toBeDefined(); expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Element); expect(rrdom.mirror.getId(rrNode)).toEqual(-4); // build from text const text = document.createTextNode('text'); expect(mirror.getMeta(text)).toBeNull(); rrNode = buildFromNode(text, rrdom, mirror)!; expect(mirror.getMeta(text)).toBeDefined(); expect(mirror.getId(text)).toEqual(-5); expect(rrNode).not.toBeNull(); expect(rrdom.mirror.getMeta(rrNode)).toBeDefined(); expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Text); expect(rrdom.mirror.getId(rrNode)).toEqual(-5); // build from comment const comment = document.createComment('comment'); expect(mirror.getMeta(comment)).toBeNull(); rrNode = buildFromNode(comment, rrdom, mirror)!; expect(mirror.getMeta(comment)).toBeDefined(); expect(mirror.getId(comment)).toEqual(-6); expect(rrNode).not.toBeNull(); expect(rrdom.mirror.getMeta(rrNode)).toBeDefined(); expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Comment); expect(rrdom.mirror.getId(rrNode)).toEqual(-6); // build from CDATASection const xmlDoc = new DOMParser().parseFromString( '', 'application/xml', ); const cdata = 'Some data & then some'; var cdataSection = xmlDoc.createCDATASection(cdata); expect(mirror.getMeta(cdataSection)).toBeNull(); expect(mirror.getMeta(cdataSection)).toBeNull(); rrNode = buildFromNode(cdataSection, rrdom, mirror)!; expect(mirror.getMeta(cdataSection)).toBeDefined(); expect(mirror.getId(cdataSection)).toEqual(-7); expect(rrNode).not.toBeNull(); expect(rrdom.mirror.getMeta(rrNode)).toBeDefined(); expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.CDATA); expect(rrdom.mirror.getId(rrNode)).toEqual(-7); expect(rrNode.textContent).toEqual(cdata); }); it('can record scroll position from HTMLElements', () => { expect(document.body.scrollLeft).toEqual(0); expect(document.body.scrollTop).toEqual(0); const rrdom = new RRDocument(); let rrNode = buildFromNode(document.body, rrdom, mirror)!; expect((rrNode as RRElement).scrollLeft).toBeUndefined(); expect((rrNode as RRElement).scrollTop).toBeUndefined(); document.body.scrollLeft = 100; document.body.scrollTop = 200; expect(document.body.scrollLeft).toEqual(100); expect(document.body.scrollTop).toEqual(200); rrNode = buildFromNode(document.body, rrdom, mirror)!; expect((rrNode as RRElement).scrollLeft).toEqual(100); expect((rrNode as RRElement).scrollTop).toEqual(200); }); it('can build contentDocument from an iframe element', () => { const iframe = document.createElement('iframe'); document.body.appendChild(iframe); expect(iframe.contentDocument).not.toBeNull(); const rrdom = new RRDocument(); const RRIFrame = rrdom.createElement('iframe'); const rrNode = buildFromNode( iframe.contentDocument!, rrdom, mirror, RRIFrame, )!; expect(rrNode).not.toBeNull(); expect(rrdom.mirror.getMeta(rrNode)).toBeDefined(); expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Document); expect(rrdom.mirror.getId(rrNode)).toEqual(-2); expect(mirror.getId(iframe.contentDocument)).toEqual(-2); expect(rrNode).toBe(RRIFrame.contentDocument); }); it('can build from a shadow dom', () => { const div = document.createElement('div'); div.attachShadow({ mode: 'open' }); expect(div.shadowRoot).toBeDefined(); const rrdom = new RRDocument(); const parentRRNode = rrdom.createElement('div'); const rrNode = buildFromNode( div.shadowRoot!, rrdom, mirror, parentRRNode, )!; expect(rrNode).not.toBeNull(); expect(rrdom.mirror.getMeta(rrNode)).toBeDefined(); expect(rrdom.mirror.getId(rrNode)).toEqual(-2); expect(mirror.getId(div.shadowRoot)).toEqual(-2); expect(rrNode.RRNodeType).toEqual(RRNodeType.Element); expect((rrNode as RRElement).tagName).toEqual('SHADOWROOT'); expect(rrNode).toBe(parentRRNode.shadowRoot); }); it('can rebuild blocked element with correct dimensions', () => { // @ts-expect-error Testing buildNodeWithSN with rr elements const node = buildNodeWithSN( { id: 1, tagName: 'svg', type: NodeType.Element, isSVG: true, attributes: { rr_width: '50px', rr_height: '50px', }, childNodes: [], }, { // @ts-expect-error doc: new RRDocument(), mirror, blockSelector: '*', slimDOMOptions: {}, }, ) as RRElement; expect(node.style.width).toBe('50px'); expect(node.style.height).toBe('50px'); }); }); describe('create a RRDocument from a html document', () => { let browser: puppeteer.Browser; let code: string; let page: puppeteer.Page; beforeAll(async () => { browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'], }); code = fs.readFileSync( path.resolve(__dirname, '../dist/rrdom.umd.cjs'), 'utf8', ); }); afterAll(async () => { await browser.close(); }); beforeEach(async () => { page = await browser.newPage(); await page.goto('about:blank'); await page.evaluate(code + printRRDomCode); }); afterEach(async () => { await page.close(); }); it('can build from a common html', async () => { await page.setContent(getHtml('main.html')); const result = await page.evaluate(` const doc = new rrdom.RRDocument(); rrdom.buildFromDom(document, undefined, doc); printRRDom(doc, doc.mirror); `); expect(result).toMatchSnapshot(); }); it('can build from an iframe html ', async () => { await page.setContent(getHtml('iframe.html')); const result = await page.evaluate(` const doc = new rrdom.RRDocument(); rrdom.buildFromDom(document, undefined, doc); printRRDom(doc, doc.mirror); `); expect(result).toMatchSnapshot(); }); it('can build from a html containing nested shadow doms', async () => { await page.setContent(getHtml('shadow-dom.html')); const result = await page.evaluate(` const doc = new rrdom.RRDocument(); rrdom.buildFromDom(document, undefined, doc); printRRDom(doc, doc.mirror); `); expect(result).toMatchSnapshot(); }); it('can build from a xml page', async () => { const result = await page.evaluate(` var docu = new DOMParser().parseFromString('', 'application/xml'); var cdata = docu.createCDATASection('Some data & then some'); docu.getElementsByTagName('xml')[0].appendChild(cdata); // Displays: data & then some]]> const doc = new rrdom.RRDocument(); rrdom.buildFromDom(docu, undefined, doc); printRRDom(doc, doc.mirror); `); expect(result).toMatchSnapshot(); }); }); describe('RRDocument build for virtual dom', () => { it('can access a unique, decremented unserializedId every time', () => { const node = new RRDocument(); for (let i = 2; i <= 100; i++) expect(node.unserializedId).toBe(-i); }); it('can create a new RRDocument', () => { const dom = new RRDocument(); const newDom = dom.createDocument('', ''); expect(newDom).toBeInstanceOf(RRDocument); }); it('can create a new RRDocument receiving a mirror parameter', () => { const mirror = createMirror(); const dom = new RRDocument(mirror); const newDom = dom.createDocument('', ''); expect(newDom).toBeInstanceOf(RRDocument); expect(dom.mirror).toBe(mirror); }); it('can build a RRDocument from a real Dom', () => { const result = buildFromDom(document, mirror); expect(result.childNodes.length).toBe(2); expect(result.documentElement).toBeDefined(); expect(result.head).toBeDefined(); expect(result.head!.tagName).toBe('HEAD'); expect(result.body).toBeDefined(); expect(result.body!.tagName).toBe('BODY'); }); it('can destroy a RRDocument tree', () => { const dom = new RRDocument(); const node1 = dom.createDocumentType('', '', ''); dom.appendChild(node1); dom.mirror.add(node1, { id: 0, type: NodeType.DocumentType, name: '', publicId: '', systemId: '', }); const node2 = dom.createElement('html'); dom.appendChild(node2); dom.mirror.add(node1, { id: 1, type: NodeType.Document, childNodes: [], }); expect(dom.childNodes.length).toEqual(2); expect(dom.mirror.has(0)).toBeTruthy(); expect(dom.mirror.has(1)).toBeTruthy(); dom.destroyTree(); expect(dom.childNodes.length).toEqual(0); expect(dom.mirror.has(0)).toBeFalsy(); expect(dom.mirror.has(1)).toBeFalsy(); }); it('can close and open a RRDocument', () => { const dom = new RRDocument(); const documentType = dom.createDocumentType('html', '', ''); dom.appendChild(documentType); expect(dom.childNodes[0]).toBe(documentType); expect(dom.unserializedId).toBe(-2); expect(dom.unserializedId).toBe(-3); expect(dom.close()); expect(dom.open()); expect(dom.childNodes.length).toEqual(0); expect(dom.unserializedId).toBe(-2); }); it('can execute a dummy getContext function in RRCanvasElement', () => { const canvas = new RRCanvasElement('CANVAS'); expect(canvas.getContext).toBeDefined(); expect(canvas.getContext()).toBeNull(); }); describe('Mirror in the RRDocument', () => { it('should have a mirror to store id and node', () => { const dom = new RRDocument(); expect(dom.mirror).toBeDefined(); const node1 = dom.createElement('div'); dom.mirror.add(node1, getDefaultSN(node1, 0)); const node2 = dom.createTextNode('text'); dom.mirror.add(node2, getDefaultSN(node2, 1)); expect(dom.mirror.getNode(0)).toBe(node1); expect(dom.mirror.getNode(1)).toBe(node2); expect(dom.mirror.getNode(2)).toBeNull(); expect(dom.mirror.getNode(-1)).toBeNull(); }); it('can get node id', () => { const dom = new RRDocument(); const node1 = dom.createElement('div'); dom.mirror.add(node1, getDefaultSN(node1, 0)); expect(dom.mirror.getId(node1)).toEqual(0); const node2 = dom.createTextNode('text'); expect(dom.mirror.getId(node2)).toEqual(-1); expect(dom.mirror.getId(null as unknown as RRNode)).toEqual(-1); }); it('has() should return whether the mirror has an ID', () => { const dom = new RRDocument(); const node1 = dom.createElement('div'); dom.mirror.add(node1, getDefaultSN(node1, 0)); const node2 = dom.createTextNode('text'); dom.mirror.add(node2, getDefaultSN(node2, 1)); expect(dom.mirror.has(0)).toBeTruthy(); expect(dom.mirror.has(1)).toBeTruthy(); expect(dom.mirror.has(2)).toBeFalsy(); expect(dom.mirror.has(-1)).toBeFalsy(); }); it('can remove node from the mirror', () => { const dom = new RRDocument(); const node1 = dom.createElement('div'); dom.mirror.add(node1, getDefaultSN(node1, 0)); const node2 = dom.createTextNode('text'); dom.mirror.add(node2, getDefaultSN(node2, 1)); node1.appendChild(node2); expect(dom.mirror.has(0)).toBeTruthy(); expect(dom.mirror.has(1)).toBeTruthy(); dom.mirror.removeNodeFromMap(node2); expect(dom.mirror.has(0)).toBeTruthy(); expect(dom.mirror.has(1)).toBeFalsy(); dom.mirror.add(node2, getDefaultSN(node2, 1)); expect(dom.mirror.has(1)).toBeTruthy(); // To remove node1 and its child node2 from the mirror. dom.mirror.removeNodeFromMap(node1); expect(dom.mirror.has(0)).toBeFalsy(); expect(dom.mirror.has(1)).toBeFalsy(); }); it('can reset the mirror', () => { const dom = new RRDocument(); const node1 = dom.createElement('div'); dom.mirror.add(node1, getDefaultSN(node1, 0)); const node2 = dom.createTextNode('text'); dom.mirror.add(node2, getDefaultSN(node2, 1)); expect(dom.mirror.has(0)).toBeTruthy(); expect(dom.mirror.has(1)).toBeTruthy(); dom.mirror.reset(); expect(dom.mirror.has(0)).toBeFalsy(); expect(dom.mirror.has(1)).toBeFalsy(); }); it('hasNode() should return whether the mirror has a node', () => { const dom = new RRDocument(); const node1 = dom.createElement('div'); const node2 = dom.createTextNode('text'); expect(dom.mirror.hasNode(node1)).toBeFalsy(); dom.mirror.add(node1, getDefaultSN(node1, 0)); expect(dom.mirror.hasNode(node1)).toBeTruthy(); expect(dom.mirror.hasNode(node2)).toBeFalsy(); dom.mirror.add(node2, getDefaultSN(node2, 1)); expect(dom.mirror.hasNode(node2)).toBeTruthy(); }); it('can get all IDs from the mirror', () => { const dom = new RRDocument(); expect(dom.mirror.getIds().length).toBe(0); const node1 = dom.createElement('div'); dom.mirror.add(node1, getDefaultSN(node1, 0)); const node2 = dom.createTextNode('text'); dom.mirror.add(node2, getDefaultSN(node2, 1)); expect(dom.mirror.getIds().length).toBe(2); expect(dom.mirror.getIds()).toStrictEqual([0, 1]); }); it('can replace nodes', () => { const dom = new RRDocument(); expect(dom.mirror.getIds().length).toBe(0); const node1 = dom.createElement('div'); dom.mirror.add(node1, getDefaultSN(node1, 0)); expect(dom.mirror.getNode(0)).toBe(node1); const node2 = dom.createTextNode('text'); dom.mirror.replace(0, node2); expect(dom.mirror.getNode(0)).toBe(node2); }); }); }); describe('can get default SN value from a RRNode', () => { const rrdom = new RRDocument(); it('can get from RRDocument', () => { const node = rrdom; const sn = getDefaultSN(node, 1); expect(sn).toBeDefined(); expect(sn.type).toEqual(RRNodeType.Document); expect((sn as documentNode).childNodes).toBeInstanceOf(Array); }); it('can get from RRDocumentType', () => { const name = 'name', publicId = 'publicId', systemId = 'systemId'; const node = rrdom.createDocumentType(name, publicId, systemId); const sn = getDefaultSN(node, 1); expect(sn).toBeDefined(); expect(sn.type).toEqual(RRNodeType.DocumentType); expect((sn as documentTypeNode).name).toEqual(name); expect((sn as documentTypeNode).publicId).toEqual(publicId); expect((sn as documentTypeNode).systemId).toEqual(systemId); }); it('can get from RRElement', () => { const node = rrdom.createElement('div'); const sn = getDefaultSN(node, 1); expect(sn).toBeDefined(); expect(sn.type).toEqual(RRNodeType.Element); expect((sn as elementNode).tagName).toEqual('div'); expect((sn as elementNode).attributes).toBeDefined(); expect((sn as elementNode).childNodes).toBeInstanceOf(Array); }); it('can get from RRText', () => { const node = rrdom.createTextNode('text'); const sn = getDefaultSN(node, 1); expect(sn).toBeDefined(); expect(sn.type).toEqual(RRNodeType.Text); expect((sn as textNode).textContent).toEqual('text'); }); it('can get from RRComment', () => { const node = rrdom.createComment('comment'); const sn = getDefaultSN(node, 1); expect(sn).toBeDefined(); expect(sn.type).toEqual(RRNodeType.Comment); expect((sn as commentNode).textContent).toEqual('comment'); }); it('can get from RRCDATASection', () => { const node = rrdom.createCDATASection('data'); const sn = getDefaultSN(node, 1); expect(sn).toBeDefined(); expect(sn.type).toEqual(RRNodeType.CDATA); expect((sn as cdataNode).textContent).toEqual(''); }); }); }); function getHtml(fileName: string) { const filePath = path.resolve(__dirname, `./html/${fileName}`); return fs.readFileSync(filePath, 'utf8'); } ================================================ FILE: packages/rrdom/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "include": ["src"], "compilerOptions": { "rootDir": "src", "tsBuildInfoFile": "./tsconfig.tsbuildinfo" }, "references": [ { "path": "../rrweb-snapshot" }, { "path": "../types" } ] } ================================================ FILE: packages/rrdom/vite.config.js ================================================ import path from 'path'; import config from '../../vite.config.default'; export default config(path.resolve(__dirname, 'src/index.ts'), 'rrdom'); ================================================ FILE: packages/rrdom/vitest.config.ts ================================================ /// import { defineProject, mergeConfig } from 'vitest/config'; import configShared from '../../vitest.config'; export default mergeConfig( configShared, defineProject({ test: { globals: true, }, }), ); ================================================ FILE: packages/rrdom-nodejs/.gitignore ================================================ dist es lib typings ================================================ FILE: packages/rrdom-nodejs/.vscode/extensions.json ================================================ { "recommendations": ["vitest.explorer"] } ================================================ FILE: packages/rrdom-nodejs/.vscode/launch.json ================================================ { // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Debug Current Test File", "autoAttachChildProcesses": true, "skipFiles": ["/**", "**/node_modules/**"], "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", "args": ["run", "${relativeFile}"], "smartStep": true, "console": "integratedTerminal" } ] } ================================================ FILE: packages/rrdom-nodejs/CHANGELOG.md ================================================ # rrdom-nodejs ## 2.0.0-alpha.20 ### Patch Changes - Updated dependencies []: - rrdom@2.0.0-alpha.20 - @rrweb/types@2.0.0-alpha.20 ## 2.0.0-alpha.19 ### Patch Changes - Updated dependencies []: - rrdom@2.0.0-alpha.19 - @rrweb/types@2.0.0-alpha.19 ## 2.0.0-alpha.18 ### Patch Changes - [#1593](https://github.com/rrweb-io/rrweb/pull/1593) [`5a78938`](https://github.com/rrweb-io/rrweb/commit/5a789385a341311ba327a768fe0e2f0f2f5002ee) Thanks [@daibhin](https://github.com/daibhin)! - `NodeType` enum was moved from rrweb-snapshot to @rrweb/types The following types where moved from rrweb-snapshot to @rrweb/types: `documentNode`, `documentTypeNode`, `legacyAttributes`, `textNode`, `cdataNode`, `commentNode`, `elementNode`, `serializedNode`, `serializedNodeWithId`, `serializedElementNodeWithId`, `serializedTextNodeWithId`, `IMirror`, `INode`, `mediaAttributes`, `attributes` and `DataURLOptions` - Updated dependencies [[`8e55c45`](https://github.com/rrweb-io/rrweb/commit/8e55c455ff2987a3b5f367f23f48c1f2de74ce45), [`5a78938`](https://github.com/rrweb-io/rrweb/commit/5a789385a341311ba327a768fe0e2f0f2f5002ee)]: - rrdom@2.0.0-alpha.18 - @rrweb/types@2.0.0-alpha.18 ## 2.0.0-alpha.17 ### Patch Changes - Updated dependencies [[`40bbc25`](https://github.com/rrweb-io/rrweb/commit/40bbc25fc287badc317a53f2d3f21b1c9f2b211b), [`335639a`](https://github.com/rrweb-io/rrweb/commit/335639af9b0ce7f70eb0f38ce113d877c7325158), [`335639a`](https://github.com/rrweb-io/rrweb/commit/335639af9b0ce7f70eb0f38ce113d877c7325158), [`d350da8`](https://github.com/rrweb-io/rrweb/commit/d350da8552d8616dd118ee550bdfbce082986562), [`be6bf52`](https://github.com/rrweb-io/rrweb/commit/be6bf52c248c35de1b3491e3a3440ff61f876414)]: - rrweb-snapshot@2.0.0-alpha.17 - rrdom@2.0.0-alpha.17 ## 2.0.0-alpha.16 ### Patch Changes - Updated dependencies [[`a2c8a1a`](https://github.com/rrweb-io/rrweb/commit/a2c8a1a37bfcf8389b280af792262c8263a979a3), [`d08624c`](https://github.com/rrweb-io/rrweb/commit/d08624cb28add386c3618a0e6607424c3f1884d8)]: - rrweb-snapshot@2.0.0-alpha.16 - rrdom@2.0.0-alpha.16 ## 2.0.0-alpha.15 ### Major Changes - [#1497](https://github.com/rrweb-io/rrweb/pull/1497) [`2606a2a`](https://github.com/rrweb-io/rrweb/commit/2606a2a28f2a6d897b8ae4ea3ec40ef0eeacbfaf) Thanks [@Juice10](https://github.com/Juice10)! - Distributed files have new filenames, paths and extensions. **Important: If you reference distributed files or types directly, you might have to update your paths/filenames. E.g. you import from `rrweb/typings/...` or `rrdom/es`. However you run `import rrweb from 'rrweb'` you won't notice a difference with this change.** If you include rrweb files directly in a script tag, you might have to update that path to include a the `.umd.cjs` files instead. All `.js` files now use ES modules which can be used in modern browsers, node.js and bundlers that support ES modules. All npm packages now also ship `.cjs` and `.umd.cjs` files. The `.umd.cjs` files are CommonJS modules that bundle all files together to make it easy to ship one file to browser environments (similar to the previous `.js` files). The `.cjs` files are CommonJS modules that can be used in older Node.js environments. Types should be better defined in `package.json` and if you need specific types they might be exported from new packages (for example `PlayerMachineState` and `SpeedMachineState` are now exported from `@rrweb/replay`). Check the `package.json`'s `main` and `exports` field for the available files. ### Patch Changes - Updated dependencies [[`4014305`](https://github.com/rrweb-io/rrweb/commit/40143059446cee5c042c007b1c2e976f36e172f5), [`82f6fec`](https://github.com/rrweb-io/rrweb/commit/82f6fecf36413ecbc994a510144487f1de20d1d5), [`2606a2a`](https://github.com/rrweb-io/rrweb/commit/2606a2a28f2a6d897b8ae4ea3ec40ef0eeacbfaf), [`f3cf092`](https://github.com/rrweb-io/rrweb/commit/f3cf0928df30d5ed5c0d573c524be6e744c0f8d3), [`e08706a`](https://github.com/rrweb-io/rrweb/commit/e08706ae60268b6eb05c6292ef948c71bd423ce3)]: - rrweb-snapshot@2.0.0-alpha.15 - rrdom@2.0.0-alpha.15 ## 2.0.0-alpha.14 ### Patch Changes - Updated dependencies [[`03b5216`](https://github.com/rrweb-io/rrweb/commit/03b5216a9403f1509b4f69d1d71ef9874277fe91), [`46f1b25`](https://github.com/rrweb-io/rrweb/commit/46f1b252a5919c68c68e825bd6089cc2e7d34e7c), [`cbbd1e5`](https://github.com/rrweb-io/rrweb/commit/cbbd1e55f1f7fa2eed9fa11e4152b509bdfd88f7), [`5e7943d`](https://github.com/rrweb-io/rrweb/commit/5e7943dbae6e2cde76c484bdd26bc0b96f1b6dce), [`c0f83af`](https://github.com/rrweb-io/rrweb/commit/c0f83afab8f1565633de0e986b7e96fa56f2d25c), [`e96f668`](https://github.com/rrweb-io/rrweb/commit/e96f668c86bd0ab5dc190bb2957a170271bb2ebc)]: - rrweb-snapshot@2.0.0-alpha.14 - rrdom@2.0.0-alpha.14 ## 2.0.0-alpha.13 ### Patch Changes - Updated dependencies [[`123a81e`](https://github.com/rrweb-io/rrweb/commit/123a81e12d072cd95d701231176d7eb2d03b3961), [`123a81e`](https://github.com/rrweb-io/rrweb/commit/123a81e12d072cd95d701231176d7eb2d03b3961), [`f7c6973`](https://github.com/rrweb-io/rrweb/commit/f7c6973ae9c21b9ea014bdef7101f976f04d9356)]: - rrdom@2.0.0-alpha.13 - rrweb-snapshot@2.0.0-alpha.13 ## 2.0.0-alpha.12 ### Patch Changes - Updated dependencies [[`58c9104`](https://github.com/rrweb-io/rrweb/commit/58c9104eddc8b7994a067a97daae5684e42f892f), [`a2be77b`](https://github.com/rrweb-io/rrweb/commit/a2be77b82826c4be0e7f3c7c9f7ee50476d5f6f8), [`a7c33f2`](https://github.com/rrweb-io/rrweb/commit/a7c33f2093c4d92faf7ae25e8bb0e088d122c13b), [`8aea5b0`](https://github.com/rrweb-io/rrweb/commit/8aea5b00a4dfe5a6f59bd2ae72bb624f45e51e81), [`314a8dd`](https://github.com/rrweb-io/rrweb/commit/314a8dde5a13095873b89d07bac7c949918bf817), [`e607e83`](https://github.com/rrweb-io/rrweb/commit/e607e83b21d45131a56c1ff606e9519a5b475fc1), [`7c0dc9d`](https://github.com/rrweb-io/rrweb/commit/7c0dc9dfe1564c9d6624557c5b394e7844955882), [`07ac5c9`](https://github.com/rrweb-io/rrweb/commit/07ac5c9e1371824ec3ffb705f9250bbe10f4b73e)]: - rrweb-snapshot@2.0.0-alpha.12 - rrdom@2.0.0-alpha.12 ## 2.0.0-alpha.11 ### Patch Changes - Updated dependencies [[`11f6567`](https://github.com/rrweb-io/rrweb/commit/11f6567fd81ef9ed0f954a7b6d5e39653f56004f), [`efdc167`](https://github.com/rrweb-io/rrweb/commit/efdc167ca6c039d04af83612e3d92498bb9b41a7), [`efdc167`](https://github.com/rrweb-io/rrweb/commit/efdc167ca6c039d04af83612e3d92498bb9b41a7)]: - rrweb-snapshot@2.0.0-alpha.11 - rrdom@2.0.0-alpha.11 ## 2.0.0-alpha.10 ### Patch Changes - Updated dependencies [[`c6600e7`](https://github.com/rrweb-io/rrweb/commit/c6600e742b8ec0b6295816bb5de9edcd624d975e)]: - rrweb-snapshot@2.0.0-alpha.10 - rrdom@2.0.0-alpha.10 ## 2.0.0-alpha.9 ### Patch Changes - Updated dependencies [[`b798f2d`](https://github.com/rrweb-io/rrweb/commit/b798f2dbc07b5a24dcaf40d164159200b6c0679d), [`d7c72bf`](https://github.com/rrweb-io/rrweb/commit/d7c72bff0724b46a6fa94af455220626a27104fe)]: - rrdom@2.0.0-alpha.9 - rrweb-snapshot@2.0.0-alpha.9 ## 2.0.0-alpha.8 ### Patch Changes - Updated dependencies [[`bc84246`](https://github.com/rrweb-io/rrweb/commit/bc84246f78849a80dbb8fe9b4e76117afcc5c3f7), [`d0fdc0f`](https://github.com/rrweb-io/rrweb/commit/d0fdc0f273bb156a1faab4782b40fbec8dccf915)]: - rrweb-snapshot@2.0.0-alpha.8 - rrdom@2.0.0-alpha.8 ## 2.0.0-alpha.7 ### Patch Changes - Updated dependencies [[`d2582e9`](https://github.com/rrweb-io/rrweb/commit/d2582e9a81197130cd93bc1dd778e16fddfb0be3), [`e7f0c80`](https://github.com/rrweb-io/rrweb/commit/e7f0c808c3f348fb27d1acd5fa300a5d92b14d00)]: - rrweb-snapshot@2.0.0-alpha.7 - rrdom@2.0.0-alpha.7 ## 2.0.0-alpha.6 ### Patch Changes - Updated dependencies [[`c28ef5f`](https://github.com/rrweb-io/rrweb/commit/c28ef5f658abb93086504581409cf7a376db48dc), [`f6f07e9`](https://github.com/rrweb-io/rrweb/commit/f6f07e953376634a4caf28ff8cbfed5a017c4347), [`eac9b18`](https://github.com/rrweb-io/rrweb/commit/eac9b18bbfa3c350797b99b583dd93a5fc32b828), [`f27e545`](https://github.com/rrweb-io/rrweb/commit/f27e545e1871ed2c1753d37543f556e8ddc406b4), [`8e47ca1`](https://github.com/rrweb-io/rrweb/commit/8e47ca1021ebb4fc036b37623ef10abf7976d6dd)]: - rrweb-snapshot@2.0.0-alpha.6 - rrdom@2.0.0-alpha.6 ## 2.0.0-alpha.5 ### Major Changes - [#1127](https://github.com/rrweb-io/rrweb/pull/1127) [`3cc4323`](https://github.com/rrweb-io/rrweb/commit/3cc4323094065a12f8b65afecd45061d604e245f) Thanks [@YunFeng0817](https://github.com/YunFeng0817)! - Refactor: Improve performance by 80% in a super large benchmark case. 1. Refactor: change the data structure of childNodes from array to linked list 2. Improve the performance of the "contains" function. New algorithm will reduce the complexity from O(n) to O(logn) ### Patch Changes - [#1126](https://github.com/rrweb-io/rrweb/pull/1126) [`227d43a`](https://github.com/rrweb-io/rrweb/commit/227d43abb93d57cadc70c760b28c46911bf7d8ff) Thanks [@YunFeng0817](https://github.com/YunFeng0817)! - Refactor all suffix of bundled scripts with commonjs module from 'js' to cjs [#1087](https://github.com/rrweb-io/rrweb/pull/1087). - Updated dependencies [[`1385f7a`](https://github.com/rrweb-io/rrweb/commit/1385f7acc0052f83be1458a7b00e18c026ee393f), [`227d43a`](https://github.com/rrweb-io/rrweb/commit/227d43abb93d57cadc70c760b28c46911bf7d8ff), [`227d43a`](https://github.com/rrweb-io/rrweb/commit/227d43abb93d57cadc70c760b28c46911bf7d8ff), [`3cc4323`](https://github.com/rrweb-io/rrweb/commit/3cc4323094065a12f8b65afecd45061d604e245f)]: - rrweb-snapshot@2.0.0-alpha.5 - rrdom@2.0.0-alpha.5 ================================================ FILE: packages/rrdom-nodejs/README.md ================================================ # rrdom-nodejs `rrdom-nodejs` is a Node.js implementation of the [`rrdom`](../rrdom/) library. It allows you to replay and inspect recorded user interactions with `rrweb` in a Node.js environment. See the [guide](../../guide.md) for more info on rrweb. ## Sponsors [Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site. ### Gold Sponsors 🥇
sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Silver Sponsors 🥈
sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Bronze Sponsors 🥉
sponsor sponsor sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Backers ## Core Team Members

Yuyz0112


Yun Feng


eoghanmurray


Juice10
open for rrweb consulting
## Who's using rrweb?
Smart screen recording for SaaS
The first ever UX automation tool Remote Access & Co-Browsing The open source, fullstack Monitoring Platform. Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions.
Intercept, Modify, Record & Replay HTTP Requests. In-app bug reporting & customer feedback platform. Self-hosted website analytics with heatmaps and session recordings. Interactive product demos for small marketing teams
================================================ FILE: packages/rrdom-nodejs/package.json ================================================ { "name": "rrdom-nodejs", "version": "2.0.0-alpha.20", "scripts": { "dev": "vite build --watch", "build": "yarn turbo run prepublish", "check-types": "tsc -noEmit", "test": "vitest run", "test:watch": "vitest watch", "prepublish": "tsc -noEmit && vite build", "lint": "yarn eslint src/**/*.ts" }, "keywords": [ "rrweb", "rrdom-nodejs" ], "license": "MIT", "type": "module", "main": "./dist/rrdom-nodejs.umd.cjs", "module": "./dist/rrdom-nodejs.js", "unpkg": "./dist/rrdom-nodejs.umd.cjs", "typings": "dist/index.d.ts", "exports": { ".": { "import": { "types": "./dist/index.d.ts", "default": "./dist/rrdom-nodejs.js" }, "require": { "types": "./dist/index.d.cts", "default": "./dist/rrdom-nodejs.umd.cjs" } } }, "files": [ "umd", "dist", "package.json" ], "devDependencies": { "@types/cssom": "^0.4.1", "@types/cssstyle": "^2.2.1", "@types/nwsapi": "^2.2.2", "@types/puppeteer": "^5.4.4", "@typescript-eslint/eslint-plugin": "^5.23.0", "@typescript-eslint/parser": "^5.23.0", "compare-versions": "^4.1.3", "eslint": "^8.15.0", "puppeteer": "^9.1.1", "vite": "^6.0.1", "vite-plugin-dts": "^3.9.1", "vitest": "^1.4.0", "typescript": "^5.4.5" }, "dependencies": { "cssom": "^0.5.0", "cssstyle": "^2.3.0", "nwsapi": "2.2.0", "rrdom": "^2.0.0-alpha.20", "@rrweb/types": "^2.0.0-alpha.20" } } ================================================ FILE: packages/rrdom-nodejs/src/document-nodejs.ts ================================================ import { NodeType as RRNodeType } from '@rrweb/types'; import type { NWSAPI } from 'nwsapi'; import type { CSSStyleDeclaration as CSSStyleDeclarationType } from 'cssstyle'; import { BaseRRCDATASection, BaseRRComment, BaseRRDocument, BaseRRDocumentType, BaseRRElement, BaseRRMediaElement, BaseRRNode, BaseRRText, ClassList, type IRRDocument, type CSSStyleDeclaration, } from 'rrdom'; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires const nwsapi = require('nwsapi'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires const cssom = require('cssom'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires const cssstyle = require('cssstyle'); export class RRWindow { scrollLeft = 0; scrollTop = 0; scrollTo(options?: ScrollToOptions) { if (!options) return; if (typeof options.left === 'number') this.scrollLeft = options.left; if (typeof options.top === 'number') this.scrollTop = options.top; } } export class RRDocument extends BaseRRDocument implements IRRDocument { readonly nodeName = '#document' as const; private _nwsapi: NWSAPI | undefined; get nwsapi(): NWSAPI { if (!this._nwsapi) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call this._nwsapi = nwsapi({ document: this as unknown as Document, DOMException: null as unknown as new ( message?: string, name?: string, ) => DOMException, }) as NWSAPI; this._nwsapi.configure({ LOGERRORS: false, IDS_DUPES: true, MIXEDCASE: true, }); } return this._nwsapi; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore get documentElement(): RRElement | null { return super.documentElement as RRElement | null; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore get body(): RRElement | null { return super.body as RRElement | null; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore get head() { return super.head as RRElement | null; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore get implementation(): RRDocument { return this; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore get firstElementChild(): RRElement | null { return this.documentElement; } appendChild(childNode: BaseRRNode) { return super.appendChild(childNode); } insertBefore(newChild: BaseRRNode, refChild: BaseRRNode | null) { return super.insertBefore(newChild, refChild); } querySelectorAll(selectors: string): BaseRRNode[] { return this.nwsapi.select(selectors) as unknown as BaseRRNode[]; } getElementsByTagName(tagName: string): RRElement[] { if (this.documentElement) return this.documentElement.getElementsByTagName(tagName); return []; } getElementsByClassName(className: string): RRElement[] { if (this.documentElement) return this.documentElement.getElementsByClassName(className); return []; } getElementById(elementId: string): RRElement | null { if (this.documentElement) return this.documentElement.getElementById(elementId); return null; } createDocument( // eslint-disable-next-line @typescript-eslint/no-unused-vars _namespace: string | null, // eslint-disable-next-line @typescript-eslint/no-unused-vars _qualifiedName: string | null, // eslint-disable-next-line @typescript-eslint/no-unused-vars _doctype?: DocumentType | null, ) { return new RRDocument(); } createDocumentType( qualifiedName: string, publicId: string, systemId: string, ) { const documentTypeNode = new RRDocumentType( qualifiedName, publicId, systemId, ); documentTypeNode.ownerDocument = this; return documentTypeNode; } createElement( tagName: K, ): RRElementType; createElement(tagName: string): RRElement; createElement(tagName: string) { const upperTagName = tagName.toUpperCase(); let element; switch (upperTagName) { case 'AUDIO': case 'VIDEO': element = new RRMediaElement(upperTagName); break; case 'IFRAME': element = new RRIFrameElement(upperTagName); break; case 'IMG': element = new RRImageElement(upperTagName); break; case 'CANVAS': element = new RRCanvasElement(upperTagName); break; case 'STYLE': element = new RRStyleElement(upperTagName); break; default: element = new RRElement(upperTagName); break; } element.ownerDocument = this; return element; } createElementNS(_namespaceURI: string, qualifiedName: string) { return this.createElement(qualifiedName as keyof HTMLElementTagNameMap); } createComment(data: string) { const commentNode = new RRComment(data); commentNode.ownerDocument = this; return commentNode; } createCDATASection(data: string) { const sectionNode = new RRCDATASection(data); sectionNode.ownerDocument = this; return sectionNode; } createTextNode(data: string) { const textNode = new RRText(data); textNode.ownerDocument = this; return textNode; } } export class RRDocumentType extends BaseRRDocumentType {} export class RRElement extends BaseRRElement { private _style: CSSStyleDeclarationType; constructor(tagName: string) { super(tagName); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access this._style = new cssstyle.CSSStyleDeclaration(); const style = this._style; Object.defineProperty(this.attributes, 'style', { get() { return style.cssText; }, set(cssText: string) { style.cssText = cssText; }, }); } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore get style() { return this._style as unknown as CSSStyleDeclaration; } attachShadow(_init: ShadowRootInit): RRElement { return super.attachShadow(_init) as RRElement; } appendChild(newChild: BaseRRNode): BaseRRNode { return super.appendChild(newChild) as BaseRRNode; } insertBefore(newChild: BaseRRNode, refChild: BaseRRNode | null): BaseRRNode { return super.insertBefore(newChild, refChild) as BaseRRNode; } getAttribute(name: string) { const upperName = name && name.toLowerCase(); if (upperName in this.attributes) return this.attributes[upperName]; return null; } setAttribute(name: string, attribute: string) { this.attributes[name.toLowerCase()] = attribute; } removeAttribute(name: string) { delete this.attributes[name.toLowerCase()]; } get firstElementChild(): RRElement | null { for (const child of this.childNodes) if (child.RRNodeType === RRNodeType.Element) return child as RRElement; return null; } get nextElementSibling(): RRElement | null { const parentNode = this.parentNode; if (!parentNode) return null; const siblings = parentNode.childNodes; const index = siblings.indexOf(this); for (let i = index + 1; i < siblings.length; i++) if (siblings[i] instanceof RRElement) return siblings[i] as RRElement; return null; } querySelectorAll(selectors: string): BaseRRNode[] { const result: RRElement[] = []; if (this.ownerDocument !== null) { (this.ownerDocument as RRDocument).nwsapi.select( selectors, this as unknown as Element, (element) => { if ((element as unknown as RRElement) !== this) result.push(element as unknown as RRElement); }, ) as unknown as BaseRRNode[]; } return result; } getElementById(elementId: string): RRElement | null { if (this.id === elementId) return this; for (const child of this.childNodes) { if (child instanceof RRElement) { const result = child.getElementById(elementId); if (result !== null) return result; } } return null; } getElementsByClassName(className: string): RRElement[] { let elements: RRElement[] = []; const queryClassList = new ClassList(className); // Make sure this element has all queried class names. if ( this instanceof RRElement && queryClassList.classes.filter((queriedClassName) => this.classList.classes.some((name) => name === queriedClassName), ).length == queryClassList.classes.length ) elements.push(this); for (const child of this.childNodes) { if (child instanceof RRElement) elements = elements.concat(child.getElementsByClassName(className)); } return elements; } getElementsByTagName(tagName: string): RRElement[] { let elements: RRElement[] = []; const normalizedTagName = tagName.toUpperCase(); if (this instanceof RRElement && this.tagName === normalizedTagName) elements.push(this); for (const child of this.childNodes) { if (child instanceof RRElement) elements = elements.concat(child.getElementsByTagName(tagName)); } return elements; } } export class RRImageElement extends RRElement { src = ''; width = 0; height = 0; onload: ((this: GlobalEventHandlers, ev: Event) => unknown) | null = null; } export class RRMediaElement extends BaseRRMediaElement {} export class RRCanvasElement extends RRElement { /** * This is just a dummy implementation to prevent rrweb replayer from drawing mouse tail. If further analysis of canvas is needed, we may implement it with node-canvas. */ getContext(): CanvasRenderingContext2D | null { return null; } } export class RRStyleElement extends RRElement { private _sheet: CSSStyleSheet | null = null; get sheet() { if (!this._sheet) { let result = ''; for (const child of this.childNodes) if (child.RRNodeType === RRNodeType.Text) result += (child as RRText).textContent; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment this._sheet = cssom.parse(result); } return this._sheet; } } export class RRIFrameElement extends RRElement { width = ''; height = ''; src = ''; contentDocument: RRDocument = new RRDocument(); contentWindow: RRWindow = new RRWindow(); constructor(tagName: string) { super(tagName); const htmlElement = this.contentDocument.createElement('HTML'); this.contentDocument.appendChild(htmlElement); htmlElement.appendChild(this.contentDocument.createElement('HEAD')); htmlElement.appendChild(this.contentDocument.createElement('BODY')); } } export class RRText extends BaseRRText { readonly nodeName = '#text' as const; } export class RRComment extends BaseRRComment { readonly nodeName = '#comment' as const; } export class RRCDATASection extends BaseRRCDATASection { readonly nodeName = '#cdata-section' as const; } interface RRElementTagNameMap { audio: RRMediaElement; canvas: RRCanvasElement; iframe: RRIFrameElement; img: RRImageElement; style: RRStyleElement; video: RRMediaElement; } type RRElementType = K extends keyof RRElementTagNameMap ? RRElementTagNameMap[K] : RRElement; ================================================ FILE: packages/rrdom-nodejs/src/index.ts ================================================ import { polyfillPerformance, polyfillRAF, polyfillEvent, polyfillNode, polyfillDocument, } from './polyfill'; polyfillPerformance(); polyfillRAF(); polyfillEvent(); polyfillNode(); polyfillDocument(); export * from './document-nodejs'; ================================================ FILE: packages/rrdom-nodejs/src/polyfill.ts ================================================ import { BaseRRNode } from 'rrdom'; import { RRDocument } from './document-nodejs'; /** * Polyfill the performance for nodejs. * Note: The performance api is available through the global object from nodejs v16.0.0. * https://github.com/nodejs/node/pull/37970 */ export function polyfillPerformance() { if (typeof window !== 'undefined' || 'performance' in global) return; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-var-requires const performance = require('perf_hooks').performance; ((global as Window & typeof globalThis).performance as unknown) = performance; } /** * Polyfill requestAnimationFrame and cancelAnimationFrame for nodejs. */ export function polyfillRAF() { if (typeof window !== 'undefined' || 'requestAnimationFrame' in global) return; const FPS = 60, INTERVAL = 1_000 / FPS; let timeoutHandle: NodeJS.Timeout | null = null, rafCount = 0, requests = Object.create(null) as Record void>; function onFrameTimer() { const currentRequests = requests; requests = Object.create(null) as Record void>; timeoutHandle = null; Object.keys(currentRequests).forEach(function (id) { const request = currentRequests[id]; if (request) request(Date.now()); }); } function requestAnimationFrame(callback: (timestamp: number) => void) { const cbHandle = ++rafCount; requests[cbHandle] = callback; if (timeoutHandle === null) timeoutHandle = setTimeout(onFrameTimer, INTERVAL); return cbHandle; } function cancelAnimationFrame(handleId: number) { delete requests[handleId]; if (Object.keys(requests).length === 0 && timeoutHandle !== null) { clearTimeout(timeoutHandle); timeoutHandle = null; } } (global as Window & typeof globalThis).requestAnimationFrame = requestAnimationFrame; (global as Window & typeof globalThis).cancelAnimationFrame = cancelAnimationFrame; } /** * Try to polyfill Event type. * The implementation of Event so far is empty because rrweb doesn't strongly depend on it in nodejs mode. * Note: The Event class is available through the global object from nodejs v15.0.0. */ export function polyfillEvent() { if (typeof Event !== 'undefined') return; (global.Event as unknown) = function () { // }; } /** * Polyfill Node type with BaseRRNode for nodejs. */ export function polyfillNode() { if (typeof Node !== 'undefined') return; (global.Node as unknown) = BaseRRNode; } /** * Polyfill document object with RRDocument for nodejs. */ export function polyfillDocument() { if (typeof document !== 'undefined') return; const rrdom = new RRDocument(); (() => { rrdom.appendChild(rrdom.createElement('html')); rrdom.documentElement?.appendChild(rrdom.createElement('head')); rrdom.documentElement?.appendChild(rrdom.createElement('body')); })(); global.document = rrdom as unknown as Document; } ================================================ FILE: packages/rrdom-nodejs/test/document-nodejs.test.ts ================================================ /** * @vitest-environment jsdom */ import { describe, it, expect, beforeAll } from 'vitest'; import * as fs from 'fs'; import * as path from 'path'; import { NodeType as RRNodeType } from '@rrweb/types'; import { RRCanvasElement, RRCDATASection, RRComment, RRDocument, RRElement, RRIFrameElement, RRImageElement, RRMediaElement, RRStyleElement, RRText, } from '../src/document-nodejs'; import { buildFromDom } from 'rrdom'; describe('RRDocument for nodejs environment', () => { describe('RRDocument API', () => { let rrdom: RRDocument; beforeAll(() => { // initialize rrdom document.write(getHtml('main.html')); rrdom = new RRDocument(); buildFromDom(document, undefined, rrdom); }); it('can create different type of RRNodes', () => { const document = rrdom.createDocument('', ''); expect(document).toBeInstanceOf(RRDocument); const audio = rrdom.createElement('audio'); expect(audio).toBeInstanceOf(RRMediaElement); const video = rrdom.createElement('video'); expect(video).toBeInstanceOf(RRMediaElement); const iframe = rrdom.createElement('iframe'); expect(iframe).toBeInstanceOf(RRIFrameElement); const image = rrdom.createElement('img'); expect(image).toBeInstanceOf(RRImageElement); const canvas = rrdom.createElement('canvas'); expect(canvas).toBeInstanceOf(RRCanvasElement); const style = rrdom.createElement('style'); expect(style).toBeInstanceOf(RRStyleElement); const elementNS = rrdom.createElementNS( 'http://www.w3.org/2000/svg', 'div', ); expect(elementNS).toBeInstanceOf(RRElement); expect(elementNS.tagName).toEqual('DIV'); const text = rrdom.createTextNode('text'); expect(text).toBeInstanceOf(RRText); expect(text.textContent).toEqual('text'); const comment = rrdom.createComment('comment'); expect(comment).toBeInstanceOf(RRComment); expect(comment.textContent).toEqual('comment'); const CDATA = rrdom.createCDATASection('data'); expect(CDATA).toBeInstanceOf(RRCDATASection); expect(CDATA.data).toEqual('data'); }); it('can get head element', () => { expect(rrdom.head).toBeDefined(); expect(rrdom.head!.tagName).toBe('HEAD'); expect(rrdom.head!.parentElement).toBe(rrdom.documentElement); }); it('can get body element', () => { expect(rrdom.body).toBeDefined(); expect(rrdom.body!.tagName).toBe('BODY'); expect(rrdom.body!.parentElement).toBe(rrdom.documentElement); }); it('can get implementation', () => { expect(rrdom.implementation).toBeDefined(); expect(rrdom.implementation).toBe(rrdom); }); it('can insert elements', () => { expect(() => rrdom.insertBefore(rrdom.createDocumentType('', '', ''), null), ).toThrowErrorMatchingInlineSnapshot( `[Error: RRDomException: Failed to execute 'insertBefore' on 'RRNode': Only one RRDoctype on RRDocument allowed.]`, ); expect(() => rrdom.insertBefore(rrdom.createElement('div'), null), ).toThrowErrorMatchingInlineSnapshot( `[Error: RRDomException: Failed to execute 'insertBefore' on 'RRNode': Only one RRElement on RRDocument allowed.]`, ); const node = new RRDocument(); const doctype = rrdom.createDocumentType('', '', ''); const documentElement = node.createElement('html'); node.insertBefore(documentElement, null); node.insertBefore(doctype, documentElement); expect(node.childNodes.length).toEqual(2); expect(node.childNodes[0]).toBe(doctype); expect(node.childNodes[1]).toBe(documentElement); expect(node.documentElement).toBe(documentElement); }); it('get firstElementChild', () => { expect(rrdom.firstElementChild).toBeDefined(); expect(rrdom.firstElementChild!.tagName).toEqual('HTML'); const div1 = rrdom.getElementById('block1'); expect(div1).toBeDefined(); expect(div1!.firstElementChild).toBeDefined(); expect(div1!.firstElementChild!.id).toEqual('block2'); const div2 = div1!.firstElementChild; expect(div2!.firstElementChild!.id).toEqual('block3'); }); it('getElementsByTagName', () => { for (let tagname of [ 'HTML', 'BODY', 'HEAD', 'STYLE', 'META', 'TITLE', 'SCRIPT', 'LINK', 'DIV', 'H1', 'P', 'BUTTON', 'IMG', 'CANVAS', 'FORM', 'INPUT', ]) { const expectedResult = document.getElementsByTagName(tagname).length; expect(rrdom.getElementsByTagName(tagname).length).toEqual( expectedResult, ); expect( rrdom.getElementsByTagName(tagname.toLowerCase()).length, ).toEqual(expectedResult); for (let node of rrdom.getElementsByTagName(tagname)) { expect(node.tagName).toEqual(tagname); } } const node = new RRDocument(); expect(node.getElementsByTagName('h2').length).toEqual(0); }); it('getElementsByClassName', () => { for (let className of [ 'blocks', 'blocks1', ':hover', 'blocks1 blocks', 'blocks blocks1', ':hover blocks1', ':hover blocks1 blocks', ':hover blocks1 block', ]) { const msg = `queried class name: '${className}'`; expect({ message: msg, result: rrdom.getElementsByClassName(className).length, }).toEqual({ message: msg, result: document.getElementsByClassName(className).length, }); } const node = new RRDocument(); expect(node.getElementsByClassName('block').length).toEqual(0); }); it('getElementById', () => { for (let elementId of ['block1', 'block2', 'block3']) { expect(rrdom.getElementById(elementId)).not.toBeNull(); expect(rrdom.getElementById(elementId)!.id).toEqual(elementId); } for (let elementId of ['block', 'blocks', 'blocks1']) expect(rrdom.getElementById(elementId)).toBeNull(); const node = new RRDocument(); expect(node.getElementById('id')).toBeNull(); }); it('querySelectorAll querying tag name', () => { expect(rrdom.querySelectorAll('H1')).toHaveLength(2); expect(rrdom.querySelectorAll('H1')[0]).toBeInstanceOf(RRElement); expect((rrdom.querySelectorAll('H1')[0] as RRElement).tagName).toEqual( 'H1', ); expect(rrdom.querySelectorAll('H1')[1]).toBeInstanceOf(RRElement); expect((rrdom.querySelectorAll('H1')[1] as RRElement).tagName).toEqual( 'H1', ); }); it('querySelectorAll querying class name', () => { for (let className of [ '.blocks', '.blocks1', '.\\:hover', '.blocks1.blocks', '.blocks.blocks1', '.\\:hover.blocks1', '.\\:hover.blocks1.blocks', '.\\:hover.blocks1.block', ]) { const msg = `queried class name: '${className}'`; expect({ message: msg, result: rrdom.querySelectorAll(className).length, }).toEqual({ message: msg, result: document.querySelectorAll(className).length, }); } for (let element of rrdom.querySelectorAll('.\\:hover')) { expect(element).toBeInstanceOf(RRElement); expect((element as RRElement).classList.classes).toContain(':hover'); } }); it('querySelectorAll querying id', () => { for (let query of ['#block1', '#block2', '#block3']) { expect(rrdom.querySelectorAll(query).length).toEqual(1); const targetElement = rrdom.querySelectorAll(query)[0] as RRElement; expect(targetElement.id).toEqual(query.substring(1, query.length)); } for (let query of ['#block', '#blocks', '#block1#block2']) expect(rrdom.querySelectorAll(query).length).toEqual(0); }); it('querySelectorAll', () => { expect(rrdom.querySelectorAll('link[rel="stylesheet"]').length).toEqual( 1, ); const targetLink = rrdom.querySelectorAll( 'link[rel="stylesheet"]', )[0] as RRElement; expect(targetLink.tagName).toEqual('LINK'); expect(targetLink.getAttribute('rel')).toEqual('stylesheet'); expect(rrdom.querySelectorAll('.blocks#block1').length).toEqual(1); expect(rrdom.querySelectorAll('.blocks#block3').length).toEqual(0); }); }); describe('RRElement API', () => { let rrdom: RRDocument; beforeAll(() => { // initialize rrdom document.write(getHtml('main.html')); rrdom = new RRDocument(); buildFromDom(document, undefined, rrdom); }); it('can get attribute', () => { expect( rrdom.getElementsByTagName('DIV')[0].getAttribute('class'), ).toEqual('blocks blocks1'); expect( rrdom.getElementsByTagName('dIv')[0].getAttribute('cLaSs'), ).toEqual('blocks blocks1'); expect(rrdom.getElementsByTagName('DIV')[0].getAttribute('id')).toEqual( 'block1', ); expect(rrdom.getElementsByTagName('div')[0].getAttribute('iD')).toEqual( 'block1', ); expect( rrdom.getElementsByTagName('p')[0].getAttribute('class'), ).toBeNull(); }); it('can set attribute', () => { const node = rrdom.createElement('div'); expect(node.getAttribute('class')).toEqual(null); node.setAttribute('class', 'className'); expect(node.getAttribute('cLass')).toEqual('className'); expect(node.getAttribute('iD')).toEqual(null); node.setAttribute('iD', 'id'); expect(node.getAttribute('id')).toEqual('id'); }); it('can remove attribute', () => { const node = rrdom.createElement('div'); node.setAttribute('Class', 'className'); expect(node.getAttribute('class')).toEqual('className'); node.removeAttribute('clAss'); expect(node.getAttribute('class')).toEqual(null); node.removeAttribute('Id'); expect(node.getAttribute('id')).toEqual(null); }); it('get nextElementSibling', () => { expect(rrdom.documentElement!.firstElementChild).not.toBeNull(); expect(rrdom.documentElement!.firstElementChild!.tagName).toEqual('HEAD'); expect( rrdom.documentElement!.firstElementChild!.nextElementSibling, ).not.toBeNull(); expect( rrdom.documentElement!.firstElementChild!.nextElementSibling!.tagName, ).toEqual('BODY'); expect( rrdom.documentElement!.firstElementChild!.nextElementSibling! .nextElementSibling, ).toBeNull(); expect(rrdom.getElementsByTagName('h1').length).toEqual(2); const element1 = rrdom.getElementsByTagName('h1')[0]; const element2 = rrdom.getElementsByTagName('h1')[1]; expect(element1.tagName).toEqual('H1'); expect(element2.tagName).toEqual('H1'); expect(element1.nextElementSibling).toEqual(element2); expect(element2.nextElementSibling).not.toBeNull(); expect(element2.nextElementSibling!.id).toEqual('block1'); expect(element2.nextElementSibling!.nextElementSibling).toBeNull(); const node = rrdom.createElement('div'); expect(node.nextElementSibling).toBeNull(); }); it('can get CSS style declaration', () => { const node = rrdom.createElement('div'); const style = node.style; expect(style).toBeDefined(); expect(style.setProperty).toBeDefined(); expect(style.removeProperty).toBeDefined(); node.attributes.style = 'color: blue; background-color: red; width: 78%; height: 50vh !important;'; expect(node.style.color).toBe('blue'); expect(node.style.backgroundColor).toBe('red'); expect(node.style.width).toBe('78%'); expect(node.style.height).toBe('50vh'); }); it('can set CSS property', () => { const node = rrdom.createElement('div'); const style = node.style; style.setProperty('color', 'red'); expect(node.attributes.style).toEqual('color: red;'); // camelCase style is unacceptable style.setProperty('backgroundColor', 'blue'); expect(node.attributes.style).toEqual('color: red;'); style.setProperty('height', '50vh', 'important'); expect(node.attributes.style).toEqual( 'color: red; height: 50vh !important;', ); // kebab-case style.setProperty('background-color', 'red'); expect(node.attributes.style).toEqual( 'color: red; height: 50vh !important; background-color: red;', ); // remove the property style.setProperty('background-color', null); expect(node.attributes.style).toEqual( 'color: red; height: 50vh !important;', ); }); it('can remove CSS property', () => { const node = rrdom.createElement('div'); node.attributes.style = 'color: blue; background-color: red; width: 78%; height: 50vh;'; const style = node.style; expect(style.removeProperty('color')).toEqual('blue'); expect(node.attributes.style).toEqual( 'background-color: red; width: 78%; height: 50vh;', ); expect(style.removeProperty('height')).toEqual('50vh'); expect(node.attributes.style).toEqual( 'background-color: red; width: 78%;', ); // kebab-case expect(style.removeProperty('background-color')).toEqual('red'); expect(node.attributes.style).toEqual('width: 78%;'); style.setProperty('background-color', 'red'); expect(node.attributes.style).toEqual( 'width: 78%; background-color: red;', ); expect(style.removeProperty('backgroundColor')).toEqual(''); expect(node.attributes.style).toEqual( 'width: 78%; background-color: red;', ); // remove a non-exist property expect(style.removeProperty('margin')).toEqual(''); }); it('can parse more inline styles correctly', () => { const node = rrdom.createElement('div'); // general node.attributes.style = 'display: inline-block; margin: 0 auto; border: 5px solid #BADA55; font-size: .75em; position:absolute;width: 33.3%; z-index:1337; font-family: "Goudy Bookletter 1911", Gill Sans Extrabold, sans-serif;'; const style = node.style; expect(style.display).toEqual('inline-block'); expect(style.margin).toEqual('0px auto'); expect(style.border).toEqual('5px solid #bada55'); expect(style.fontSize).toEqual('.75em'); expect(style.position).toEqual('absolute'); expect(style.width).toEqual('33.3%'); expect(style.zIndex).toEqual('1337'); expect(style.fontFamily).toEqual( '"Goudy Bookletter 1911", Gill Sans Extrabold, sans-serif', ); // multiple of same property node.attributes.style = 'color:rgba(0,0,0,1);color:white'; expect(style.color).toEqual('white'); // url node.attributes.style = 'background-image: url("http://example.com/img.png")'; expect(node.style.backgroundImage).toEqual( 'url(http://example.com/img.png)', ); // comment node.attributes.style = 'top: 0; /* comment1 */ bottom: /* comment2 */42rem;'; expect(node.style.top).toEqual('0px'); expect(node.style.bottom).toEqual('42rem'); // empty comment node.attributes.style = 'top: /**/0;'; expect(node.style.top).toEqual('0px'); // incomplete node.attributes.style = 'overflow:'; expect(node.style.overflow).toEqual(''); }); it('querySelectorAll', () => { const element = rrdom.getElementById('block2')!; expect(element).toBeDefined(); expect(element.id).toEqual('block2'); const result = element.querySelectorAll('div'); expect(result.length).toBe(1); expect((result[0]! as RRElement).tagName).toEqual('DIV'); expect(element.querySelectorAll('.blocks').length).toEqual(0); const element2 = rrdom.getElementById('block1')!; expect(element2).toBeDefined(); expect(element2.id).toEqual('block1'); expect(element2.querySelectorAll('div').length).toEqual(2); expect(element2.querySelectorAll('.blocks').length).toEqual(1); }); it('can attach shadow dom', () => { const node = rrdom.createElement('div'); expect(node.shadowRoot).toBeNull(); node.attachShadow({ mode: 'open' }); expect(node.shadowRoot).not.toBeNull(); expect(node.shadowRoot!.RRNodeType).toBe(RRNodeType.Element); expect(node.shadowRoot!.tagName).toBe('SHADOWROOT'); expect(node.parentNode).toBeNull(); }); it('can insert new child before an existing child', () => { const node = rrdom.createElement('div'); const child1 = rrdom.createElement('h1'); const child2 = rrdom.createElement('h2'); expect(() => node.insertBefore(node, child1), ).toThrowErrorMatchingInlineSnapshot( `[Error: Failed to execute 'insertBefore' on 'RRNode': The RRNode before which the new node is to be inserted is not a child of this RRNode.]`, ); expect(node.insertBefore(child1, null)).toBe(child1); expect(node.childNodes[0]).toBe(child1); expect(child1.parentNode).toBe(node); expect(child1.parentElement).toBe(node); expect(node.insertBefore(child2, child1)).toBe(child2); expect(node.childNodes.length).toBe(2); expect(node.childNodes[0]).toBe(child2); expect(node.childNodes[1]).toBe(child1); expect(child2.parentNode).toBe(node); expect(child2.parentElement).toBe(node); }); it('style element', () => { expect(rrdom.getElementsByTagName('style').length).not.toEqual(0); expect(rrdom.getElementsByTagName('style')[0].tagName).toEqual('STYLE'); const styleElement = rrdom.getElementsByTagName( 'style', )[0] as RRStyleElement; expect(styleElement.sheet).toBeDefined(); expect(styleElement.sheet!.cssRules).toBeDefined(); expect(styleElement.sheet!.cssRules.length).toEqual(5); const rules = styleElement.sheet!.cssRules; expect(rules[0].cssText).toEqual(`h1 {color: 'black';}`); expect(rules[1].cssText).toEqual(`.blocks {padding: 0;}`); expect(rules[2].cssText).toEqual(`.blocks1 {margin: 0;}`); expect(rules[3].cssText).toEqual( `#block1 {width: 100px; height: 200px;}`, ); expect(rules[4].cssText).toEqual(`@import url(main.css);`); expect((rules[4] as CSSImportRule).href).toEqual('main.css'); expect(styleElement.sheet!.insertRule).toBeDefined(); const newRule = "p {color: 'black';}"; styleElement.sheet!.insertRule(newRule, 5); expect(rules[5].cssText).toEqual(newRule); expect(styleElement.sheet!.deleteRule).toBeDefined(); styleElement.sheet!.deleteRule(5); expect(rules[5]).toBeUndefined(); expect(rules[4].cssText).toEqual(`@import url(main.css);`); }); it('can create an RRIframeElement', () => { const iframe = rrdom.createElement('iframe'); expect(iframe.tagName).toEqual('IFRAME'); expect(iframe.width).toEqual(''); expect(iframe.height).toEqual(''); expect(iframe.contentDocument).toBeDefined(); expect(iframe.contentDocument!.childNodes.length).toBe(1); expect(iframe.contentDocument!.documentElement).toBeDefined(); expect(iframe.contentDocument!.head).toBeDefined(); expect(iframe.contentDocument!.body).toBeDefined(); expect(iframe.contentWindow).toBeDefined(); expect(iframe.contentWindow!.scrollTop).toEqual(0); expect(iframe.contentWindow!.scrollLeft).toEqual(0); expect(iframe.contentWindow!.scrollTo).toBeDefined(); // empty parameter and did nothing iframe.contentWindow!.scrollTo(); expect(iframe.contentWindow!.scrollTop).toEqual(0); expect(iframe.contentWindow!.scrollLeft).toEqual(0); iframe.contentWindow!.scrollTo({ top: 10, left: 20 }); expect(iframe.contentWindow!.scrollTop).toEqual(10); expect(iframe.contentWindow!.scrollLeft).toEqual(20); }); it('should have a RRCanvasElement', () => { const canvas = rrdom.createElement('canvas'); expect(canvas.getContext()).toBeNull(); }); }); }); function getHtml(fileName: string) { const filePath = path.resolve(__dirname, `../../rrdom/test/html/${fileName}`); return fs.readFileSync(filePath, 'utf8'); } ================================================ FILE: packages/rrdom-nodejs/test/polyfill.test.ts ================================================ import { describe, it, expect, vi } from 'vitest'; import { compare } from 'compare-versions'; import { RRDocument } from '../src/document-nodejs'; import { polyfillPerformance, polyfillRAF, polyfillEvent, polyfillNode, polyfillDocument, } from '../src/polyfill'; import { performance as nativePerformance } from 'perf_hooks'; import { BaseRRNode } from 'rrdom'; describe('polyfill for nodejs', () => { it('should polyfill performance api', () => { if (compare(process.version, 'v16.0.0', '<')) expect(global.performance).toBeUndefined(); polyfillPerformance(); expect(global.performance).toBeDefined(); expect(performance).toBeDefined(); expect(performance.now).toBeDefined(); expect(performance.now()).toBeCloseTo(nativePerformance.now(), 1e-10); }); it('should not polyfill performance if it already exists', () => { if (compare(process.version, 'v16.0.0', '>=')) { const originalPerformance = global.performance; polyfillPerformance(); expect(global.performance).toBe(originalPerformance); } const fakePerformance = vi.fn() as unknown as Performance; global.performance = fakePerformance; polyfillPerformance(); expect(global.performance).toEqual(fakePerformance); }); it('should polyfill requestAnimationFrame', () => { expect(global.requestAnimationFrame).toBeUndefined(); expect(global.cancelAnimationFrame).toBeUndefined(); polyfillRAF(); expect(global.requestAnimationFrame).toBeDefined(); expect(global.cancelAnimationFrame).toBeDefined(); expect(requestAnimationFrame).toBeDefined(); expect(cancelAnimationFrame).toBeDefined(); vi.useFakeTimers(); const AnimationTime = 1_000; // target animation time(unit: ms) const startTime = Date.now(); let frameCount = 0; const rafCallback1 = () => { const currentTime = Date.now(); frameCount++; if (currentTime - startTime < AnimationTime) { requestAnimationFrame(rafCallback1); } else { expect(frameCount).toBeGreaterThanOrEqual(55); expect(frameCount).toBeLessThanOrEqual(65); } }; requestAnimationFrame(rafCallback1); // Fast-forward until all timers have been executed vi.runAllTimers(); let rafHandle; const rafCallback2 = () => { rafHandle = requestAnimationFrame(rafCallback2); }; rafHandle = requestAnimationFrame(rafCallback2); // If this function doesn't work, recursive function will never end. cancelAnimationFrame(rafHandle); vi.runAllTimers(); vi.useRealTimers(); }); it('should not polyfill requestAnimationFrame if it already exists', () => { const fakeRequestAnimationFrame = vi.fn() as unknown as typeof global.requestAnimationFrame; global.requestAnimationFrame = fakeRequestAnimationFrame; const fakeCancelAnimationFrame = vi.fn() as unknown as typeof global.cancelAnimationFrame; global.cancelAnimationFrame = fakeCancelAnimationFrame; polyfillRAF(); expect(global.requestAnimationFrame).toBe(fakeRequestAnimationFrame); expect(global.cancelAnimationFrame).toBe(fakeCancelAnimationFrame); }); it('should polyfill Event type', () => { // if the second version is greater if (compare(process.version, 'v15.0.0', '<')) expect(global.Event).toBeUndefined(); polyfillEvent(); expect(global.Event).toBeDefined(); expect(Event).toBeDefined(); }); it('should not polyfill Event type if it already exists', () => { const fakeEvent = vi.fn() as unknown as typeof global.Event; global.Event = fakeEvent; polyfillEvent(); expect(global.Event).toBe(fakeEvent); }); it('should polyfill Node type', () => { expect(global.Node).toBeUndefined(); polyfillNode(); expect(global.Node).toBeDefined(); expect(Node).toBeDefined(); expect(Node).toEqual(BaseRRNode); }); it('should not polyfill Node type if it already exists', () => { const fakeNode = vi.fn() as unknown as typeof global.Node; global.Node = fakeNode; polyfillNode(); expect(global.Node).toBe(fakeNode); }); it('should polyfill document object', () => { expect(global.document).toBeUndefined(); polyfillDocument(); expect(global.document).toBeDefined(); expect(document).toBeDefined(); expect(document).toBeInstanceOf(RRDocument); }); it('should not polyfill document object if it already exists', () => { const fakeDocument = vi.fn() as unknown as typeof global.document; global.document = fakeDocument; polyfillDocument(); expect(global.document).toBe(fakeDocument); }); }); ================================================ FILE: packages/rrdom-nodejs/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "include": ["src"], "compilerOptions": { "rootDir": "src", "tsBuildInfoFile": "./tsconfig.tsbuildinfo" }, "references": [ { "path": "../rrdom" }, { "path": "../types" } ] } ================================================ FILE: packages/rrdom-nodejs/vite.config.js ================================================ import path from 'path'; import config from '../../vite.config.default'; export default config(path.resolve(__dirname, 'src/index.ts'), 'rrdomNodejs'); ================================================ FILE: packages/rrdom-nodejs/vitest.config.ts ================================================ /// import { defineProject, mergeConfig } from 'vitest/config'; import configShared from '../../vitest.config'; export default mergeConfig(configShared, defineProject({})); ================================================ FILE: packages/rrvideo/CHANGELOG.md ================================================ # rrvideo ## 2.0.0-alpha.20 ### Patch Changes - Updated dependencies []: - rrweb-player@2.0.0-alpha.20 ## 2.0.0-alpha.19 ### Patch Changes - Updated dependencies []: - rrweb-player@2.0.0-alpha.19 ## 2.0.0-alpha.18 ### Patch Changes - Updated dependencies []: - rrweb-player@2.0.0-alpha.18 ## 2.0.0-alpha.17 ### Patch Changes - Updated dependencies []: - rrweb-player@2.0.0-alpha.17 ## 2.0.0-alpha.16 ### Patch Changes - Updated dependencies []: - rrweb-player@2.0.0-alpha.16 ## 2.0.0-alpha.15 ### Patch Changes - Updated dependencies [[`2606a2a`](https://github.com/rrweb-io/rrweb/commit/2606a2a28f2a6d897b8ae4ea3ec40ef0eeacbfaf)]: - rrweb-player@2.0.0-alpha.15 ## 2.0.0-alpha.14 ### Patch Changes - Updated dependencies []: - rrweb-player@2.0.0-alpha.14 ## 2.0.0-alpha.13 ### Patch Changes - Updated dependencies []: - rrweb-player@2.0.0-alpha.13 ## 2.0.0-alpha.12 ### Patch Changes - Updated dependencies []: - rrweb-player@2.0.0-alpha.12 ## 2.0.0-alpha.11 ### Patch Changes - Updated dependencies [[`efdc167`](https://github.com/rrweb-io/rrweb/commit/efdc167ca6c039d04af83612e3d92498bb9b41a7)]: - rrweb-player@2.0.0-alpha.11 ## 2.0.0-alpha.10 ### Patch Changes - Updated dependencies []: - rrweb-player@2.0.0-alpha.10 ## 2.0.0-alpha.9 ### Patch Changes - [#1197](https://github.com/rrweb-io/rrweb/pull/1197) [`23d0138`](https://github.com/rrweb-io/rrweb/commit/23d01387f439db68d2874879242b6ade3e103f75) Thanks [@YunFeng0817](https://github.com/YunFeng0817)! - Refactor: Improve the video quality and add a progress bar for the CLI tool - Updated dependencies [[`a01a12e`](https://github.com/rrweb-io/rrweb/commit/a01a12ef6769f26aa922ccd6ac76499f0837f0c2)]: - rrweb-player@2.0.0-alpha.9 ## 2.0.0-alpha.8 ### Patch Changes - Updated dependencies [[`b5e30cf`](https://github.com/rrweb-io/rrweb/commit/b5e30cf6cc7f5335d674ef1917a92bdf2895fe9e)]: - rrweb-player@2.0.0-alpha.8 ## 2.0.0-alpha.7 ### Patch Changes - [#1181](https://github.com/rrweb-io/rrweb/pull/1181) [`f1f5865`](https://github.com/rrweb-io/rrweb/commit/f1f5865dcf19db5637bbb12b220eb2aa0c0219ad) Thanks [@YunFeng0817](https://github.com/YunFeng0817)! - Refactor: Move rrvideo to rrweb's monorepo - Updated dependencies []: - rrweb-player@2.0.0-alpha.7 ================================================ FILE: packages/rrvideo/README.md ================================================ # rrvideo [中文文档](./README.zh_CN.md) rrvideo is a tool for transforming the session recorded by [rrweb](https://github.com/rrweb-io/rrweb) into a video. ![Demo Video](./demo/demo.gif) ## Install rrvideo 1. Install [Node.JS](https://nodejs.org/en/download/)。 2. Run `npm i -g rrvideo` to install the rrvideo CLI. ## Use rrvideo ### Transform a rrweb session(in JSON format) into a video. ```shell rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE ``` Running this command will output a `rrvideo-output.webm` file in the current working directory. ### Config the output path ```shell rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE --output OUTPUT_PATH ``` ### Config the replay You can prepare a rrvideo config file and pass it to CLI. ```shell rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_JSON_FILE --config PATH_TO_YOUR_RRVIDEO_CONFIG_FILE ``` You can find an example of the rrvideo config file [here](./rrvideo.config.example.json). ## Sponsors [Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site. ### Gold Sponsors 🥇
sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Silver Sponsors 🥈
sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Bronze Sponsors 🥉
sponsor sponsor sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Backers ## Core Team Members

Yuyz0112


Yun Feng


eoghanmurray


Juice10
open for rrweb consulting
## Who's using rrweb?
Smart screen recording for SaaS
The first ever UX automation tool Remote Access & Co-Browsing The open source, fullstack Monitoring Platform. Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions.
Intercept, Modify, Record & Replay HTTP Requests. In-app bug reporting & customer feedback platform. Self-hosted website analytics with heatmaps and session recordings. Interactive product demos for small marketing teams
================================================ FILE: packages/rrvideo/README.zh_CN.md ================================================ # rrvideo rrvideo 是用于将 [rrweb](https://github.com/rrweb-io/rrweb) 录制的数据转为视频格式的工具。 ![Demo Video](./demo/demo.gif) ## 安装 rrvideo 1. 安装 [Node.JS](https://nodejs.org/en/download/)。 2. 执行 `npm i -g rrvideo` 以安装 rrvideo CLI。 ## 使用 rrvideo ### 将一份 rrweb 录制的数据(JSON 格式)转换为视频。 ```shell rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE ``` 运行以上命令会在执行文件夹中生成一个 `rrvideo-output.webm` 文件。 ### 指定输出路径 ```shell rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE --output OUTPUT_PATH ``` ### 对回放进行配置 通过编写一个 rrvideo 配置文件再传入 rrvideo CLI 的方式可以对回放进行一定的配置。 ```shell rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_JSON_FILE --config PATH_TO_YOUR_RRVIDEO_CONFIG_FILE ``` rrvideo 配置文件可参考[示例](./rrvideo.config.example.json)。 ## Sponsors [Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site. ### Gold Sponsors 🥇
sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Silver Sponsors 🥈
sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Bronze Sponsors 🥉
sponsor sponsor sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Backers ## Core Team Members

Yuyz0112


Yun Feng


eoghanmurray


Juice10
open for rrweb consulting
## Who's using rrweb?
Smart screen recording for SaaS
The first ever UX automation tool Remote Access & Co-Browsing The open source, fullstack Monitoring Platform. Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions.
Intercept, Modify, Record & Replay HTTP Requests. In-app bug reporting & customer feedback platform. Self-hosted website analytics with heatmaps and session recordings. Interactive product demos for small marketing teams
================================================ FILE: packages/rrvideo/jest.config.js ================================================ // eslint-disable-next-line tsdoc/syntax /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { preset: 'ts-jest', testEnvironment: 'node', }; ================================================ FILE: packages/rrvideo/package.json ================================================ { "name": "rrvideo", "version": "2.0.0-alpha.20", "description": "transform rrweb session into video", "main": "build/index.js", "bin": { "rrvideo": "build/cli.js" }, "files": [ "build", "package.json" ], "types": "build/index.d.ts", "scripts": { "install": "playwright install", "build": "tsc", "test": "jest", "check-types": "tsc -noEmit", "prepublish": "yarn build" }, "author": "yanzhen@smartx.com", "license": "MIT", "devDependencies": { "@types/fs-extra": "11.0.1", "@types/jest": "^27.4.1", "@types/minimist": "^1.2.1", "@types/node": "^18.15.11", "jest": "^27.5.1", "ts-jest": "^27.1.3", "@rrweb/types": "^2.0.0-alpha.20" }, "dependencies": { "@open-tech-world/cli-progress-bar": "^2.0.2", "fs-extra": "^11.1.1", "minimist": "^1.2.5", "playwright": "^1.56.1", "rrweb-player": "^2.0.0-alpha.20" } } ================================================ FILE: packages/rrvideo/rrvideo.config.example.json ================================================ { "width": 1400, "height": 900, "speed": 4, "skipInactive": true, "mouseTail": { "strokeStyle": "green", "lineWidth": 2 } } ================================================ FILE: packages/rrvideo/src/cli.ts ================================================ #!/usr/bin/env node import * as fs from 'fs'; import * as path from 'path'; import minimist from 'minimist'; import { ProgressBar } from '@open-tech-world/cli-progress-bar'; import type Player from 'rrweb-player'; import { transformToVideo } from './index'; const argv = minimist(process.argv.slice(2)); if (!argv.input) { throw new Error('please pass --input to your rrweb events file'); } let config = {}; if (argv.config) { const configPathStr = argv.config as string; const configPath = path.isAbsolute(configPathStr) ? configPathStr : path.resolve(process.cwd(), configPathStr); config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as Omit< ConstructorParameters[0]['props'], 'events' >; } const pBar = new ProgressBar({ prefix: 'Transforming' }); const onProgressUpdate = (percent: number) => { if (percent < 1) pBar.run({ value: percent * 100, total: 100 }); else pBar.run({ value: 100, total: 100, prefix: 'Transformation Completed!' }); }; transformToVideo({ input: argv.input as string, output: argv.output as string, rrwebPlayer: config, onProgressUpdate, }) .then((file) => { console.log(`Successfully transformed into "${file}".`); }) .catch((error) => { console.log('Failed to transform this session.'); console.error(error); process.exit(1); }); ================================================ FILE: packages/rrvideo/src/index.ts ================================================ import * as fs from 'fs-extra'; import * as path from 'path'; import { chromium } from 'playwright'; import { EventType, eventWithTime } from '@rrweb/types'; import type Player from 'rrweb-player'; const rrwebScriptPath = path.resolve( require.resolve('rrweb-player'), '../../dist/rrweb-player.umd.cjs', ); const rrwebStylePath = path.resolve(rrwebScriptPath, '../style.css'); const rrwebRaw = fs.readFileSync(rrwebScriptPath, 'utf-8'); const rrwebStyle = fs.readFileSync(rrwebStylePath, 'utf-8'); // The max valid scale value for the scaling method which can improve the video quality. const MaxScaleValue = 2.5; type RRvideoConfig = { input: string; output?: string; headless?: boolean; // A number between 0 and 1. The higher the value, the better the quality of the video. resolutionRatio?: number; // A callback function that will be called when the progress of the replay is updated. onProgressUpdate?: (percent: number) => void; rrwebPlayer?: Omit< ConstructorParameters[0]['props'], 'events' >; }; const defaultConfig: Required = { input: '', output: 'rrvideo-output.webm', headless: true, // A good trade-off value between quality and file size. resolutionRatio: 0.8, onProgressUpdate: () => { // }, rrwebPlayer: {}, }; function getHtml(events: Array, config?: RRvideoConfig): string { return ` `; } /** * Preprocess all events to get a maximum view port size. */ function getMaxViewport(events: eventWithTime[]) { let maxWidth = 0, maxHeight = 0; events.forEach((event) => { if (event.type !== EventType.Meta) return; if (event.data.width > maxWidth) maxWidth = event.data.width; if (event.data.height > maxHeight) maxHeight = event.data.height; }); return { width: maxWidth, height: maxHeight, }; } export async function transformToVideo(options: RRvideoConfig) { const defaultVideoDir = '__rrvideo__temp__'; const config = { ...defaultConfig }; if (!options.input) throw new Error('input is required'); // If the output is not specified or undefined, use the default value. if (!options.output) delete options.output; Object.assign(config, options); if (config.resolutionRatio > 1) config.resolutionRatio = 1; // The max value is 1. const eventsPath = path.isAbsolute(config.input) ? config.input : path.resolve(process.cwd(), config.input); const outputPath = path.isAbsolute(config.output) ? config.output : path.resolve(process.cwd(), config.output); const events = JSON.parse( fs.readFileSync(eventsPath, 'utf-8'), ) as eventWithTime[]; // Make the browser viewport fit the player size. const maxViewport = getMaxViewport(events); // Use the scaling method to improve the video quality. const scaledViewport = { width: Math.round( maxViewport.width * (config.resolutionRatio ?? 1) * MaxScaleValue, ), height: Math.round( maxViewport.height * (config.resolutionRatio ?? 1) * MaxScaleValue, ), }; Object.assign(config.rrwebPlayer, scaledViewport); const browser = await chromium.launch({ headless: config.headless, }); const context = await browser.newContext({ viewport: scaledViewport, recordVideo: { dir: defaultVideoDir, size: scaledViewport, }, }); const page = await context.newPage(); await page.goto('about:blank'); // Listen to console messages from the page page.on('console', (msg) => { console.log('[PAGE CONSOLE]', msg.type(), msg.text()); }); // Listen to page errors page.on('pageerror', (error) => { console.error('[PAGE ERROR]', error.message); }); await page.exposeFunction( 'onReplayProgressUpdate', (data: { payload: number }) => { config.onProgressUpdate(data.payload); }, ); // Wait for the replay to finish await new Promise((resolve, reject) => { const timeoutBuffer = 120000; // 2 minute timeout buffer const videoStartTime = events[0]?.timestamp; const videoEndTime = events[events.length - 1]?.timestamp; const videoDuration = videoEndTime - videoStartTime; const videoPlaybackSpeed = options.rrwebPlayer?.speed || 1; const expectedPlaybackTime = videoDuration / videoPlaybackSpeed; console.log( `[DEBUG] Expected playback time: ${expectedPlaybackTime}ms (video duration: ${videoDuration}ms, playback speed: ${videoPlaybackSpeed}x)`, ); const totalTimeout = expectedPlaybackTime + timeoutBuffer; const timeout = setTimeout(() => { console.error('[DEBUG] Replay timeout - finish event never fired'); reject(new Error('Replay timeout')); }, totalTimeout); // playback + 2 minute timeout void page .exposeFunction('onReplayFinish', () => { console.log('[DEBUG] Replay finished'); clearTimeout(timeout); resolve(); }) .then(() => { console.log('[DEBUG] Setting page content'); return page.setContent(getHtml(events, config)); }) .then(() => { console.log('[DEBUG] Page content set successfully'); }) .catch((err) => { console.error('[DEBUG] Error setting page content:', err); clearTimeout(timeout); reject(err); }); }); const videoPath = (await page.video()?.path()) || ''; const cleanFiles = async (videoPath: string) => { await fs.remove(videoPath); if ((await fs.readdir(defaultVideoDir)).length === 0) { await fs.remove(defaultVideoDir); } }; await context.close(); await Promise.all([ fs .move(videoPath, outputPath, { overwrite: true }) .catch((e) => { console.error( "Can't create video file. Please check the output path.", e, ); }) .finally(() => void cleanFiles(videoPath)), browser.close(), ]); return outputPath; } ================================================ FILE: packages/rrvideo/test/cli.test.ts ================================================ import { execSync } from 'child_process'; import * as fs from 'fs-extra'; import * as path from 'path'; import exampleEvents from './events/example'; describe('should be able to run cli', () => { beforeAll(() => { fs.mkdirSync(path.resolve(__dirname, './generated')); fs.writeJsonSync( path.resolve(__dirname, './generated/example.json'), exampleEvents, { spaces: 2, }, ); }); afterAll(async () => { await fs.remove(path.resolve(__dirname, './generated')); }); const execOptions = { timeout: 60_000 } as const; const execOptionsWithOutput = { ...execOptions, stdio: 'inherit' } as const; it('should throw error without input path', () => { expect(() => { execSync('node ./build/cli.js', { ...execOptions, stdio: 'pipe' }); }).toThrowError(/.*please pass --input to your rrweb events file.*/); }); it('should generate a video without output path', () => { execSync( 'node ./build/cli.js --input ./test/generated/example.json', execOptionsWithOutput, ); const outputFile = path.resolve(__dirname, '../rrvideo-output.webm'); expect(fs.existsSync(outputFile)).toBe(true); fs.removeSync(outputFile); }); it('should generate a video with specific output path', () => { const outputFile = path.resolve(__dirname, './generated/output.webm'); execSync( `node ./build/cli.js --input ./test/generated/example.json --output ${outputFile}`, execOptionsWithOutput, ); expect(fs.existsSync(outputFile)).toBe(true); fs.removeSync(outputFile); }); }); ================================================ FILE: packages/rrvideo/test/events/example.ts ================================================ import { EventType, IncrementalSource } from '@rrweb/types'; import type { eventWithTime } from '@rrweb/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: 1000, height: 800, }, timestamp: now + 100, }, // 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: 5, type: 2, tagName: 'body', attributes: {}, childNodes: [], }, ], }, ], }, initialOffset: { top: 0, left: 0 }, }, type: EventType.FullSnapshot, timestamp: now + 100, }, // mutation that adds select elements { type: EventType.IncrementalSnapshot, data: { source: IncrementalSource.Mutation, texts: [], attributes: [], removes: [], adds: [ { parentId: 5, nextId: null, node: { type: 2, tagName: 'select', childNodes: [], attributes: {}, id: 26, }, }, { parentId: 26, nextId: null, node: { type: 2, tagName: 'option', attributes: { value: 'valueC' }, childNodes: [], id: 27, }, }, { parentId: 27, nextId: null, node: { type: 3, textContent: 'C', id: 28 }, }, { parentId: 26, nextId: 27, node: { type: 2, tagName: 'option', attributes: { value: 'valueB', selected: true }, childNodes: [], id: 29, }, }, { parentId: 26, nextId: 29, node: { type: 2, tagName: 'option', attributes: { value: 'valueA' }, childNodes: [], id: 30, }, }, { parentId: 30, nextId: null, node: { type: 3, textContent: 'A', id: 31 }, }, { parentId: 29, nextId: null, node: { type: 3, textContent: 'B', id: 32 }, }, ], }, timestamp: now + 200, }, // input event { type: EventType.IncrementalSnapshot, data: { source: IncrementalSource.Input, text: 'valueA', isChecked: false, id: 26, }, timestamp: now + 300, }, ]; export default events; ================================================ FILE: packages/rrvideo/test/tsconfig.json ================================================ { "compilerOptions": {} } ================================================ FILE: packages/rrvideo/tsconfig.json ================================================ { "compilerOptions": { "composite": true, "target": "ES6", "module": "commonjs", "declaration": true, "sourceMap": true, "outDir": "./build", "rootDir": "./src", "strictNullChecks": true, "noImplicitAny": true, "strictBindCallApply": true, "noUnusedLocals": true, "noUnusedParameters": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "exclude": ["build", "node_modules", "test"], "references": [ { "path": "../rrweb-player" }, { "path": "../types" } ] } ================================================ FILE: packages/rrweb/.gitignore ================================================ .vscode .idea node_modules package-lock.json # yarn.lock tsconfig.tsbuildinfo build dist es lib typings temp *.log .env __diff_output__ ================================================ FILE: packages/rrweb/.release-it.json ================================================ { "non-interactive": true, "hooks": { "before:init": ["npm run bundle", "npm run typings"] }, "git": { "requireCleanWorkingDir": false }, "github": { "release": true } } ================================================ FILE: packages/rrweb/CHANGELOG.md ================================================ # rrweb ## 2.0.0-alpha.20 ### Patch Changes - [#1763](https://github.com/rrweb-io/rrweb/pull/1763) [`6388fb5`](https://github.com/rrweb-io/rrweb/commit/6388fb5a468e1a860ab8bb5c6826c811dcc3100c) Thanks [@wfk007](https://github.com/wfk007)! - fix: wujie monkeypatches ownerDocument - Updated dependencies [[`6388fb5`](https://github.com/rrweb-io/rrweb/commit/6388fb5a468e1a860ab8bb5c6826c811dcc3100c)]: - @rrweb/utils@2.0.0-alpha.20 - rrweb-snapshot@2.0.0-alpha.20 - rrdom@2.0.0-alpha.20 - @rrweb/types@2.0.0-alpha.20 ## 2.0.0-alpha.19 ### Patch Changes - [#1615](https://github.com/rrweb-io/rrweb/pull/1615) [`dc20cd4`](https://github.com/rrweb-io/rrweb/commit/dc20cd45cc63058325784444af6bd32ed2cace48) Thanks [@eoghanmurray](https://github.com/eoghanmurray)! - Improve performance of splitCssText for

Verify that block class bugs are fixed

















================================================ FILE: packages/rrweb/test/html/canvas-webgl-image.html ================================================ Document ================================================ FILE: packages/rrweb/test/html/canvas-webgl-shader.html ================================================ canvas shader ================================================ FILE: packages/rrweb/test/html/canvas-webgl-square.html ================================================ canvas webgl square ================================================ FILE: packages/rrweb/test/html/canvas-webgl.html ================================================ canvas ================================================ FILE: packages/rrweb/test/html/canvas.html ================================================ canvas ================================================ FILE: packages/rrweb/test/html/dialog.html ================================================ I'm a dialog ================================================ FILE: packages/rrweb/test/html/empty.html ================================================ Empty
================================================ FILE: packages/rrweb/test/html/form.html ================================================ form fields
================================================ FILE: packages/rrweb/test/html/frame-image-blob-url.html ================================================ Frame with image ================================================ FILE: packages/rrweb/test/html/frame1.html ================================================ Frame 1 frame 1 ================================================ FILE: packages/rrweb/test/html/frame2.html ================================================ Frame 2 frame 2 ================================================ FILE: packages/rrweb/test/html/hello-world.html ================================================ Hello World! Hello world! ================================================ FILE: packages/rrweb/test/html/ignore.html ================================================ ignore fields
================================================ FILE: packages/rrweb/test/html/image-blob-url.html ================================================ Image with blob:url ================================================ FILE: packages/rrweb/test/html/link.html ================================================ Link click not link link ================================================ FILE: packages/rrweb/test/html/main.html ================================================ Main ================================================ FILE: packages/rrweb/test/html/mask-text.html ================================================ Mask text

mask1

mask2
mask3

unmask1

================================================ FILE: packages/rrweb/test/html/move-node.html ================================================

1 ================================================ FILE: packages/rrweb/test/html/mutation-observer.html ================================================

mutation observer

================================================ FILE: packages/rrweb/test/html/password.html ================================================ Document ================================================ FILE: packages/rrweb/test/html/polyfilled-shadowdom-mutation.html ================================================
================================================ FILE: packages/rrweb/test/html/react-styled-components.html ================================================ react styled components
================================================ FILE: packages/rrweb/test/html/select2.html ================================================ Select2 3.5
Select2 is a jQuery replacement for select boxes.
In the 3.5 version it use a quite complicated DOM generation strategy which is a good battle-test for rrweb's recorder.
================================================ FILE: packages/rrweb/test/html/shadow-dom.html ================================================ Shadow DOM Observer

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellat odit officiis necessitatibus laborum asperiores et adipisci dolores corporis, vero distinctio voluptas, suscipit commodi architecto, aliquam fugit. Nesciunt labore reiciendis blanditiis!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellat odit officiis necessitatibus laborum asperiores et adipisci dolores corporis, vero distinctio voluptas, suscipit commodi architecto, aliquam fugit. Nesciunt labore reiciendis blanditiis!

================================================ FILE: packages/rrweb/test/html/shuffle.html ================================================ shuffle
  • 1
  • 2
  • 3
  • 4
  • 5
================================================ FILE: packages/rrweb/test/html/style.html ================================================ style ================================================ FILE: packages/rrweb/test/html/video.html ================================================ Video

Big Buck Bunny

================================================ FILE: packages/rrweb/test/integration.test.ts ================================================ import * as fs from 'fs'; import * as path from 'path'; import type * as puppeteer from 'puppeteer'; import { vi } from 'vitest'; import { assertSnapshot, startServer, getServerURL, launchPuppeteer, waitForRAF, waitForIFrameLoad, replaceLast, generateRecordSnippet, ISuite, } from './utils'; import type { recordOptions } from '../src/types'; import { eventWithTime, NodeType, EventType } from '@rrweb/types'; import { visitSnapshot } from 'rrweb-snapshot'; describe('record integration tests', function (this: ISuite) { vi.setConfig({ testTimeout: 10_000 }); const getHtml = ( fileName: string, options: recordOptions = {}, ): string => { const filePath = path.resolve(__dirname, `./html/${fileName}`); const html = fs.readFileSync(filePath, 'utf8'); return replaceLast( html, '', ` `, ); }; 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.umd.cjs'); code = fs.readFileSync(bundlePath, 'utf8'); }); afterAll(async () => { await browser.close(); server.close(); }); it('can record clicks', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'link.html')); await page.click('span'); // also tap on the span const span = await page.waitForSelector('span'); const center = await page.evaluate((el) => { const { x, y, width, height } = el!.getBoundingClientRect(); return { x: Math.round(x + width / 2), y: Math.round(y + height / 2), }; }, span); await page.touchscreen.tap(center.x, center.y); await page.click('a'); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('can record form interactions', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'form.html')); 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.select('select', '1'); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('can record and replay textarea mutations correctly', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'empty.html')); await waitForRAF(page); // ensure mutations aren't included in fullsnapshot await page.evaluate(() => { const ta = document.createElement('textarea'); ta.innerText = 'pre value'; document.body.append(ta); const ta2 = document.createElement('textarea'); ta2.id = 'ta2'; document.body.append(ta2); }); await waitForRAF(page); await page.evaluate(() => { const t = document.querySelector('textarea') as HTMLTextAreaElement; t.innerText = 'ok'; // this mutation should be recorded const ta2t = document.createTextNode('added'); document.getElementById('ta2').append(ta2t); }); await waitForRAF(page); await page.evaluate(() => { const t = document.querySelector('textarea') as HTMLTextAreaElement; (t.childNodes[0] as Text).appendData('3'); // this mutation is also valid document.getElementById('ta2').remove(); // done with this }); await waitForRAF(page); await page.type('textarea', '1'); // types (inserts) at index 0, in front of existing text await waitForRAF(page); await page.evaluate(() => { const t = document.querySelector('textarea') as HTMLTextAreaElement; // user has typed so childNode content should now be ignored (t.childNodes[0] as Text).data = 'igno'; (t.childNodes[0] as Text).appendData('re'); // this mutation is currently emitted, and shows up in snapshot // but we will check that it doesn't have any effect on the value // there is nothing explicit in rrweb which enforces this, but this test may protect against // a future change where a mutation on a textarea incorrectly updates the .value }); await waitForRAF(page); await page.type('textarea', '2'); // cursor is at index 1 const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); // check after each mutation and text input const replayTextareaValues = await page.evaluate(` const { Replayer } = rrweb; const replayer = new Replayer(window.snapshots); const vals = []; window.snapshots.filter((e)=>e.data.attributes || e.data.source === 5).forEach((e)=>{ replayer.pause((e.timestamp - window.snapshots[0].timestamp)+1); let ts = replayer.iframe.contentDocument.querySelector('textarea'); vals.push((e.data.source === 0 ? 'Mutation' : 'User') + ':' + ts.value); let ts2 = replayer.iframe.contentDocument.getElementById('ta2'); if (ts2) { vals.push('ta2:' + ts2.value); } }); vals; `); expect(replayTextareaValues).toEqual([ 'Mutation:pre value', 'ta2:', 'Mutation:ok', 'ta2:added', 'Mutation:ok3', 'User:1ok3', 'Mutation:1ok3', // if this gets set to 'ignore', it's an error, as the 'user' has modified the textarea 'User:12ok3', ]); }); it('can record and replay style mutations', async () => { // This test shows that the `isStyle` attribute on textContent is not needed in a mutation // TODO: we could get a lot more elaborate here with mixed textContent and insertRule mutations const page: puppeteer.Page = await browser.newPage(); await page.goto(`${serverURL}/html`); await page.setContent(getHtml.call(this, 'style.html')); await waitForRAF(page); // ensure mutations aren't included in fullsnapshot await page.evaluate(() => { let styleEl = document.querySelector('style#dual-textContent'); if (styleEl) { styleEl.append( document.createTextNode('body { background-color: darkgreen; }'), ); styleEl.append( document.createTextNode( '.absolutify { background-image: url("./rel"); }', ), ); } }); await waitForRAF(page); await page.evaluate(() => { let styleEl = document.querySelector('style#dual-textContent'); if (styleEl) { styleEl.childNodes.forEach((cn) => { if (cn.textContent) { cn.textContent = cn.textContent.replace('darkgreen', 'purple'); cn.textContent = cn.textContent.replace( 'orange !important', 'yellow', ); } }); } }); await waitForRAF(page); await page.evaluate(() => { let styleEl = document.querySelector('style#dual-textContent'); if (styleEl) { styleEl.childNodes.forEach((cn) => { if (cn.textContent) { cn.textContent = cn.textContent.replace( 'black', 'black !important', ); } }); } let hoverMutationStyleEl = document.querySelector('style#hover-mutation'); if (hoverMutationStyleEl) { hoverMutationStyleEl.childNodes.forEach((cn) => { if (cn.textContent) { cn.textContent = 'a:hover { outline: cyan solid 1px; }'; } }); } let st = document.createElement('style'); st.id = 'goldilocks'; st.innerText = 'body { color: brown }'; document.body.append(st); }); await waitForRAF(page); await page.evaluate(() => { let styleEl = document.querySelector('style#goldilocks'); if (styleEl) { styleEl.childNodes.forEach((cn) => { if (cn.textContent) { cn.textContent = cn.textContent.replace('brown', 'gold'); } }); } }); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; // following ensures that the ./rel url has been absolutized (in a mutation) await assertSnapshot(snapshots); // check after each mutation and text input const replayStyleValues = await page.evaluate(` const { Replayer } = rrweb; const replayer = new Replayer(window.snapshots); const vals = []; window.snapshots.filter((e)=>e.data.attributes || e.data.source === 5).forEach((e)=>{ replayer.pause((e.timestamp - window.snapshots[0].timestamp)+1); let bodyStyle = getComputedStyle(replayer.iframe.contentDocument.querySelector('body')) vals.push({ 'background-color': bodyStyle['background-color'], 'color': bodyStyle['color'], }); }); vals.push(replayer.iframe.contentDocument.getElementById('single-textContent').innerText); vals.push(replayer.iframe.contentDocument.getElementById('empty').innerText); vals.push(replayer.iframe.contentDocument.getElementById('hover-mutation').innerText); vals; `); expect(replayStyleValues).toEqual([ { 'background-color': 'rgb(0, 100, 0)', // darkgreen color: 'rgb(255, 165, 0)', // orange (from style.html) }, { 'background-color': 'rgb(128, 0, 128)', // purple color: 'rgb(255, 255, 0)', // yellow }, { 'background-color': 'rgb(0, 0, 0)', // black !important color: 'rgb(165, 42, 42)', // brown }, { 'background-color': 'rgb(0, 0, 0)', color: 'rgb(255, 215, 0)', // gold }, 'a:hover,\na.\\:hover { outline: red solid 1px; }', // has run adaptCssForReplay 'a:hover,\na.\\:hover { outline: blue solid 1px; }', // has run adaptCssForReplay 'a:hover,\na.\\:hover { outline: cyan solid 1px; }', // has run adaptCssForReplay after text mutation ]); }); it('can record childList mutations', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'mutation-observer.html')); await page.evaluate(() => { const li = document.createElement('li'); const ul = document.querySelector('ul') as HTMLUListElement; ul.appendChild(li); document.body.removeChild(ul); const p = document.querySelector('p') as HTMLParagraphElement; p.appendChild(document.createElement('span')); }); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('can record character data muatations', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'mutation-observer.html')); await page.evaluate(() => { const li = document.createElement('li'); const ul = document.querySelector('ul') as HTMLUListElement; ul.appendChild(li); li.innerText = 'new list item'; li.innerText = 'new list item edit'; document.body.removeChild(ul); const p = document.querySelector('p') as HTMLParagraphElement; p.innerText = 'mutated'; }); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('can record attribute mutation', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'mutation-observer.html')); await page.evaluate(() => { const li = document.createElement('li'); const ul = document.querySelector('ul') as HTMLUListElement; ul.appendChild(li); li.setAttribute('foo', 'bar'); document.body.removeChild(ul); document.body.setAttribute('test', 'true'); }); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('handles null attribute values', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'mutation-observer.html', {})); await page.evaluate(() => { const li = document.createElement('li'); const ul = document.querySelector('ul') as HTMLUListElement; ul.appendChild(li); li.setAttribute('aria-label', 'label'); li.setAttribute('id', 'test-li'); }); await new Promise((resolve) => setTimeout(resolve, 100)); await page.evaluate(() => { const li = document.querySelector('#test-li') as HTMLLIElement; // This triggers the mutation observer with a `null` attribute value li.removeAttribute('aria-label'); }); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('can record node mutations', async () => { 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', { 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', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('can record style changes compactly and preserve css var() functions', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'blank.html'), { waitUntil: 'networkidle0', }); // goal here is to ensure var(--mystery) ends up in the mutations (CSSOM fails in this case) await page.evaluate( 'document.body.setAttribute("style", "background: var(--mystery)")', ); await waitForRAF(page); // and in this change we can't use the shorter styleObj format either await page.evaluate( 'document.body.setAttribute("style", "background: var(--mystery); background-color: black")', ); // reset is always shorter to be recorded as a sting rather than a styleObj await page.evaluate('document.body.setAttribute("style", "")'); await waitForRAF(page); await page.evaluate('document.body.setAttribute("style", "display:block")'); await waitForRAF(page); // following should be recorded as an update of `{ color: 'var(--mystery-color)' }` without needing to include the display await page.evaluate( 'document.body.setAttribute("style", "color:var(--mystery-color);display:block")', ); await waitForRAF(page); // whereas this case, it's shorter to record the entire string than the longhands for margin await page.evaluate( 'document.body.setAttribute("style", "color:var(--mystery-color);display:block;margin:10px")', ); await waitForRAF(page); // and in this case, it's shorter to record just the change to the longhand margin-left; await page.evaluate( 'document.body.setAttribute("style", "color:var(--mystery-color);display:block;margin:10px 10px 10px 0px;")', ); await waitForRAF(page); // see what happens when we manipulate the style object directly (expecting a compact mutation with just these two changes) await page.evaluate( 'document.body.style.marginTop = 0; document.body.style.color = null', ); await waitForRAF(page); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('can freeze mutations', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'mutation-observer.html', { recordCanvas: true }), ); await page.evaluate(() => { const li = document.createElement('li'); const ul = document.querySelector('ul') as HTMLUListElement; ul.appendChild(li); li.setAttribute('foo', 'bar'); document.body.setAttribute('test', 'true'); }); 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'); li.innerText = 'bad text'; ul.appendChild(li); document.body.removeChild(ul); }); await waitForRAF(page); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should not record input events on ignored elements', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'ignore.html', { ignoreSelector: '[data-rr-ignore]', }), ); await page.type('.rr-ignore', 'secret'); await page.type('[data-rr-ignore]', 'secret'); await page.type('.dont-ignore', 'not secret'); await assertSnapshot(page); }); it('should not record input values if maskAllInputs is enabled', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'form.html', { maskAllInputs: 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', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('can use maskInputOptions to configure which type of inputs should be masked', async () => { 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', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should mask password 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('#password', 'secr3t'); // Change type to text (simulate "show password") await page.click('#show-password'); await page.type('#password', 'XY'); await page.click('#show-password'); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should mask inputs via function call', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'form.html', { maskAllInputs: true, maskInputFn: (text: string, element: HTMLElement) => { // If the element has the attribute "data-unmask-example", we don't mask it if (element.hasAttribute('data-unmask-example')) { return text; } return '*'.repeat(text.length); }, }), ); 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', )) as eventWithTime[]; await 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', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should not record blocked elements and its child nodes', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'block.html')); await page.type('input', 'should not be record'); await page.evaluate(`document.getElementById('text').innerText = '1'`); await page.click('#text'); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await 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', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('mutations should work when blocked class is unblocked', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about: blank'); await page.setContent(getHtml.call(this, 'blocked-unblocked.html')); const elements1 = (await page.$x( '/html/body/div[1]/button', )) as puppeteer.ElementHandle[]; await elements1[0].click(); const elements2 = (await page.$x( '/html/body/div[2]/button', )) as puppeteer.ElementHandle[]; await elements2[0].click(); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record DOM node movement 1', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'move-node.html')); await page.evaluate(() => { const div = document.querySelector('div')!; const p = document.querySelector('p')!; const span = document.querySelector('span')!; document.body.removeChild(span); p.appendChild(span); p.removeChild(span); div.appendChild(span); }); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record DOM node movement 2', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'move-node.html')); await page.evaluate(() => { const div = document.createElement('div'); const span = document.querySelector('span')!; document.body.appendChild(div); div.appendChild(span); }); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record dynamic CSS changes', async () => { 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', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record canvas mutations', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'canvas.html', { recordCanvas: true, }), ); await page.waitForFunction('window.canvasMutationApplied'); await waitForRAF(page); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; for (const event of snapshots) { if (event.type === EventType.FullSnapshot) { visitSnapshot(event.data.node, (n) => { if (n.type === NodeType.Element && n.attributes.rr_dataURL) { n.attributes.rr_dataURL = `LOOKS LIKE WE COULD NOT GET STABLE BASE64 FROM SAME IMAGE.`; } }); } } await assertSnapshot(snapshots); }); it('should not record input values if dynamically added and maskAllInputs is true', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'empty.html', { maskAllInputs: true }), ); await page.evaluate(() => { const el = document.createElement('input'); el.size = 50; el.id = 'input'; el.value = 'input should be masked'; const nextElement = document.querySelector('#one')!; nextElement.parentNode!.insertBefore(el, nextElement); const ta = document.createElement('textarea'); ta.size = 50; ta.id = 'textarea'; ta.setAttribute('size', '50'); ta.value = 'textarea should be masked'; nextElement.parentNode!.insertBefore(ta, nextElement); }); await page.type('#input', 'moo'); await page.type('#textarea', 'boo'); await page.evaluate(() => { const el = document.querySelector('input'); el.value = 'input attribute mutation should also be masked'; const ta = document.querySelector('textarea'); ta.value = 'textarea attribute mutation should also be masked'; }); await page.evaluate(() => { const el = document.querySelector('input'); el.setAttribute( 'value', "input attribute mutation should also be masked (even though the new value doesn't take effect)", ); const ta = document.querySelector('textarea'); ta.setAttribute( 'value', "textarea attribute mutation should also be masked (even though the new value doesn't take effect)", ); }); await page.evaluate(() => { const ta = document.querySelector('textarea'); ta.innerText = 'textarea attribute mutation via innerText should also be masked '; }); await assertSnapshot(page); }); 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', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('can correctly serialize a shader and multiple webgl contexts', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'canvas-webgl-shader.html', { recordCanvas: true, }), ); await waitForRAF(page); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('will serialize node before record', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'mutation-observer.html')); await page.evaluate(() => { const ul = document.querySelector('ul') as HTMLUListElement; let count = 3; while (count > 0) { count--; const li = document.createElement('li'); ul.appendChild(li); } }); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('will defer missing next node mutation', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'shuffle.html')); const text = await page.evaluate(() => { const els = Array.prototype.slice.call(document.querySelectorAll('li')); const parent = document.querySelector('ul')!; parent.removeChild(els[3]); parent.removeChild(els[2]); parent.removeChild(els[1]); parent.removeChild(els[0]); parent.insertBefore(els[3], els[4]); parent.insertBefore(els[2], els[4]); parent.insertBefore(els[1], els[4]); parent.insertBefore(els[0], els[4]); return parent.innerText; }); expect(text).toEqual('4\n3\n2\n1\n5'); }); it('should nest record iframe', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto(`${serverURL}/html`); await page.setContent(getHtml.call(this, 'main.html')); const frameIdTwo = await waitForIFrameLoad(page, '#two'); const frameIdFour = await waitForIFrameLoad(frameIdTwo, '#four'); await waitForIFrameLoad(frameIdFour, '#five'); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record images with blob url', async () => { const page: puppeteer.Page = await browser.newPage(); page.on('console', (msg) => console.log(msg.text())); await page.goto(`${serverURL}/html`); page.setContent( getHtml.call(this, 'image-blob-url.html', { inlineImages: true }), ); await page.waitForResponse(`${serverURL}/html/assets/robot.png`); await page.waitForSelector('img'); // wait for image to get added await waitForRAF(page); // wait for image to be captured const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record images inside iframe with blob url', async () => { const page: puppeteer.Page = await browser.newPage(); page.on('console', (msg) => console.log(msg.text())); await page.goto(`${serverURL}/html`); await page.setContent( getHtml.call(this, 'frame-image-blob-url.html', { inlineImages: true }), ); await page.waitForResponse(`${serverURL}/html/assets/robot.png`); await page.waitForTimeout(50); // wait for image to get added await waitForRAF(page); // wait for image to be captured const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record images inside iframe with blob url after iframe was reloaded', async () => { const page: puppeteer.Page = await browser.newPage(); page.on('console', (msg) => console.log(msg.text())); await page.goto(`${serverURL}/html`); await page.setContent( getHtml.call(this, 'frame2.html', { inlineImages: true }), ); await page.waitForSelector('iframe'); // wait for iframe to get added await waitForRAF(page); // wait for iframe to load page.evaluate(() => { const iframe = document.querySelector('iframe')!; iframe.setAttribute('src', '/html/image-blob-url.html'); }); await page.waitForResponse(`${serverURL}/html/assets/robot.png`); // wait for image to get loaded await page.waitForTimeout(50); // wait for image to get added await waitForRAF(page); // wait for image to be captured const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record shadow DOM', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'shadow-dom.html')); await page.evaluate(() => { const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const el = document.querySelector('.my-element') as HTMLDivElement; const shadowRoot = el.shadowRoot as ShadowRoot; shadowRoot.appendChild(document.createElement('span')); shadowRoot.appendChild(document.createElement('p')); sleep(1) .then(() => { shadowRoot.lastChild!.appendChild(document.createElement('p')); return sleep(1); }) .then(() => { const firstP = shadowRoot.querySelector('p') as HTMLParagraphElement; shadowRoot.removeChild(firstP); return sleep(1); }) .then(() => { (shadowRoot.lastChild!.childNodes[0] as HTMLElement).innerText = 'hi'; return sleep(1); }) .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', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record shadow DOM 2', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'blank.html')); await page.evaluate(() => { return new Promise((resolve) => { const el = document.createElement('div') as HTMLDivElement; el.attachShadow({ mode: 'open' }); (el.shadowRoot as ShadowRoot).appendChild( document.createElement('input'), ); setTimeout(() => { document.body.append(el); resolve(null); }, 10); }); }); await waitForRAF(page); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record shadow DOM 3', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'blank.html')); await page.evaluate(() => { const el = document.createElement('div') as HTMLDivElement; el.attachShadow({ mode: 'open' }); (el.shadowRoot as ShadowRoot).appendChild( document.createElement('input'), ); document.body.append(el); }); await waitForRAF(page); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record moved shadow DOM', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'blank.html')); await page.evaluate(() => { return new Promise((resolve) => { const el = document.createElement('div') as HTMLDivElement; el.attachShadow({ mode: 'open' }); (el.shadowRoot as ShadowRoot).appendChild( document.createElement('input'), ); document.body.append(el); setTimeout(() => { const newEl = document.createElement('div') as HTMLDivElement; document.body.append(newEl); newEl.append(el); resolve(null); }, 50); }); }); await waitForRAF(page); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record moved shadow DOM 2', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'blank.html')); await page.evaluate(() => { const el = document.createElement('div') as HTMLDivElement; el.id = 'el'; el.attachShadow({ mode: 'open' }); (el.shadowRoot as ShadowRoot).appendChild( document.createElement('input'), ); document.body.append(el); (el.shadowRoot as ShadowRoot).appendChild(document.createElement('span')); (el.shadowRoot as ShadowRoot).appendChild(document.createElement('p')); const newEl = document.createElement('div') as HTMLDivElement; newEl.id = 'newEl'; document.body.append(newEl); newEl.append(el); const input = el.shadowRoot?.children[0] as HTMLInputElement; const span = el.shadowRoot?.children[1] as HTMLSpanElement; const p = el.shadowRoot?.children[2] as HTMLParagraphElement; input.remove(); span.append(input); p.append(input); span.append(input); setTimeout(() => { p.append(input); }, 0); }); await waitForRAF(page); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await 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.waitForSelector('iframe'); // wait for iframe to get added await waitForRAF(page); // wait till browser loaded contents of frame await page.evaluate(() => { // 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]; const iframe = contentDocument1.createElement('iframe'); // append an iframe to shadow dom #1 div.appendChild(iframe); }); await waitForRAF(page); // wait till browser loaded contents of frame page.evaluate(() => { const iframe: HTMLIFrameElement = document .querySelector('iframe')! .contentDocument!.body.shadowRoot!.querySelector('iframe')!; const contentDocument2 = iframe.contentDocument!; // create shadow dom #2 in the iframe contentDocument2.body.attachShadow({ mode: 'open' }); contentDocument2.body.shadowRoot!.appendChild( document.createElement('span'), ); }); await waitForRAF(page); // wait till browser sent snapshots const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record mutations in iframes accross pages', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto(`${serverURL}/html`); page.on('console', (msg) => console.log(msg.text())); await page.setContent(getHtml.call(this, 'frame2.html')); await page.waitForSelector('iframe'); // wait for iframe to get added await waitForRAF(page); // wait for iframe to load page.evaluate((serverURL) => { const iframe = document.querySelector('iframe')!; iframe.setAttribute('src', `${serverURL}/html`); // load new page }, serverURL); await page.waitForResponse(`${serverURL}/html`); // wait for iframe to load pt1 await waitForRAF(page); // wait for iframe to load pt2 await page.evaluate(() => { const iframeDocument = document.querySelector('iframe')!.contentDocument!; const div = iframeDocument.createElement('div'); iframeDocument.body.appendChild(div); }); await waitForRAF(page); // wait for snapshot to be updated const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); // https://github.com/webcomponents/polyfills/tree/master/packages/shadydom it('should record shadow doms polyfilled by shadydom', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( // insert shadydom script replaceLast( getHtml.call(this, 'polyfilled-shadowdom-mutation.html'), '', ` `, ), ); await page.evaluate(() => { const target3 = document.querySelector('#target3'); target3?.attachShadow({ mode: 'open', }); target3?.shadowRoot?.appendChild(document.createElement('span')); }); await waitForRAF(page); // wait till browser sent snapshots const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); // https://github.com/salesforce/lwc/tree/master/packages/%40lwc/synthetic-shadow it('should record shadow doms polyfilled by synthetic-shadow', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( // insert lwc's synthetic-shadow script replaceLast( getHtml.call(this, 'polyfilled-shadowdom-mutation.html'), '', ` `, ), ); await page.evaluate(() => { const target3 = document.querySelector('#target3'); // create a shadow dom with synthetic shadow // https://github.com/salesforce/lwc/blob/v2.20.3/packages/@lwc/synthetic-shadow/src/faux-shadow/element.ts#L81-L87 target3?.attachShadow({ mode: 'open', '$$lwc-synthetic-mode': true, } as ShadowRootInit); target3?.shadowRoot?.appendChild(document.createElement('span')); const target4 = document.createElement('div'); target4.id = 'target4'; // create a native shadow dom document.body.appendChild(target4); target4.attachShadow({ mode: 'open', }); target4.shadowRoot?.appendChild(document.createElement('ul')); }); await waitForRAF(page); // wait till browser sent snapshots const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should mask texts', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'mask-text.html', { maskTextSelector: '[data-masking="true"]', }), ); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should mask texts using maskTextFn', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'mask-text.html', { maskTextSelector: '[data-masking="true"]', maskTextFn: (t: string) => t.replace(/[a-z]/g, '*'), }), ); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should unmask texts using maskTextFn', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'mask-text.html', { maskTextSelector: '*', maskTextFn: (t: string, el: HTMLElement) => { return el.matches('[data-unmask-example="true"]') ? t : t.replace(/[a-z]/g, '*'); }, }), ); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('can mask character data mutations', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'mutation-observer.html')); await page.evaluate(() => { const li = document.createElement('li'); const ul = document.querySelector('ul') as HTMLUListElement; const p = document.querySelector('p') as HTMLParagraphElement; [li, p].forEach((element) => { element.className = 'rr-mask'; }); ul.appendChild(li); li.innerText = 'new list item'; p.innerText = 'mutated'; }); await page.evaluate(() => { // generate a characterData mutation; innerText doesn't do that const p = document.querySelector('p') as HTMLParagraphElement; (p.childNodes[0] as Text).insertData(0, 'doubly '); }); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('can mask character data mutations with regexp', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'mutation-observer.html', { maskTextClass: /custom/, }), ); await page.evaluate(() => { const li = document.createElement('li'); const ul = document.querySelector('ul') as HTMLUListElement; const p = document.querySelector('p') as HTMLParagraphElement; [ul, p].forEach((element) => { element.className = 'custom-mask'; }); ul.appendChild(li); li.innerText = 'new list item'; p.innerText = 'mutated'; }); await page.evaluate(() => { // generate a characterData mutation; innerText doesn't do that const li = document.querySelector('li:not(:empty)') as HTMLLIElement; (li.childNodes[0] as Text).insertData(0, 'descendent should be masked '); }); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record after DOMContentLoaded event', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); await page.setContent( getHtml.call(this, 'blank.html', { recordAfter: 'DOMContentLoaded', }), ); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); /** * the regression part of the following is now handled by replayer.test.ts::'can deal with duplicate/conflicting values on style elements' * so this test could be dropped if we add more robust mixing of `insertRule` into 'can record and replay style mutations' */ it('should record style mutations and replay them correctly', async () => { const page: puppeteer.Page = await browser.newPage(); const OldColor = 'rgb(255, 0, 0)'; // red color const NewColor = 'rgb(255, 255, 0)'; // yellow color await page.setContent( `
`, ); // Start rrweb recording await page.evaluate( (code, recordSnippet) => { const script = document.createElement('script'); script.textContent = `${code}window.Date.now = () => new Date(Date.UTC(2018, 10, 15, 8)).valueOf();${recordSnippet}`; document.head.appendChild(script); }, code, generateRecordSnippet({}), ); await page.evaluate( async (OldColor, NewColor) => { // Create a new style element with the same content as the existing style element and apply it to the #two div element const incrementalStyle = document.createElement( 'style', ) as HTMLStyleElement; incrementalStyle.textContent = ` \n`; document.head.appendChild(incrementalStyle); incrementalStyle.sheet!.insertRule(`#two { color: ${OldColor}; }`, 0); await new Promise((resolve) => requestAnimationFrame(() => { requestAnimationFrame(resolve); }), ); // Change the color of the #one div element to yellow as an incremental style mutation const styleElement = document.querySelector('style')!; (styleElement.sheet!.cssRules[0] as any).style.setProperty( 'color', NewColor, ); // Change the color of the #two div element to yellow as an incremental style mutation (incrementalStyle.sheet!.cssRules[0] as any).style.setProperty( 'color', NewColor, ); }, OldColor, NewColor, ); await waitForRAF(page); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); /** * Replay the recorded events and check if the style mutation is applied correctly */ const changedColors = await page.evaluate(` const { Replayer } = rrweb; const replayer = new Replayer(window.snapshots); replayer.pause(1000); // Get the color of the element after applying the style mutation event [ window.getComputedStyle( replayer.iframe.contentDocument.querySelector('#one'), ).color, window.getComputedStyle( replayer.iframe.contentDocument.querySelector('#two'), ).color, ]; `); expect(changedColors).toEqual([NewColor, NewColor]); await page.close(); }); it('should record style mutations with multiple child nodes and replay them correctly', async () => { // ensure that presence of multiple text nodes doesn't interfere with programmatic insertRule operations const page: puppeteer.Page = await browser.newPage(); const Color = 'rgb(255, 0, 0)'; // red color await page.setContent( `
`, ); // Start rrweb recording await page.evaluate( (code, recordSnippet) => { const script = document.createElement('script'); script.textContent = `${code};${recordSnippet}`; document.head.appendChild(script); }, code, generateRecordSnippet({}), ); await page.evaluate(async (Color) => { // Create a new style element with the same content as the existing style element and apply it to the #two div element const incrementalStyle = document.createElement( 'style', ) as HTMLStyleElement; incrementalStyle.append(document.createTextNode('/* hello */')); incrementalStyle.append(document.createTextNode('/* world */')); document.head.appendChild(incrementalStyle); incrementalStyle.sheet!.insertRule(`#two { color: ${Color}; }`, 0); }, Color); const snapshots = (await page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); /** * Replay the recorded events and check if the style mutation is applied correctly */ const changedColors = await page.evaluate(` const { Replayer } = rrweb; const replayer = new Replayer(window.snapshots); replayer.pause(1000); // Get the color of the element after applying the style mutation event [ window.getComputedStyle( replayer.iframe.contentDocument.querySelector('#one'), ).color, window.getComputedStyle( replayer.iframe.contentDocument.querySelector('#two'), ).color, ]; `); expect(changedColors).toEqual([Color, Color]); await page.close(); }); }); ================================================ FILE: packages/rrweb/test/machine.test.ts ================================================ import { discardPriorSnapshots } from '../src/replay/machine'; import { sampleEvents } from './utils'; import { EventType } from '@rrweb/types'; const events = sampleEvents.filter( (e) => ![EventType.DomContentLoaded, EventType.Load].includes(e.type), ); const nextEvents = events.map((e) => ({ ...e, timestamp: e.timestamp + 1000, })); const nextNextEvents = nextEvents.map((e) => ({ ...e, timestamp: e.timestamp + 1000, })); describe('get last session', () => { it('will return all the events when there is only one session', () => { expect(discardPriorSnapshots(events, events[0].timestamp)).toEqual(events); }); it('will return last session when there is more than one in the events', () => { const multiple = events.concat(nextEvents).concat(nextNextEvents); expect( discardPriorSnapshots( multiple, nextNextEvents[nextNextEvents.length - 1].timestamp, ), ).toEqual(nextNextEvents); }); it('will return last session when baseline time is future time', () => { const multiple = events.concat(nextEvents).concat(nextNextEvents); expect( discardPriorSnapshots( multiple, nextNextEvents[nextNextEvents.length - 1].timestamp + 1000, ), ).toEqual(nextNextEvents); }); it('will return all sessions when baseline time is prior time', () => { expect(discardPriorSnapshots(events, events[0].timestamp - 1000)).toEqual( events, ); }); }); ================================================ FILE: packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap ================================================ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`cross origin iframes > audio.html > should emit contents of iframe once 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/audio.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": { \\"lang\\": \\"en\\" }, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 15 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"charset\\": \\"UTF-8\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 16 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 17 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"http-equiv\\": \\"X-UA-Compatible\\", \\"content\\": \\"IE=edge\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"name\\": \\"viewport\\", \\"content\\": \\"width=device-width, initial-scale=1.0\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 2, \\"tagName\\": \\"title\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"Audio\\", \\"rootId\\": 11, \\"id\\": 23 } ], \\"rootId\\": 11, \\"id\\": 22 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 26 } ], \\"rootId\\": 11, \\"id\\": 25 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 29 }, { \\"type\\": 2, \\"tagName\\": \\"h1\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1 minute of silence\\", \\"rootId\\": 11, \\"id\\": 31 } ], \\"rootId\\": 11, \\"id\\": 30 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 32 }, { \\"type\\": 2, \\"tagName\\": \\"audio\\", \\"attributes\\": { \\"controls\\": \\"\\", \\"rr_mediaState\\": \\"paused\\", \\"rr_mediaCurrentTime\\": 0, \\"rr_mediaPlaybackRate\\": 1, \\"rr_mediaMuted\\": false, \\"rr_mediaLoop\\": false, \\"rr_mediaVolume\\": 1 }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 34 }, { \\"type\\": 2, \\"tagName\\": \\"source\\", \\"attributes\\": { \\"src\\": \\"http://localhost:3030/html/assets/1-minute-of-silence.mp3\\", \\"type\\": \\"audio/mpeg\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 35 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n Your browser does not support the audio element.\\\\n \\", \\"rootId\\": 11, \\"id\\": 36 } ], \\"rootId\\": 11, \\"id\\": 33 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 37 } ], \\"rootId\\": 11, \\"id\\": 28 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 7, \\"type\\": 0, \\"id\\": 33, \\"currentTime\\": 0, \\"volume\\": 1, \\"muted\\": false, \\"playbackRate\\": 1, \\"loop\\": false } } ]" `; exports[`cross origin iframes > blank.html > should filter out forwarded cross origin rrweb messages 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/blank.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 15 } ], \\"rootId\\": 11, \\"id\\": 14 } ], \\"rootId\\": 11, \\"id\\": 13 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 17 } ], \\"rootId\\": 11, \\"id\\": 16 } ], \\"rootId\\": 11, \\"id\\": 12 } ], \\"compatMode\\": \\"BackCompat\\", \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 16, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 18 } } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 18, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 19, \\"id\\": 23 } ], \\"rootId\\": 19, \\"id\\": 22 } ], \\"rootId\\": 19, \\"id\\": 21 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n\\\\n\\", \\"rootId\\": 19, \\"id\\": 25 } ], \\"rootId\\": 19, \\"id\\": 24 } ], \\"rootId\\": 19, \\"id\\": 20 } ], \\"compatMode\\": \\"BackCompat\\", \\"id\\": 19 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } } ]" `; exports[`cross origin iframes > blank.html > should record same-origin iframe in cross-origin iframe 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/blank.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 15 } ], \\"rootId\\": 11, \\"id\\": 14 } ], \\"rootId\\": 11, \\"id\\": 13 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 17 } ], \\"rootId\\": 11, \\"id\\": 16 } ], \\"rootId\\": 11, \\"id\\": 12 } ], \\"compatMode\\": \\"BackCompat\\", \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 16, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 18 } } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 18, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 19, \\"id\\": 21 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"Same-origin iframe in cross-origin iframe\\", \\"rootId\\": 19, \\"id\\": 23 } ], \\"rootId\\": 19, \\"id\\": 22 } ], \\"rootId\\": 19, \\"id\\": 20 } ], \\"compatMode\\": \\"BackCompat\\", \\"id\\": 19 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } } ]" `; exports[`cross origin iframes > form.html > should map input events correctly 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"style\\": \\"width: 400px; height: 400px;\\", \\"rr_src\\": \\"http://localhost:3030/html/form.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": { \\"lang\\": \\"en\\" }, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 15 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"charset\\": \\"UTF-8\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 16 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 17 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"name\\": \\"viewport\\", \\"content\\": \\"width=device-width, initial-scale=1.0\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"http-equiv\\": \\"X-UA-Compatible\\", \\"content\\": \\"ie=edge\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 2, \\"tagName\\": \\"title\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"form fields\\", \\"rootId\\": 11, \\"id\\": 23 } ], \\"rootId\\": 11, \\"id\\": 22 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 26 } ], \\"rootId\\": 11, \\"id\\": 25 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 29 }, { \\"type\\": 2, \\"tagName\\": \\"form\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"text\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 33 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"text\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 34 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 35 } ], \\"rootId\\": 11, \\"id\\": 32 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 36 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 38 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"radio\\", \\"name\\": \\"toggle\\", \\"value\\": \\"on\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 39 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 40 } ], \\"rootId\\": 11, \\"id\\": 37 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 41 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 43 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"radio\\", \\"name\\": \\"toggle\\", \\"value\\": \\"off\\", \\"checked\\": true }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 44 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 45 } ], \\"rootId\\": 11, \\"id\\": 42 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 46 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"checkbox\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 48 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"checkbox\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 49 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 50 } ], \\"rootId\\": 11, \\"id\\": 47 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 51 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"textarea\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 53 }, { \\"type\\": 2, \\"tagName\\": \\"textarea\\", \\"attributes\\": { \\"name\\": \\"\\", \\"id\\": \\"\\", \\"cols\\": \\"30\\", \\"rows\\": \\"10\\", \\"data-unmask-example\\": \\"true\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 54 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 55 } ], \\"rootId\\": 11, \\"id\\": 52 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 56 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"select\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 58 }, { \\"type\\": 2, \\"tagName\\": \\"select\\", \\"attributes\\": { \\"name\\": \\"\\", \\"id\\": \\"\\", \\"value\\": \\"1\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 60 }, { \\"type\\": 2, \\"tagName\\": \\"option\\", \\"attributes\\": { \\"value\\": \\"1\\", \\"selected\\": true }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 62 } ], \\"rootId\\": 11, \\"id\\": 61 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 63 }, { \\"type\\": 2, \\"tagName\\": \\"option\\", \\"attributes\\": { \\"value\\": \\"2\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"2\\", \\"rootId\\": 11, \\"id\\": 65 } ], \\"rootId\\": 11, \\"id\\": 64 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 66 } ], \\"rootId\\": 11, \\"id\\": 59 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 67 } ], \\"rootId\\": 11, \\"id\\": 57 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 68 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"password\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 70 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"password\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 71 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 72 } ], \\"rootId\\": 11, \\"id\\": 69 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 73 }, { \\"type\\": 2, \\"tagName\\": \\"textarea\\", \\"attributes\\": { \\"value\\": \\"pre value\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 74 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 75 } ], \\"rootId\\": 11, \\"id\\": 30 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 76 } ], \\"rootId\\": 11, \\"id\\": 28 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 5, \\"id\\": 34 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"t\\", \\"isChecked\\": false, \\"id\\": 34 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"te\\", \\"isChecked\\": false, \\"id\\": 34 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"tes\\", \\"isChecked\\": false, \\"id\\": 34 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"test\\", \\"isChecked\\": false, \\"id\\": 34 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 1, \\"id\\": 39 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 6, \\"id\\": 34 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 5, \\"id\\": 39 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 0, \\"id\\": 39 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 2, \\"id\\": 39, \\"pointerType\\": 0 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"on\\", \\"isChecked\\": true, \\"id\\": 39 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"off\\", \\"isChecked\\": false, \\"id\\": 44 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 1, \\"id\\": 49 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 6, \\"id\\": 39 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 5, \\"id\\": 49 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 0, \\"id\\": 49 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 2, \\"id\\": 49, \\"pointerType\\": 0 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"on\\", \\"isChecked\\": true, \\"id\\": 49 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 6, \\"id\\": 49 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 5, \\"id\\": 71 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"*\\", \\"isChecked\\": false, \\"id\\": 71 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"**\\", \\"isChecked\\": false, \\"id\\": 71 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"***\\", \\"isChecked\\": false, \\"id\\": 71 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"****\\", \\"isChecked\\": false, \\"id\\": 71 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"*****\\", \\"isChecked\\": false, \\"id\\": 71 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"******\\", \\"isChecked\\": false, \\"id\\": 71 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"*******\\", \\"isChecked\\": false, \\"id\\": 71 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"********\\", \\"isChecked\\": false, \\"id\\": 71 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 6, \\"id\\": 71 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 2, \\"type\\": 5, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"t\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"te\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"tex\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"text\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"texta\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"textar\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"textare\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"textarea\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"textarea \\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"textarea t\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"textarea te\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"textarea tes\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"textarea test\\", \\"isChecked\\": false, \\"id\\": 54 } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 5, \\"text\\": \\"1\\", \\"isChecked\\": false, \\"id\\": 59 } } ]" `; exports[`cross origin iframes > form.html > should map scroll events correctly 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"style\\": \\"width: 400px; height: 400px;\\", \\"rr_src\\": \\"http://localhost:3030/html/form.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": { \\"lang\\": \\"en\\" }, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 15 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"charset\\": \\"UTF-8\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 16 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 17 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"name\\": \\"viewport\\", \\"content\\": \\"width=device-width, initial-scale=1.0\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"http-equiv\\": \\"X-UA-Compatible\\", \\"content\\": \\"ie=edge\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 2, \\"tagName\\": \\"title\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"form fields\\", \\"rootId\\": 11, \\"id\\": 23 } ], \\"rootId\\": 11, \\"id\\": 22 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 26 } ], \\"rootId\\": 11, \\"id\\": 25 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 29 }, { \\"type\\": 2, \\"tagName\\": \\"form\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"text\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 33 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"text\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 34 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 35 } ], \\"rootId\\": 11, \\"id\\": 32 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 36 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 38 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"radio\\", \\"name\\": \\"toggle\\", \\"value\\": \\"on\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 39 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 40 } ], \\"rootId\\": 11, \\"id\\": 37 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 41 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 43 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"radio\\", \\"name\\": \\"toggle\\", \\"value\\": \\"off\\", \\"checked\\": true }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 44 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 45 } ], \\"rootId\\": 11, \\"id\\": 42 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 46 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"checkbox\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 48 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"checkbox\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 49 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 50 } ], \\"rootId\\": 11, \\"id\\": 47 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 51 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"textarea\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 53 }, { \\"type\\": 2, \\"tagName\\": \\"textarea\\", \\"attributes\\": { \\"name\\": \\"\\", \\"id\\": \\"\\", \\"cols\\": \\"30\\", \\"rows\\": \\"10\\", \\"data-unmask-example\\": \\"true\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 54 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 55 } ], \\"rootId\\": 11, \\"id\\": 52 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 56 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"select\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 58 }, { \\"type\\": 2, \\"tagName\\": \\"select\\", \\"attributes\\": { \\"name\\": \\"\\", \\"id\\": \\"\\", \\"value\\": \\"1\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 60 }, { \\"type\\": 2, \\"tagName\\": \\"option\\", \\"attributes\\": { \\"value\\": \\"1\\", \\"selected\\": true }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 62 } ], \\"rootId\\": 11, \\"id\\": 61 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 63 }, { \\"type\\": 2, \\"tagName\\": \\"option\\", \\"attributes\\": { \\"value\\": \\"2\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"2\\", \\"rootId\\": 11, \\"id\\": 65 } ], \\"rootId\\": 11, \\"id\\": 64 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 66 } ], \\"rootId\\": 11, \\"id\\": 59 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 67 } ], \\"rootId\\": 11, \\"id\\": 57 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 68 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"password\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 70 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"password\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 71 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 72 } ], \\"rootId\\": 11, \\"id\\": 69 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 73 }, { \\"type\\": 2, \\"tagName\\": \\"textarea\\", \\"attributes\\": { \\"value\\": \\"pre value\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 74 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 75 } ], \\"rootId\\": 11, \\"id\\": 30 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 76 } ], \\"rootId\\": 11, \\"id\\": 28 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [ { \\"id\\": 9, \\"attributes\\": { \\"style\\": \\"width: Npx; height: Npx;\\" } } ], \\"removes\\": [], \\"adds\\": [] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 3, \\"id\\": 11, \\"x\\": 0, \\"y\\": 10 } } ]" `; exports[`cross origin iframes > move-node.html > captures mutations on adopted stylesheets 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/move-node.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 16 } ], \\"rootId\\": 11, \\"id\\": 15 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 2, \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"p\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 22 } ], \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 23 }, { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 25 }, { \\"type\\": 2, \\"tagName\\": \\"i\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"b\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 29 } ], \\"rootId\\": 11, \\"id\\": 28 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 30 } ], \\"rootId\\": 11, \\"id\\": 26 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 } ], \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 32 } ], \\"rootId\\": 11, \\"id\\": 17 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 15, \\"id\\": 1, \\"styleIds\\": [ 1 ], \\"styles\\": [ { \\"styleId\\": 1, \\"rules\\": [] } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 15, \\"id\\": 11, \\"styleIds\\": [ 2 ], \\"styles\\": [ { \\"styleId\\": 2, \\"rules\\": [] } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"styleId\\": 1, \\"replace\\": \\"div { color: yellow; }\\" } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"styleId\\": 2, \\"replace\\": \\"h1 { color: blue; }\\" } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"styleId\\": 1, \\"replaceSync\\": \\"div { display: inline ; }\\" } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"styleId\\": 2, \\"replaceSync\\": \\"h1 { font-size: large; }\\" } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 13, \\"styleId\\": 1, \\"set\\": { \\"property\\": \\"color\\", \\"value\\": \\"green\\" }, \\"index\\": [ 0 ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 13, \\"styleId\\": 1, \\"remove\\": { \\"property\\": \\"display\\" }, \\"index\\": [ 0 ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 13, \\"styleId\\": 2, \\"set\\": { \\"property\\": \\"font-size\\", \\"value\\": \\"medium\\", \\"priority\\": \\"important\\" }, \\"index\\": [ 0 ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"styleId\\": 2, \\"adds\\": [ { \\"rule\\": \\"h2 { color: red; }\\" } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"styleId\\": 1, \\"adds\\": [ { \\"rule\\": \\"body { border: 2px solid blue; }\\", \\"index\\": 1 } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"styleId\\": 2, \\"removes\\": [ { \\"index\\": 0 } ] } } ]" `; exports[`cross origin iframes > move-node.html > captures mutations on stylesheets 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/move-node.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 16 } ], \\"rootId\\": 11, \\"id\\": 15 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 2, \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"p\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 22 } ], \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 23 }, { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 25 }, { \\"type\\": 2, \\"tagName\\": \\"i\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"b\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 29 } ], \\"rootId\\": 11, \\"id\\": 28 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 30 } ], \\"rootId\\": 11, \\"id\\": 26 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 } ], \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 32 } ], \\"rootId\\": 11, \\"id\\": 17 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 4, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"style\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"id\\": 33 } } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 14, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"style\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 34 } } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"id\\": 33, \\"adds\\": [ { \\"rule\\": \\"div { color: yellow; }\\" } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"id\\": 34, \\"adds\\": [ { \\"rule\\": \\"h1 { color: blue; }\\" } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 13, \\"id\\": 33, \\"set\\": { \\"property\\": \\"color\\", \\"value\\": \\"green\\" }, \\"index\\": [ 0 ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 13, \\"id\\": 33, \\"remove\\": { \\"property\\": \\"display\\" }, \\"index\\": [ 0 ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 13, \\"id\\": 34, \\"set\\": { \\"property\\": \\"font-size\\", \\"value\\": \\"medium\\", \\"priority\\": \\"important\\" }, \\"index\\": [ 0 ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"id\\": 34, \\"adds\\": [ { \\"rule\\": \\"h2 { color: red; }\\" } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"id\\": 33, \\"adds\\": [ { \\"rule\\": \\"body { border: 2px solid blue; }\\", \\"index\\": 1 } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 8, \\"id\\": 34, \\"removes\\": [ { \\"index\\": 0 } ] } } ]" `; exports[`cross origin iframes > move-node.html > should record DOM attribute 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/move-node.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 16 } ], \\"rootId\\": 11, \\"id\\": 15 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 2, \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"p\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 22 } ], \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 23 }, { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 25 }, { \\"type\\": 2, \\"tagName\\": \\"i\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"b\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 29 } ], \\"rootId\\": 11, \\"id\\": 28 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 30 } ], \\"rootId\\": 11, \\"id\\": 26 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 } ], \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 32 } ], \\"rootId\\": 11, \\"id\\": 17 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [ { \\"id\\": 24, \\"attributes\\": { \\"class\\": \\"added-class-name\\" } } ], \\"removes\\": [], \\"adds\\": [] } } ]" `; exports[`cross origin iframes > move-node.html > should record DOM node movement 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/move-node.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 16 } ], \\"rootId\\": 11, \\"id\\": 15 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 2, \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"p\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 22 } ], \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 23 }, { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 25 }, { \\"type\\": 2, \\"tagName\\": \\"i\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"b\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 29 } ], \\"rootId\\": 11, \\"id\\": 28 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 30 } ], \\"rootId\\": 11, \\"id\\": 26 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 } ], \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 32 } ], \\"rootId\\": 11, \\"id\\": 17 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [ { \\"parentId\\": 17, \\"id\\": 24 } ], \\"adds\\": [ { \\"parentId\\": 24, \\"nextId\\": 26, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 25 } }, { \\"parentId\\": 24, \\"nextId\\": 31, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"i\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 26 } }, { \\"parentId\\": 26, \\"nextId\\": 28, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 } }, { \\"parentId\\": 26, \\"nextId\\": 30, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"b\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 28 } }, { \\"parentId\\": 28, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 29 } }, { \\"parentId\\": 26, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 30 } }, { \\"parentId\\": 24, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 } }, { \\"parentId\\": 17, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 33 } }, { \\"parentId\\": 33, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 24 } } ] } } ]" `; exports[`cross origin iframes > move-node.html > should record DOM node removal 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/move-node.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 16 } ], \\"rootId\\": 11, \\"id\\": 15 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 2, \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"p\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 22 } ], \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 23 }, { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 25 }, { \\"type\\": 2, \\"tagName\\": \\"i\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"b\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 29 } ], \\"rootId\\": 11, \\"id\\": 28 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 30 } ], \\"rootId\\": 11, \\"id\\": 26 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 } ], \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 32 } ], \\"rootId\\": 11, \\"id\\": 17 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [ { \\"parentId\\": 17, \\"id\\": 24 } ], \\"adds\\": [] } } ]" `; exports[`cross origin iframes > move-node.html > should record DOM text 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/move-node.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 16 } ], \\"rootId\\": 11, \\"id\\": 15 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 2, \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"p\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 22 } ], \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 23 }, { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 25 }, { \\"type\\": 2, \\"tagName\\": \\"i\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"b\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 29 } ], \\"rootId\\": 11, \\"id\\": 28 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 30 } ], \\"rootId\\": 11, \\"id\\": 26 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 } ], \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 32 } ], \\"rootId\\": 11, \\"id\\": 17 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [ { \\"id\\": 29, \\"value\\": \\"replaced text\\" } ], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [] } } ]" `; exports[`cross origin iframes > move-node.html > should record canvas elements 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/move-node.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 16 } ], \\"rootId\\": 11, \\"id\\": 15 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 2, \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"p\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 22 } ], \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 23 }, { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 25 }, { \\"type\\": 2, \\"tagName\\": \\"i\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"b\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 29 } ], \\"rootId\\": 11, \\"id\\": 28 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 30 } ], \\"rootId\\": 11, \\"id\\": 26 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 } ], \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 32 } ], \\"rootId\\": 11, \\"id\\": 17 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 17, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"canvas\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 33 } } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 9, \\"id\\": 33, \\"type\\": 1, \\"commands\\": [ { \\"property\\": \\"createProgram\\", \\"args\\": [] }, { \\"property\\": \\"linkProgram\\", \\"args\\": [ { \\"rr_type\\": \\"WebGLProgram\\", \\"index\\": 0 } ] }, { \\"property\\": \\"clear\\", \\"args\\": [ 16384 ] } ] } } ]" `; exports[`cross origin iframes > move-node.html > should record custom events 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/move-node.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 16 } ], \\"rootId\\": 11, \\"id\\": 15 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 2, \\"tagName\\": \\"div\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"p\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 22 } ], \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 23 }, { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 25 }, { \\"type\\": 2, \\"tagName\\": \\"i\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"b\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 29 } ], \\"rootId\\": 11, \\"id\\": 28 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 30 } ], \\"rootId\\": 11, \\"id\\": 26 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 } ], \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 32 } ], \\"rootId\\": 11, \\"id\\": 17 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 5, \\"data\\": { \\"tag\\": \\"test\\", \\"payload\\": { \\"id\\": 11, \\"parentId\\": 11, \\"nextId\\": 12 } } } ]" `; exports[`same origin iframes > should emit contents of iframe once 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 13 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 14 } ], \\"rootId\\": 11, \\"id\\": 12 } ], \\"compatMode\\": \\"BackCompat\\", \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 13, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 15 } }, { \\"parentId\\": 15, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 16 } } ] } } ]" `; exports[`same origin iframes > should record cross-origin iframe in same-origin iframe 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 13 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 14 } ], \\"rootId\\": 11, \\"id\\": 12 } ], \\"compatMode\\": \\"BackCompat\\", \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 13, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 15 } }, { \\"parentId\\": 15, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 16 } } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 14, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 17 } } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 17, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 18, \\"id\\": 22 } ], \\"rootId\\": 18, \\"id\\": 21 } ], \\"rootId\\": 18, \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n\\\\n\\", \\"rootId\\": 18, \\"id\\": 24 } ], \\"rootId\\": 18, \\"id\\": 23 } ], \\"rootId\\": 18, \\"id\\": 19 } ], \\"compatMode\\": \\"BackCompat\\", \\"id\\": 18 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } } ]" `; ================================================ FILE: packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap.extra ================================================ // this file is here in the case that the assertSnapshot(events); lines needs to be restored for debugging purposes for this test. // the following lines would have to be moved back into the appropriate place in rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap exports[`cross origin iframes > form.html > should replace the existing DOM nodes on iframe navigation with \`isAttachIframe\` 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"iframe\\", \\"attributes\\": { \\"style\\": \\"width: 400px; height: 400px;\\", \\"rr_src\\": \\"http://localhost:3030/html/form.html\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 11, \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": { \\"lang\\": \\"en\\" }, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 15 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"charset\\": \\"UTF-8\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 16 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 17 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"name\\": \\"viewport\\", \\"content\\": \\"width=device-width, initial-scale=1.0\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 18 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 19 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"http-equiv\\": \\"X-UA-Compatible\\", \\"content\\": \\"ie=edge\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 20 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 21 }, { \\"type\\": 2, \\"tagName\\": \\"title\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"form fields\\", \\"rootId\\": 11, \\"id\\": 23 } ], \\"rootId\\": 11, \\"id\\": 22 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 24 }, { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 11, \\"id\\": 26 } ], \\"rootId\\": 11, \\"id\\": 25 } ], \\"rootId\\": 11, \\"id\\": 14 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n\\\\n \\", \\"rootId\\": 11, \\"id\\": 27 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 29 }, { \\"type\\": 2, \\"tagName\\": \\"form\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 31 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"text\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 33 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"text\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 34 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 35 } ], \\"rootId\\": 11, \\"id\\": 32 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 36 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 38 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"radio\\", \\"name\\": \\"toggle\\", \\"value\\": \\"on\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 39 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 40 } ], \\"rootId\\": 11, \\"id\\": 37 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 41 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 43 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"radio\\", \\"name\\": \\"toggle\\", \\"value\\": \\"off\\", \\"checked\\": true }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 44 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 45 } ], \\"rootId\\": 11, \\"id\\": 42 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 46 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"checkbox\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 48 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"checkbox\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 49 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 50 } ], \\"rootId\\": 11, \\"id\\": 47 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 51 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"textarea\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 53 }, { \\"type\\": 2, \\"tagName\\": \\"textarea\\", \\"attributes\\": { \\"name\\": \\"\\", \\"id\\": \\"\\", \\"cols\\": \\"30\\", \\"rows\\": \\"10\\", \\"data-unmask-example\\": \\"true\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 54 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 55 } ], \\"rootId\\": 11, \\"id\\": 52 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 56 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"select\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 58 }, { \\"type\\": 2, \\"tagName\\": \\"select\\", \\"attributes\\": { \\"name\\": \\"\\", \\"id\\": \\"\\", \\"value\\": \\"1\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 60 }, { \\"type\\": 2, \\"tagName\\": \\"option\\", \\"attributes\\": { \\"value\\": \\"1\\", \\"selected\\": true }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"1\\", \\"rootId\\": 11, \\"id\\": 62 } ], \\"rootId\\": 11, \\"id\\": 61 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 63 }, { \\"type\\": 2, \\"tagName\\": \\"option\\", \\"attributes\\": { \\"value\\": \\"2\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"2\\", \\"rootId\\": 11, \\"id\\": 65 } ], \\"rootId\\": 11, \\"id\\": 64 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 66 } ], \\"rootId\\": 11, \\"id\\": 59 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 67 } ], \\"rootId\\": 11, \\"id\\": 57 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 68 }, { \\"type\\": 2, \\"tagName\\": \\"label\\", \\"attributes\\": { \\"for\\": \\"password\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 70 }, { \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { \\"type\\": \\"password\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 71 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 72 } ], \\"rootId\\": 11, \\"id\\": 69 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 73 }, { \\"type\\": 2, \\"tagName\\": \\"textarea\\", \\"attributes\\": { \\"value\\": \\"pre value\\" }, \\"childNodes\\": [], \\"rootId\\": 11, \\"id\\": 74 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 11, \\"id\\": 75 } ], \\"rootId\\": 11, \\"id\\": 30 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 11, \\"id\\": 76 } ], \\"rootId\\": 11, \\"id\\": 28 } ], \\"rootId\\": 11, \\"id\\": 13 } ], \\"id\\": 11 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [ { \\"id\\": 9, \\"attributes\\": { \\"rr_src\\": \\"http://localhost:3030/html/empty.html\\" } } ], \\"removes\\": [], \\"adds\\": [] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"adds\\": [ { \\"parentId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 1, \\"name\\": \\"html\\", \\"publicId\\": \\"\\", \\"systemId\\": \\"\\", \\"rootId\\": 77, \\"id\\": 78 }, { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": { \\"lang\\": \\"en\\" }, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 77, \\"id\\": 81 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"charset\\": \\"UTF-8\\" }, \\"childNodes\\": [], \\"rootId\\": 77, \\"id\\": 82 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 77, \\"id\\": 83 }, { \\"type\\": 2, \\"tagName\\": \\"meta\\", \\"attributes\\": { \\"name\\": \\"viewport\\", \\"content\\": \\"width=device-width, initial-scale=1.0\\" }, \\"childNodes\\": [], \\"rootId\\": 77, \\"id\\": 84 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 77, \\"id\\": 85 }, { \\"type\\": 2, \\"tagName\\": \\"title\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"Empty\\", \\"rootId\\": 77, \\"id\\": 87 } ], \\"rootId\\": 77, \\"id\\": 86 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 77, \\"id\\": 88 }, { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"rootId\\": 77, \\"id\\": 90 } ], \\"rootId\\": 77, \\"id\\": 89 } ], \\"rootId\\": 77, \\"id\\": 80 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 77, \\"id\\": 91 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"rootId\\": 77, \\"id\\": 93 }, { \\"type\\": 2, \\"tagName\\": \\"div\\", \\"attributes\\": { \\"id\\": \\"one\\" }, \\"childNodes\\": [], \\"rootId\\": 77, \\"id\\": 94 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"rootId\\": 77, \\"id\\": 95 } ], \\"rootId\\": 77, \\"id\\": 92 } ], \\"rootId\\": 77, \\"id\\": 79 } ], \\"id\\": 77 } } ], \\"removes\\": [], \\"texts\\": [], \\"attributes\\": [], \\"isAttachIframe\\": true } } ]" `; ================================================ FILE: packages/rrweb/test/record/__snapshots__/dialog.test.ts.snap ================================================ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`dialog > add dialog and show 1`] = ` "[ { \\"type\\": 4, \\"data\\": { \\"href\\": \\"about:blank\\", \\"width\\": 1920, \\"height\\": 1080 } }, { \\"type\\": 2, \\"data\\": { \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 5 } ], \\"id\\": 4 } ], \\"id\\": 3 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 7 }, { \\"type\\": 2, \\"tagName\\": \\"dialog\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"I'm a dialog\\", \\"id\\": 9 } ], \\"id\\": 8 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"id\\": 10 } ], \\"id\\": 6 } ], \\"id\\": 2 } ], \\"compatMode\\": \\"BackCompat\\", \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 6, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"dialog\\", \\"attributes\\": { \\"open\\": \\"\\", \\"rr_open_mode\\": \\"non-modal\\" }, \\"childNodes\\": [], \\"id\\": 11 } } ] } } ]" `; exports[`dialog > add dialog and showModal 1`] = ` "[ { \\"type\\": 4, \\"data\\": { \\"href\\": \\"about:blank\\", \\"width\\": 1920, \\"height\\": 1080 } }, { \\"type\\": 2, \\"data\\": { \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 5 } ], \\"id\\": 4 } ], \\"id\\": 3 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 7 }, { \\"type\\": 2, \\"tagName\\": \\"dialog\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"I'm a dialog\\", \\"id\\": 9 } ], \\"id\\": 8 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"id\\": 10 } ], \\"id\\": 6 } ], \\"id\\": 2 } ], \\"compatMode\\": \\"BackCompat\\", \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 6, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"dialog\\", \\"attributes\\": { \\"open\\": \\"\\", \\"rr_open_mode\\": \\"modal\\" }, \\"childNodes\\": [], \\"id\\": 11 } } ] } } ]" `; exports[`dialog > switch to show dialog 1`] = ` "[ { \\"type\\": 4, \\"data\\": { \\"href\\": \\"about:blank\\", \\"width\\": 1920, \\"height\\": 1080 } }, { \\"type\\": 2, \\"data\\": { \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 5 } ], \\"id\\": 4 } ], \\"id\\": 3 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 7 }, { \\"type\\": 2, \\"tagName\\": \\"dialog\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"I'm a dialog\\", \\"id\\": 9 } ], \\"id\\": 8 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"id\\": 10 } ], \\"id\\": 6 } ], \\"id\\": 2 } ], \\"compatMode\\": \\"BackCompat\\", \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [ { \\"id\\": 8, \\"attributes\\": { \\"open\\": \\"\\", \\"rr_open_mode\\": \\"modal\\" } } ], \\"removes\\": [], \\"adds\\": [] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [ { \\"id\\": 8, \\"attributes\\": { \\"open\\": \\"\\", \\"rr_open_mode\\": \\"non-modal\\" } } ], \\"removes\\": [], \\"adds\\": [] } } ]" `; exports[`dialog > switch to showModal dialog 1`] = ` "[ { \\"type\\": 4, \\"data\\": { \\"href\\": \\"about:blank\\", \\"width\\": 1920, \\"height\\": 1080 } }, { \\"type\\": 2, \\"data\\": { \\"node\\": { \\"type\\": 0, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"html\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"head\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 5 } ], \\"id\\": 4 } ], \\"id\\": 3 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 7 }, { \\"type\\": 2, \\"tagName\\": \\"dialog\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"I'm a dialog\\", \\"id\\": 9 } ], \\"id\\": 8 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"id\\": 10 } ], \\"id\\": 6 } ], \\"id\\": 2 } ], \\"compatMode\\": \\"BackCompat\\", \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [ { \\"id\\": 8, \\"attributes\\": { \\"open\\": \\"\\", \\"rr_open_mode\\": \\"non-modal\\" } } ], \\"removes\\": [], \\"adds\\": [] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [ { \\"id\\": 8, \\"attributes\\": { \\"open\\": \\"\\", \\"rr_open_mode\\": \\"modal\\" } } ], \\"removes\\": [], \\"adds\\": [] } } ]" `; ================================================ FILE: packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap ================================================ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`record webgl > recordCanvas FPS > should record snapshots 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"canvas\\", \\"attributes\\": { \\"id\\": \\"canvas\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } } ]" `; exports[`record webgl > should batch events by RAF 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"canvas\\", \\"attributes\\": { \\"id\\": \\"canvas\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 9, \\"id\\": 9, \\"type\\": 1, \\"commands\\": [ { \\"property\\": \\"createProgram\\", \\"args\\": [] }, { \\"property\\": \\"linkProgram\\", \\"args\\": [ { \\"rr_type\\": \\"WebGLProgram\\", \\"index\\": 0 } ] } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 9, \\"id\\": 9, \\"type\\": 1, \\"commands\\": [ { \\"property\\": \\"createProgram\\", \\"args\\": [] }, { \\"property\\": \\"linkProgram\\", \\"args\\": [ { \\"rr_type\\": \\"WebGLProgram\\", \\"index\\": 1 } ] }, { \\"property\\": \\"clear\\", \\"args\\": [ 16384 ] } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 9, \\"id\\": 9, \\"type\\": 1, \\"commands\\": [ { \\"property\\": \\"clear\\", \\"args\\": [ 16384 ] } ] } } ]" `; exports[`record webgl > will record changes to a canvas element 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"canvas\\", \\"attributes\\": { \\"id\\": \\"canvas\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 9, \\"id\\": 9, \\"type\\": 1, \\"commands\\": [ { \\"property\\": \\"clear\\", \\"args\\": [ 16384 ] } ] } } ]" `; exports[`record webgl > will record changes to a canvas element before the canvas gets added (webgl2) 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"canvas\\", \\"attributes\\": { \\"id\\": \\"canvas\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 7, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"canvas\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"id\\": 11 } } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 9, \\"id\\": 11, \\"type\\": 2, \\"commands\\": [ { \\"property\\": \\"createProgram\\", \\"args\\": [] }, { \\"property\\": \\"linkProgram\\", \\"args\\": [ { \\"rr_type\\": \\"WebGLProgram\\", \\"index\\": 0 } ] }, { \\"property\\": \\"clear\\", \\"args\\": [ 16384 ] } ] } } ]" `; exports[`record webgl > will record changes to a canvas element before the canvas gets added 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"canvas\\", \\"attributes\\": { \\"id\\": \\"canvas\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 0, \\"texts\\": [], \\"attributes\\": [], \\"removes\\": [], \\"adds\\": [ { \\"parentId\\": 7, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"canvas\\", \\"attributes\\": {}, \\"childNodes\\": [], \\"id\\": 11 } } ] } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 9, \\"id\\": 11, \\"type\\": 1, \\"commands\\": [ { \\"property\\": \\"createProgram\\", \\"args\\": [] }, { \\"property\\": \\"linkProgram\\", \\"args\\": [ { \\"rr_type\\": \\"WebGLProgram\\", \\"index\\": 0 } ] }, { \\"property\\": \\"clear\\", \\"args\\": [ 16384 ] } ] } } ]" `; exports[`record webgl > will record changes to a webgl2 canvas element 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"canvas\\", \\"attributes\\": { \\"id\\": \\"canvas\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 9, \\"id\\": 9, \\"type\\": 2, \\"commands\\": [ { \\"property\\": \\"clear\\", \\"args\\": [ 16384 ] } ] } } ]" `; exports[`record webgl > will record webgl variables 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"canvas\\", \\"attributes\\": { \\"id\\": \\"canvas\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 9, \\"id\\": 9, \\"type\\": 1, \\"commands\\": [ { \\"property\\": \\"createProgram\\", \\"args\\": [] }, { \\"property\\": \\"linkProgram\\", \\"args\\": [ { \\"rr_type\\": \\"WebGLProgram\\", \\"index\\": 0 } ] }, { \\"property\\": \\"createProgram\\", \\"args\\": [] }, { \\"property\\": \\"linkProgram\\", \\"args\\": [ { \\"rr_type\\": \\"WebGLProgram\\", \\"index\\": 1 } ] } ] } } ]" `; exports[`record webgl > will record webgl variables in reverse order 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\\": [ { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": { \\"type\\": \\"text/javascript\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 6 } ], \\"id\\": 5 } ], \\"id\\": 4 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 8 }, { \\"type\\": 2, \\"tagName\\": \\"canvas\\", \\"attributes\\": { \\"id\\": \\"canvas\\" }, \\"childNodes\\": [], \\"id\\": 9 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", \\"id\\": 10 } ], \\"id\\": 7 } ], \\"id\\": 3 } ], \\"id\\": 1 }, \\"initialOffset\\": { \\"left\\": 0, \\"top\\": 0 } } }, { \\"type\\": 3, \\"data\\": { \\"source\\": 9, \\"id\\": 9, \\"type\\": 1, \\"commands\\": [ { \\"property\\": \\"createProgram\\", \\"args\\": [] }, { \\"property\\": \\"createProgram\\", \\"args\\": [] }, { \\"property\\": \\"linkProgram\\", \\"args\\": [ { \\"rr_type\\": \\"WebGLProgram\\", \\"index\\": 1 } ] }, { \\"property\\": \\"linkProgram\\", \\"args\\": [ { \\"rr_type\\": \\"WebGLProgram\\", \\"index\\": 0 } ] } ] } } ]" `; ================================================ FILE: packages/rrweb/test/record/cross-origin-iframes.test.ts ================================================ import * as fs from 'fs'; import * as path from 'path'; import type * as puppeteer from 'puppeteer'; import { vi } from 'vitest'; import type { recordOptions } from '../../src/types'; import type { listenerHandler, eventWithTime, mutationData, } from '@rrweb/types'; import { EventType, IncrementalSource } from '@rrweb/types'; import { assertSnapshot, getServerURL, launchPuppeteer, startServer, waitForRAF, } from '../utils'; import type * as http from 'http'; interface ISuite { code: string; browser: puppeteer.Browser; page: puppeteer.Page; events: eventWithTime[]; server: http.Server; serverURL: string; } interface IWindow extends Window { rrweb: { record: ( options: recordOptions, ) => listenerHandler | undefined; addCustomEvent(tag: string, payload: T): void; pack: (e: eventWithTime) => string; }; emit: (e: eventWithTime) => undefined; snapshots: eventWithTime[]; } type ExtraOptions = { usePackFn?: boolean; }; async function injectRecordScript( frame: puppeteer.Frame, options?: ExtraOptions, ) { try { await frame.addScriptTag({ path: path.resolve(__dirname, '../../dist/rrweb.umd.cjs'), }); } catch (e) { // we get this error: `Protocol error (DOM.resolveNode): Node with given id does not belong to the document` // then the page wasn't loaded yet and we try again if ( !e.message.includes('DOM.resolveNode') && !e.message.includes('DOM.describeNode') ) throw e; await injectRecordScript(frame, options); return; } options = options || {}; await frame.evaluate((options) => { (window as unknown as IWindow).snapshots = []; const { record } = (window as unknown as IWindow).rrweb; const config: recordOptions = { recordCrossOriginIframes: true, recordCanvas: true, emit(event) { (window as unknown as IWindow).snapshots.push(event); (window as unknown as IWindow).emit(event); }, }; record(config); }, options); for (const child of frame.childFrames()) { await injectRecordScript(child, options); } } const setup = function ( this: ISuite, content: string, options?: ExtraOptions, ): ISuite { const ctx = {} as ISuite & { serverB: http.Server; serverBURL: string; }; beforeAll(async () => { ctx.browser = await launchPuppeteer(); ctx.server = await startServer(); ctx.serverURL = getServerURL(ctx.server); ctx.serverB = await startServer(); ctx.serverBURL = getServerURL(ctx.serverB); const bundlePath = path.resolve(__dirname, '../../dist/rrweb.umd.cjs'); 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.replace(/\{SERVER_URL\}/g, ctx.serverURL), ); // 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())); await injectRecordScript(ctx.page.mainFrame(), options); }); afterEach(async () => { await ctx.page.close(); }); afterAll(async () => { await ctx.browser.close(); ctx.server.close(); ctx.serverB.close(); }); return ctx; }; describe('cross origin iframes', function (this: ISuite) { vi.setConfig({ testTimeout: 100_000 }); describe('form.html', function (this: ISuite) { const ctx: ISuite = setup.call( this, ` `, ); it("won't emit events if it isn't in the top level iframe", async () => { const el = (await ctx.page.$( 'body > iframe', )) as puppeteer.ElementHandle; const frame = await el.contentFrame(); const events = await frame?.evaluate( () => (window as unknown as IWindow).snapshots, ); expect(events).toMatchObject([]); }); it('will emit events if it is in the top level iframe', async () => { const events = await ctx.page.evaluate( () => (window as unknown as IWindow).snapshots, ); expect(events.length).not.toBe(0); }); it('should emit contents of iframe', async () => { const events = await ctx.page.evaluate( () => (window as unknown as IWindow).snapshots, ); await waitForRAF(ctx.page); // two events (full snapshot + meta) from main frame, and one full snapshot from iframe expect(events.length).toBe(3); }); it('should emit full snapshot event from iframe as mutation event', async () => { const events = await ctx.page.evaluate( () => (window as unknown as IWindow).snapshots, ); await waitForRAF(ctx.page); // two events from main frame, and two from iframe expect(events[events.length - 1]).toMatchObject({ type: EventType.IncrementalSnapshot, data: { source: IncrementalSource.Mutation, adds: [ { parentId: expect.any(Number), node: { id: expect.any(Number), }, }, ], }, }); }); it('should use unique id for child of iframes', async () => { const events: eventWithTime[] = await ctx.page.evaluate( () => (window as unknown as IWindow).snapshots, ); await waitForRAF(ctx.page); expect( (events[events.length - 1].data as mutationData).adds[0].node.id, ).not.toBe(1); }); it('should replace the existing DOM nodes on iframe navigation with `isAttachIframe`', async () => { await ctx.page.evaluate((url) => { const iframe = document.querySelector('iframe') as HTMLIFrameElement; iframe.src = `${url}/html/empty.html`; }, ctx.serverURL); await waitForRAF(ctx.page); // should load iframe (but sometimes doesn't) const frame = ctx.page.mainFrame().childFrames()[0]; await frame.waitForSelector('#one'); // ensure frame has changed await injectRecordScript(ctx.page.mainFrame().childFrames()[0]); // injects script into new iframe const events: eventWithTime[] = await ctx.page.evaluate( () => (window as unknown as IWindow).snapshots, ); // for future detailed debugging of this test, the full output is available // 'out of band' in test/record/__snapshots__/cross-origin-iframes.test.ts.snap.extra // assertSnapshot(events); expect( (events[events.length - 1].data as mutationData).removes, ).toMatchObject([]); expect( (events[events.length - 1].data as mutationData).isAttachIframe, ).toBeTruthy(); }); it('should map input events correctly', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; await frame.type('input[type="text"]', 'test'); await frame.click('input[type="radio"]'); await frame.click('input[type="checkbox"]'); await frame.type('input[type="password"]', 'password'); await frame.type('textarea', 'textarea test'); await frame.select('select', '1'); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should map scroll events correctly', async () => { // force scrollbars in iframe ctx.page.evaluate(() => { const iframe = document.querySelector('iframe') as HTMLIFrameElement; iframe.style.width = '50px'; iframe.style.height = '50px'; }); await waitForRAF(ctx.page); const frame = ctx.page.mainFrame().childFrames()[0]; // scroll a little frame.evaluate(() => { window.scrollTo(0, 10); }); await waitForRAF(ctx.page); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); }); describe('move-node.html', function (this: ISuite) { const ctx: ISuite = setup.call( this, ` `, ); it('should record DOM node movement', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; await frame.evaluate(() => { const div = document.createElement('div'); const span = document.querySelector('span')!; document.body.appendChild(div); div.appendChild(span); }); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record DOM node removal', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; await frame.evaluate(() => { const span = document.querySelector('span')!; span.remove(); }); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record DOM attribute changes', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; await frame.evaluate(() => { const span = document.querySelector('span')!; span.className = 'added-class-name'; }); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record DOM text changes', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; await frame.evaluate(() => { const b = document.querySelector('b')!; b.childNodes[0].textContent = 'replaced text'; }); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record canvas elements', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; await frame.evaluate(() => { var canvas = document.createElement('canvas'); var gl = canvas.getContext('webgl')!; var program = gl.createProgram()!; gl.linkProgram(program); gl.clear(gl.COLOR_BUFFER_BIT); document.body.appendChild(canvas); }); await waitForRAF(ctx.page); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should record custom events', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; await frame.evaluate(() => { (window as unknown as IWindow).rrweb.addCustomEvent('test', { id: 1, parentId: 1, nextId: 2, }); }); await waitForRAF(ctx.page); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('captures mutations on adopted stylesheets', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; await ctx.page.evaluate(() => { const sheet = new CSSStyleSheet(); // Add stylesheet to a document. document.adoptedStyleSheets = [sheet]; }); await frame.evaluate(() => { const sheet = new CSSStyleSheet(); // Add stylesheet to a document. document.adoptedStyleSheets = [sheet]; }); await waitForRAF(ctx.page); await ctx.page.evaluate(() => { document.adoptedStyleSheets![0].replace!('div { color: yellow; }'); }); await frame.evaluate(() => { document.adoptedStyleSheets![0].replace!('h1 { color: blue; }'); }); await waitForRAF(ctx.page); await ctx.page.evaluate(() => { document.adoptedStyleSheets![0].replaceSync!( 'div { display: inline ; }', ); }); await frame.evaluate(() => { document.adoptedStyleSheets![0].replaceSync!( 'h1 { font-size: large; }', ); }); await waitForRAF(ctx.page); await ctx.page.evaluate(() => { ( document.adoptedStyleSheets![0].cssRules[0] as CSSStyleRule ).style.setProperty('color', 'green'); ( document.adoptedStyleSheets![0].cssRules[0] as CSSStyleRule ).style.removeProperty('display'); }); await frame.evaluate(() => { ( document.adoptedStyleSheets![0].cssRules[0] as CSSStyleRule ).style.setProperty('font-size', 'medium', 'important'); document.adoptedStyleSheets![0].insertRule('h2 { color: red; }'); }); await waitForRAF(ctx.page); await ctx.page.evaluate(() => { document.adoptedStyleSheets![0].insertRule( 'body { border: 2px solid blue; }', 1, ); }); await frame.evaluate(() => { document.adoptedStyleSheets![0].deleteRule(0); }); await waitForRAF(ctx.page); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('captures mutations on stylesheets', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; await ctx.page.evaluate(() => { // Add stylesheet to a document. const style = document.createElement('style'); document.head.appendChild(style); }); await frame.evaluate(() => { // Add stylesheet to a document. const style = document.createElement('style'); document.head.appendChild(style); }); await waitForRAF(ctx.page); await ctx.page.evaluate(() => { document.styleSheets[0].insertRule('div { color: yellow; }'); }); await frame.evaluate(() => { document.styleSheets[0].insertRule('h1 { color: blue; }'); }); await waitForRAF(ctx.page); await ctx.page.evaluate(() => { (document.styleSheets[0].cssRules[0] as CSSStyleRule).style.setProperty( 'color', 'green', ); ( document.styleSheets[0].cssRules[0] as CSSStyleRule ).style.removeProperty('display'); }); await frame.evaluate(() => { (document.styleSheets[0].cssRules[0] as CSSStyleRule).style.setProperty( 'font-size', 'medium', 'important', ); document.styleSheets[0].insertRule('h2 { color: red; }'); }); await waitForRAF(ctx.page); await ctx.page.evaluate(() => { document.styleSheets[0].insertRule( 'body { border: 2px solid blue; }', 1, ); }); await frame.evaluate(() => { document.styleSheets[0].deleteRule(0); }); await waitForRAF(ctx.page); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); }); describe('audio.html', function (this: ISuite) { vi.setConfig({ testTimeout: 100_000 }); const ctx: ISuite = setup.call( this, ` `, ); it('should emit contents of iframe once', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; await frame.evaluate(() => { const el = document.querySelector('audio')!; el.play(); }); await waitForRAF(ctx.page); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); }); describe('blank.html', function (this: ISuite) { const content = ` `; const ctx = setup.call(this, content) as ISuite & { serverBURL: string; }; it('should record same-origin iframe in cross-origin iframe', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; await frame.evaluate(() => { const iframe2 = document.createElement('iframe'); // Append a same-origin iframe in a cross-origin iframe. document.body.appendChild(iframe2); iframe2.contentDocument!.body.appendChild( document.createTextNode('Same-origin iframe in cross-origin iframe'), ); }); await waitForRAF(ctx.page); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); it('should filter out forwarded cross origin rrweb messages', async () => { const frame = ctx.page.mainFrame().childFrames()[0]; const iframe2URL = `${ctx.serverBURL}/html/blank.html`; await frame.evaluate((iframe2URL) => { // Add a message proxy to forward messages from child frames to its parent frame. window.addEventListener('message', (event) => { if (event.source !== window) window.parent.postMessage(event.data, '*'); }); const iframe2 = document.createElement('iframe'); iframe2.src = iframe2URL; document.body.appendChild(iframe2); }, iframe2URL); // Wait for iframe2 to load await ctx.page.waitForFrame(iframe2URL); const iframe2 = frame.childFrames()[0]; // Record iframe2 await injectRecordScript(iframe2); await waitForRAF(iframe2); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); }); }); describe('same origin iframes', function (this: ISuite) { vi.setConfig({ testTimeout: 100_000 }); const ctx: ISuite = setup.call( this, ` `, ); it('should emit contents of iframe once', async () => { const events = await ctx.page.evaluate( () => (window as unknown as IWindow).snapshots, ); await waitForRAF(ctx.page); // two events (full snapshot + meta) from main frame, // and two (full snapshot + mutation) from iframe expect(events.length).toBe(4); await assertSnapshot(events); }); it('should record cross-origin iframe in same-origin iframe', async () => { const sameOriginIframe = ctx.page.mainFrame().childFrames()[0]; await sameOriginIframe.evaluate((serverUrl) => { /** * Create a cross-origin iframe in this same-origin iframe. */ const crossOriginIframe = document.createElement('iframe'); document.body.appendChild(crossOriginIframe); crossOriginIframe.src = `${serverUrl}/html/blank.html`; return new Promise((resolve) => { crossOriginIframe.onload = resolve; }); }, ctx.serverURL); const crossOriginIframe = sameOriginIframe.childFrames()[0]; // Inject recording script into this cross-origin iframe await injectRecordScript(crossOriginIframe); await waitForRAF(ctx.page); const snapshots = (await ctx.page.evaluate( 'window.snapshots', )) as eventWithTime[]; await assertSnapshot(snapshots); }); }); ================================================ FILE: packages/rrweb/test/record/dialog.test.ts ================================================ import * as fs from 'fs'; import * as path from 'path'; import { vi } from 'vitest'; import { assertSnapshot, getServerURL, ISuite, launchPuppeteer, startServer, waitForRAF, } from '../utils'; import { attributeMutation, EventType, eventWithTime, listenerHandler, } from '@rrweb/types'; import { recordOptions } from '../../src/types'; interface IWindow extends Window { rrweb: { record: ( options: recordOptions, ) => listenerHandler | undefined; addCustomEvent(tag: string, payload: T): void; }; emit: (e: eventWithTime) => undefined; } const attributeMutationFactory = ( mutation: attributeMutation['attributes'], ) => { return { data: { attributes: [ { attributes: mutation, }, ], }, }; }; describe('dialog', () => { vi.setConfig({ testTimeout: 100_000 }); let code: ISuite['code']; let page: ISuite['page']; let browser: ISuite['browser']; let server: ISuite['server']; let serverURL: ISuite['serverURL']; let events: ISuite['events']; beforeAll(async () => { server = await startServer(); serverURL = getServerURL(server); browser = await launchPuppeteer(); const bundlePath = path.resolve(__dirname, '../../dist/rrweb.umd.cjs'); code = fs.readFileSync(bundlePath, 'utf8'); }); afterEach(async () => { await page.close(); }); afterAll(async () => { await server.close(); await browser.close(); }); beforeEach(async () => { page = await browser.newPage(); page.on('console', (msg) => { console.log(msg.text()); }); await page.goto(`${serverURL}/html/dialog.html`); await page.addScriptTag({ path: path.resolve(__dirname, '../../dist/rrweb.umd.cjs'), }); await waitForRAF(page); events = []; await page.exposeFunction('emit', (e: eventWithTime) => { if (e.type === EventType.DomContentLoaded || e.type === EventType.Load) { return; } events.push(e); }); page.on('console', (msg) => console.log('PAGE LOG:', msg.text())); await page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ emit: (window as unknown as IWindow).emit, }); }); await waitForRAF(page); }); it('show dialog', async () => { await page.evaluate(() => { const dialog = document.querySelector('dialog') as HTMLDialogElement; dialog.show(); }); const lastEvent = events[events.length - 1]; expect(lastEvent).toMatchObject(attributeMutationFactory({ open: '' })); // assertSnapshot(events); }); it('showModal dialog', async () => { await page.evaluate(() => { const dialog = document.querySelector('dialog') as HTMLDialogElement; dialog.showModal(); }); const lastEvent = events[events.length - 1]; expect(lastEvent).toMatchObject( attributeMutationFactory({ rr_open_mode: 'modal' }), ); }); it('showModal & close dialog', async () => { await page.evaluate(() => { const dialog = document.querySelector('dialog') as HTMLDialogElement; dialog.showModal(); }); await waitForRAF(page); await page.evaluate(() => { const dialog = document.querySelector('dialog') as HTMLDialogElement; dialog.close(); }); const lastEvent = events[events.length - 1]; expect(lastEvent).toMatchObject(attributeMutationFactory({ open: null })); }); it('show & close dialog', async () => { await page.evaluate(() => { const dialog = document.querySelector('dialog') as HTMLDialogElement; dialog.show(); }); await waitForRAF(page); await page.evaluate(() => { const dialog = document.querySelector('dialog') as HTMLDialogElement; dialog.close(); }); const lastEvent = events[events.length - 1]; expect(lastEvent).toMatchObject(attributeMutationFactory({ open: null })); }); it('switch to showModal dialog', async () => { await page.evaluate(() => { const dialog = document.querySelector('dialog') as HTMLDialogElement; dialog.show(); }); await waitForRAF(page); await page.evaluate(() => { const dialog = document.querySelector('dialog') as HTMLDialogElement; dialog.close(); dialog.showModal(); }); await assertSnapshot(events); }); it('switch to show dialog', async () => { await page.evaluate(() => { const dialog = document.querySelector('dialog') as HTMLDialogElement; dialog.showModal(); }); await waitForRAF(page); await page.evaluate(() => { const dialog = document.querySelector('dialog') as HTMLDialogElement; dialog.close(); dialog.show(); }); await assertSnapshot(events); }); it('add dialog and showModal', async () => { await page.evaluate(() => { const dialog = document.createElement('dialog') as HTMLDialogElement; document.body.appendChild(dialog); dialog.showModal(); }); await waitForRAF(page); await assertSnapshot(events); }); it('add dialog and show', async () => { await page.evaluate(() => { const dialog = document.createElement('dialog') as HTMLDialogElement; document.body.appendChild(dialog); dialog.show(); }); await waitForRAF(page); await assertSnapshot(events); }); // TODO: implement me in the future it.skip('should record playback order with multiple dialogs opening', async () => { await page.evaluate(() => { const dialog1 = document.createElement('dialog') as HTMLDialogElement; dialog1.className = 'dialog1'; document.body.appendChild(dialog1); const dialog2 = document.createElement('dialog') as HTMLDialogElement; dialog1.className = 'dialog2'; document.body.appendChild(dialog2); dialog2.showModal(); // <== Note that dialog TWO is being triggered first dialog1.showModal(); }); await waitForRAF(page); await assertSnapshot(events); // <== This should trigger showModal() on dialog2 first, then dialog1 }); }); ================================================ FILE: packages/rrweb/test/record/error-handler.test.ts ================================================ import * as fs from 'fs'; import * as path from 'path'; import type * as puppeteer from 'puppeteer'; import { vi } from 'vitest'; import type { recordOptions } from '../../src/types'; import { listenerHandler, eventWithTime, EventType } from '@rrweb/types'; import { launchPuppeteer } from '../utils'; import { callbackWrapper, registerErrorHandler, unregisterErrorHandler, } from '../../src/record/error-handler'; 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, canvasSample: 'all' | number = 'all', ): ISuite { const ctx = {} as ISuite; beforeAll(async () => { ctx.browser = await launchPuppeteer(); const bundlePath = path.resolve(__dirname, '../../dist/rrweb.umd.cjs'); 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('error-handler', function (this: ISuite) { vi.setConfig({ testTimeout: 100_000 }); const ctx: ISuite = setup.call( this, `
`, ); describe('CSSStyleSheet.prototype', () => { it('triggers for errors from insertRule', async () => { await ctx.page.evaluate(() => { // @ts-ignore rewrite this to something buggy window.CSSStyleSheet.prototype.insertRule = function () { // @ts-ignore window.doSomethingWrong(); }; }); await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ errorHandler: (error) => { document.getElementById('out')!.innerText = `${error}`; }, emit: (window as unknown as IWindow).emit, }); // Trigger buggy style sheet insert setTimeout(() => { // @ts-ignore document.styleSheets[0].insertRule('body { background: blue; }'); }, 50); }); await ctx.page.waitForTimeout(100); const element = await ctx.page.$('#out'); const text = await element!.evaluate((el) => el.textContent); expect(text).toEqual( 'TypeError: window.doSomethingWrong is not a function', ); }); it('triggers for errors from deleteRule', async () => { await ctx.page.evaluate(() => { // @ts-ignore rewrite this to something buggy window.CSSStyleSheet.prototype.deleteRule = function () { // @ts-ignore window.doSomethingWrong(); }; }); await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ errorHandler: (error) => { document.getElementById('out')!.innerText = `${error}`; }, emit: (window as unknown as IWindow).emit, }); // Trigger buggy style sheet delete setTimeout(() => { document.styleSheets[0].deleteRule(0); }, 50); }); await ctx.page.waitForTimeout(100); const element = await ctx.page.$('#out'); const text = await element!.evaluate((el) => el.textContent); expect(text).toEqual( 'TypeError: window.doSomethingWrong is not a function', ); }); it('triggers for errors from replace', async () => { await ctx.page.evaluate(() => { // @ts-ignore rewrite this to something buggy window.CSSStyleSheet.prototype.replace = function () { // @ts-ignore window.doSomethingWrong(); }; }); await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ errorHandler: (error) => { document.getElementById('out')!.innerText = `${error}`; }, emit: (window as unknown as IWindow).emit, }); // Trigger buggy style sheet insert setTimeout(() => { // @ts-ignore document.styleSheets[0].replace('body { background: blue; }'); }, 50); }); await ctx.page.waitForTimeout(100); const element = await ctx.page.$('#out'); const text = await element!.evaluate((el) => el.textContent); expect(text).toEqual( 'TypeError: window.doSomethingWrong is not a function', ); }); it('triggers for errors from replaceSync', async () => { await ctx.page.evaluate(() => { // @ts-ignore rewrite this to something buggy window.CSSStyleSheet.prototype.replaceSync = function () { // @ts-ignore window.doSomethingWrong(); }; }); await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ errorHandler: (error) => { document.getElementById('out')!.innerText = `${error}`; }, emit: (window as unknown as IWindow).emit, }); // Trigger buggy style sheet insert setTimeout(() => { // @ts-ignore document.styleSheets[0].replaceSync('body { background: blue; }'); }, 50); }); await ctx.page.waitForTimeout(100); const element = await ctx.page.$('#out'); const text = await element!.evaluate((el) => el.textContent); expect(text).toEqual( 'TypeError: window.doSomethingWrong is not a function', ); }); it('triggers for errors from CSSGroupingRule.insertRule', async () => { await ctx.page.evaluate(() => { // @ts-ignore rewrite this to something buggy window.CSSGroupingRule.prototype.insertRule = function () { // @ts-ignore window.doSomethingWrong(); }; }); await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ errorHandler: (error) => { document.getElementById('out')!.innerText = `${error}`; }, emit: (window as unknown as IWindow).emit, }); // Trigger buggy style sheet insert setTimeout(() => { document.styleSheets[0].insertRule('@media {}'); const atMediaRule = document.styleSheets[0] .cssRules[0] as CSSMediaRule; const ruleIdx0 = atMediaRule.insertRule( 'body { background: #000; }', 0, ); }, 50); }); await ctx.page.waitForTimeout(100); const element = await ctx.page.$('#out'); const text = await element!.evaluate((el) => el.textContent); expect(text).toEqual( 'TypeError: window.doSomethingWrong is not a function', ); }); it('triggers for errors from CSSGroupingRule.deleteRule', async () => { await ctx.page.evaluate(() => { // @ts-ignore rewrite this to something buggy window.CSSGroupingRule.prototype.deleteRule = function () { // @ts-ignore window.doSomethingWrong(); }; }); await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ errorHandler: (error) => { document.getElementById('out')!.innerText = `${error}`; }, emit: (window as unknown as IWindow).emit, }); // Trigger buggy style sheet delete setTimeout(() => { document.styleSheets[0].insertRule('@media {}'); const atMediaRule = document.styleSheets[0] .cssRules[0] as CSSMediaRule; const ruleIdx0 = atMediaRule.deleteRule(0); }, 50); }); await ctx.page.waitForTimeout(100); const element = await ctx.page.$('#out'); const text = await element!.evaluate((el) => el.textContent); expect(text).toEqual( 'TypeError: window.doSomethingWrong is not a function', ); }); it('triggers for errors from CSSStyleDeclaration.setProperty', async () => { await ctx.page.evaluate(() => { // @ts-ignore rewrite this to something buggy window.CSSStyleDeclaration.prototype.setProperty = function () { // @ts-ignore window.doSomethingWrong(); }; }); await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ errorHandler: (error) => { document.getElementById('out')!.innerText = `${error}`; }, emit: (window as unknown as IWindow).emit, }); // Trigger buggy style sheet insert setTimeout(() => { ( document.styleSheets[0].cssRules[0] as unknown as { style: CSSStyleDeclaration; } ).style.setProperty('background', 'blue'); }, 50); }); await ctx.page.waitForTimeout(100); const element = await ctx.page.$('#out'); const text = await element!.evaluate((el) => el.textContent); expect(text).toEqual( 'TypeError: window.doSomethingWrong is not a function', ); }); it('triggers for errors from CSSStyleDeclaration.removeProperty', async () => { await ctx.page.evaluate(() => { // @ts-ignore rewrite this to something buggy window.CSSStyleDeclaration.prototype.removeProperty = function () { // @ts-ignore window.doSomethingWrong(); }; }); await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ errorHandler: (error) => { document.getElementById('out')!.innerText = `${error}`; }, emit: (window as unknown as IWindow).emit, }); // Trigger buggy style sheet insert setTimeout(() => { ( document.styleSheets[0].cssRules[0] as unknown as { style: CSSStyleDeclaration; } ).style.removeProperty('background'); }, 50); }); await ctx.page.waitForTimeout(100); const element = await ctx.page.$('#out'); const text = await element!.evaluate((el) => el.textContent); expect(text).toEqual( 'TypeError: window.doSomethingWrong is not a function', ); }); }); it('triggers for errors from mutation observer', async () => { await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ errorHandler: (error) => { document.getElementById('out')!.innerText = `${error}`; }, emit: (window as unknown as IWindow).emit, }); // Trigger buggy mutation observer setTimeout(() => { const el = document.getElementById('in')!; // @ts-ignore we want to trigger an error in the mutation observer, which uses this el.getAttribute = undefined; el.setAttribute('data-attr', 'new'); }, 50); }); await ctx.page.waitForTimeout(100); const element = await ctx.page.$('#out'); const text = await element!.evaluate((el) => el.textContent); expect(text).toEqual('TypeError: m.target.getAttribute is not a function'); }); }); describe('errorHandler unit', function () { afterEach(function () { unregisterErrorHandler(); }); it('does not swallow if no errorHandler set', () => { unregisterErrorHandler(); const wrapped = callbackWrapper(() => { throw new Error('test'); }); expect(() => wrapped()).toThrowError('test'); }); it('does not swallow if errorHandler returns void', () => { registerErrorHandler(() => { // do nothing }); const wrapped = callbackWrapper(() => { throw new Error('test'); }); expect(() => wrapped()).toThrowError('test'); }); it('does not swallow if errorHandler returns false', () => { registerErrorHandler(() => { return false; }); const wrapped = callbackWrapper(() => { throw new Error('test'); }); expect(() => wrapped()).toThrowError('test'); }); it('swallows if errorHandler returns true', () => { registerErrorHandler(() => { return true; }); const wrapped = callbackWrapper(() => { throw new Error('test'); }); expect(() => wrapped()).not.toThrowError('test'); }); }); ================================================ FILE: packages/rrweb/test/record/serialize-args.test.ts ================================================ /** * @vitest-environment jsdom */ import { polyfillWebGLGlobals } from '../utils'; polyfillWebGLGlobals(); import { serializeArg } from '../../src/record/observers/canvas/serialize-args'; const createContext = () => { const ctx = new WebGL2RenderingContext(); return ctx; }; let context: WebGL2RenderingContext; describe('serializeArg', () => { beforeEach(() => { context = createContext(); }); it('should serialize Float32Array values', async () => { const float32Array = new Float32Array([-1, -1, 3, -1, -1, 3]); const expected = { rr_type: 'Float32Array', args: [[-1, -1, 3, -1, -1, 3]], }; expect(serializeArg(float32Array, window, context)).toStrictEqual(expected); }); it('should serialize Float64Array values', async () => { const float64Array = new Float64Array([-1, -1, 3, -1, -1, 3]); const expected = { rr_type: 'Float64Array', args: [[-1, -1, 3, -1, -1, 3]], }; expect(serializeArg(float64Array, window, context)).toStrictEqual(expected); }); it('should serialize ArrayBuffer values', async () => { const arrayBuffer = new Uint8Array([1, 2, 0, 4]).buffer; const expected = { rr_type: 'ArrayBuffer', base64: 'AQIABA==', }; expect(serializeArg(arrayBuffer, window, context)).toStrictEqual(expected); }); it('should serialize Uint8Array values', async () => { const object = new Uint8Array([1, 2, 0, 4]); const expected = { rr_type: 'Uint8Array', args: [[1, 2, 0, 4]], }; expect(serializeArg(object, window, context)).toStrictEqual(expected); }); it('should serialize DataView values', async () => { const dataView = new DataView(new ArrayBuffer(16), 0, 16); const expected = { rr_type: 'DataView', args: [ { rr_type: 'ArrayBuffer', base64: 'AAAAAAAAAAAAAAAAAAAAAA==', }, 0, 16, ], }; expect(serializeArg(dataView, window, context)).toStrictEqual(expected); }); it('should leave arrays intact', async () => { const array = [1, 2, 3, 4]; expect(serializeArg(array, window, context)).toStrictEqual(array); }); it('should serialize complex objects', async () => { const dataView = [new DataView(new ArrayBuffer(16), 0, 16), 5, 6]; const expected = [ { rr_type: 'DataView', args: [ { rr_type: 'ArrayBuffer', base64: 'AAAAAAAAAAAAAAAAAAAAAA==', }, 0, 16, ], }, 5, 6, ]; expect(serializeArg(dataView, window, context)).toStrictEqual(expected); }); it('should serialize arraybuffer contents', async () => { const buffer = new Float32Array([1, 2, 3, 4]).buffer; const expected = { rr_type: 'ArrayBuffer', base64: 'AACAPwAAAEAAAEBAAACAQA==', }; expect(serializeArg(buffer, window, context)).toStrictEqual(expected); }); it('should leave null as-is', async () => { expect(serializeArg(null, window, context)).toStrictEqual(null); }); it('should support indexed variables', async () => { const webGLProgram = new WebGLProgram(); expect(serializeArg(webGLProgram, window, context)).toStrictEqual({ rr_type: 'WebGLProgram', index: 0, }); const webGLProgram2 = new WebGLProgram(); expect(serializeArg(webGLProgram2, window, context)).toStrictEqual({ rr_type: 'WebGLProgram', index: 1, }); }); it('should support indexed variables grouped by context', async () => { const context1 = createContext(); const webGLProgram1 = new WebGLProgram(); expect(serializeArg(webGLProgram1, window, context1)).toStrictEqual({ rr_type: 'WebGLProgram', index: 0, }); const context2 = createContext(); const webGLProgram2 = new WebGLProgram(); expect(serializeArg(webGLProgram2, window, context2)).toStrictEqual({ rr_type: 'WebGLProgram', index: 0, }); }); it('should support HTMLImageElements', async () => { const image = new Image(); image.src = 'http://example.com/image.png'; expect(serializeArg(image, window, context)).toStrictEqual({ rr_type: 'HTMLImageElement', src: 'http://example.com/image.png', }); }); it('should support HTMLCanvasElements saved to image', async () => { const canvas = document.createElement('canvas'); // polyfill canvas.toDataURL as it doesn't exist in jsdom canvas.toDataURL = () => 'data:image/png;base64,...'; expect(serializeArg(canvas, window, context)).toMatchObject({ rr_type: 'HTMLImageElement', src: 'data:image/png;base64,...', }); }); it('should serialize ImageData', async () => { const arr = new Uint8ClampedArray(40000); // Iterate through every pixel for (let i = 0; i < arr.length; i += 4) { arr[i + 0] = 0; // R value arr[i + 1] = 190; // G value arr[i + 2] = 0; // B value arr[i + 3] = 255; // A value } // Initialize a new ImageData object let imageData = new ImageData(arr, 200, 50); const contents = Array.from(arr); expect(serializeArg(imageData, window, context)).toStrictEqual({ rr_type: 'ImageData', args: [ { rr_type: 'Uint8ClampedArray', args: [contents], }, 200, 50, ], }); }); // we do not yet support async serializing which is needed to call Blob.arrayBuffer() it.skip('should serialize a blob', async () => { const arrayBuffer = new Uint8Array([1, 2, 0, 4]).buffer; const blob = new Blob([arrayBuffer], { type: 'image/png' }); const expected = { rr_type: 'ArrayBuffer', base64: 'AQIABA==', }; expect(await serializeArg(blob, window, context)).toStrictEqual({ rr_type: 'Blob', args: [expected, { type: 'image/png' }], }); }); }); ================================================ FILE: packages/rrweb/test/record/webgl.test.ts ================================================ import * as fs from 'fs'; import * as path from 'path'; import type * as puppeteer from 'puppeteer'; import { vi } from 'vitest'; import type { recordOptions } from '../../src/types'; import { listenerHandler, eventWithTime, EventType, IncrementalSource, CanvasContext, } from '@rrweb/types'; import { assertSnapshot, launchPuppeteer, stripBase64, waitForRAF, } from '../utils'; import type { ICanvas } from 'rrweb-snapshot'; 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, canvasSample: 'all' | number = 'all', ): ISuite { const ctx = {} as ISuite; beforeAll(async () => { ctx.browser = await launchPuppeteer(); }); beforeEach(async () => { ctx.page = await ctx.browser.newPage(); await ctx.page.goto('about:blank'); await ctx.page.setContent(content); await ctx.page.addScriptTag({ path: path.resolve(__dirname, '../../dist/rrweb.umd.cjs'), }); 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())); await ctx.page.evaluate((canvasSample) => { const { record } = (window as unknown as IWindow).rrweb; record({ recordCanvas: true, sampling: { canvas: canvasSample, }, emit: (window as unknown as IWindow).emit, }); }, canvasSample); }); afterEach(async () => { await ctx.page.close(); }); afterAll(async () => { await ctx.browser.close(); }); return ctx; }; describe('record webgl', function (this: ISuite) { vi.setConfig({ testTimeout: 100_000 }); const ctx: ISuite = setup.call( this, ` `, ); it('will record changes to a canvas element', async () => { await ctx.page.evaluate(() => { var canvas = document.getElementById('canvas') as HTMLCanvasElement; var gl = canvas.getContext('webgl')!; gl.clear(gl.COLOR_BUFFER_BIT); }); await ctx.page.waitForTimeout(50); const lastEvent = ctx.events[ctx.events.length - 1]; expect(lastEvent).toMatchObject({ data: { source: IncrementalSource.CanvasMutation, type: CanvasContext.WebGL, commands: [ { args: [16384], property: 'clear', }, ], }, }); await assertSnapshot(ctx.events); }); it('will record changes to a webgl2 canvas element', async () => { await ctx.page.evaluate(() => { var canvas = document.getElementById('canvas') as HTMLCanvasElement; var gl = canvas.getContext('webgl2')!; gl.clear(gl.COLOR_BUFFER_BIT); }); await ctx.page.waitForTimeout(50); const lastEvent = ctx.events[ctx.events.length - 1]; expect(lastEvent).toMatchObject({ data: { source: IncrementalSource.CanvasMutation, type: CanvasContext.WebGL2, commands: [ { args: [16384], property: 'clear', }, ], }, }); await assertSnapshot(ctx.events); }); it('will record changes to a canvas element before the canvas gets added', async () => { await ctx.page.evaluate(() => { var canvas = document.createElement('canvas'); var gl = canvas.getContext('webgl')!; var program = gl.createProgram()!; gl.linkProgram(program); gl.clear(gl.COLOR_BUFFER_BIT); document.body.appendChild(canvas); }); await waitForRAF(ctx.page); await assertSnapshot(ctx.events); }); it('will record changes to a canvas element before the canvas gets added (webgl2)', async () => { await ctx.page.evaluate(() => { return new Promise((resolve) => { var canvas = document.createElement('canvas'); var gl = canvas.getContext('webgl2')!; var program = gl.createProgram()!; gl.linkProgram(program); gl.clear(gl.COLOR_BUFFER_BIT); setTimeout(() => { document.body.appendChild(canvas); resolve(); }, 10); }); }); // FIXME: this wait deeply couples the test to the implementation // When `pendingCanvasMutations` isn't run on requestAnimationFrame, // we need to change this await waitForRAF(ctx.page); await assertSnapshot(ctx.events); }); it('will record webgl variables', async () => { await ctx.page.evaluate(() => { var canvas = document.getElementById('canvas') as HTMLCanvasElement; var gl = canvas.getContext('webgl')!; var program0 = gl.createProgram()!; gl.linkProgram(program0); var program1 = gl.createProgram()!; gl.linkProgram(program1); }); await ctx.page.waitForTimeout(50); await assertSnapshot(ctx.events); }); it('will record webgl variables in reverse order', async () => { await ctx.page.evaluate(() => { var canvas = document.getElementById('canvas') as HTMLCanvasElement; var gl = canvas.getContext('webgl')!; var program0 = gl.createProgram()!; var program1 = gl.createProgram()!; // attach them in reverse order gl.linkProgram(program1); gl.linkProgram(program0); }); await ctx.page.waitForTimeout(50); await assertSnapshot(ctx.events); }); it('sets _context on canvas.getContext()', async () => { const context = await ctx.page.evaluate(() => { var canvas = document.getElementById('canvas') as HTMLCanvasElement; canvas.getContext('webgl')!; return (canvas as ICanvas).__context; }); expect(context).toBe('webgl'); }); it('only sets _context on first canvas.getContext() call', async () => { const context = await ctx.page.evaluate(() => { var canvas = document.getElementById('canvas') as HTMLCanvasElement; canvas.getContext('webgl'); canvas.getContext('2d'); // returns null return (canvas as ICanvas).__context; }); expect(context).toBe('webgl'); }); it('should batch events by RAF', async () => { await ctx.page.evaluate(() => { return new Promise((resolve) => { const canvas = document.getElementById('canvas') as HTMLCanvasElement; const gl = canvas.getContext('webgl') as WebGLRenderingContext; const program = gl.createProgram()!; gl.linkProgram(program); requestAnimationFrame(() => { const program2 = gl.createProgram()!; gl.linkProgram(program2); gl.clear(gl.COLOR_BUFFER_BIT); requestAnimationFrame(() => { gl.clear(gl.COLOR_BUFFER_BIT); resolve(); }); }); }); }); await ctx.page.waitForTimeout(50); await assertSnapshot(ctx.events); expect(ctx.events.length).toEqual(5); }); describe('recordCanvas FPS', function (this: ISuite) { vi.setConfig({ testTimeout: 10_000 }); const maxFPS = 60; const ctx: ISuite = setup.call( this, ` `, maxFPS, ); it('should record snapshots', async () => { await ctx.page.evaluate(() => { const canvas = document.getElementById('canvas') as HTMLCanvasElement; const gl = canvas.getContext('webgl', { preserveDrawingBuffer: true })!; // Set the clear color to darkish green. gl.clearColor(0.0, 0.5, 0.0, 1.0); // Clear the context with the newly set color. This is // the function call that actually does the drawing. gl.clear(gl.COLOR_BUFFER_BIT); }); await ctx.page.waitForTimeout(200); // give it some time buffer await ctx.page.evaluate(() => { const canvas = document.getElementById('canvas') as HTMLCanvasElement; const gl = canvas.getContext('webgl', { preserveDrawingBuffer: true })!; // Set the clear color to darkish blue. gl.clearColor(0.0, 0.0, 0.5, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); }); await ctx.page.waitForTimeout(200); await waitForRAF(ctx.page); // should yield a frame for each change at a max of 60fps await assertSnapshot(stripBase64(ctx.events)); }); }); }); ================================================ FILE: packages/rrweb/test/record.test.ts ================================================ import * as fs from 'fs'; import * as path from 'path'; import type * as puppeteer from 'puppeteer'; import { vi } from 'vitest'; import 'construct-style-sheets-polyfill'; import type { recordOptions } from '../src/types'; import { listenerHandler, eventWithTime, EventType, IncrementalSource, styleSheetRuleData, selectionData, } from '@rrweb/types'; import { assertSnapshot, getServerURL, launchPuppeteer, startServer, waitForRAF, } from './utils'; import type { Server } from 'http'; interface ISuite { code: string; browser: puppeteer.Browser; page: puppeteer.Page; events: eventWithTime[]; } interface IWindow extends Window { rrweb: { record: (( options: recordOptions, ) => listenerHandler | undefined) & { takeFullSnapshot: (isCheckout?: boolean | undefined) => void; }; freezePage(): void; 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({ devtools: true, }); const bundlePath = path.resolve(__dirname, '../dist/rrweb.umd.cjs'); 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) { vi.setConfig({ testTimeout: 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, }); }); await ctx.page.type('input', 'a'); await ctx.page.waitForTimeout(300); expect( ctx.events.filter((event: eventWithTime) => event.type === EventType.Meta) .length, ).toEqual(1); // before first automatic snapshot expect( ctx.events.filter( (event: eventWithTime) => event.type === EventType.FullSnapshot, ).length, ).toEqual(1); // before first automatic snapshot await ctx.page.waitForTimeout(200); await ctx.page.type('input', 'a'); await ctx.page.waitForTimeout(10); 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); }); 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); await assertSnapshot(ctx.events); }); it('should record scroll position', async () => { await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ emit: (window as unknown as IWindow).emit, }); const p = document.createElement('p'); p.innerText = 'testtesttesttesttesttesttesttesttesttest'; p.setAttribute('style', 'overflow: auto; height: 1px; width: 1px;'); document.body.appendChild(p); p.scrollTop = 10; p.scrollLeft = 10; }); await waitForRAF(ctx.page); await assertSnapshot(ctx.events); }); it('should record selection event', async () => { await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ emit: (window as unknown as IWindow).emit, }); const startNode = document.createElement('p'); startNode.innerText = 'Lorem ipsum dolor sit amet consectetur adipisicing elit.'; const endNode = document.createElement('span'); endNode.innerText = 'nihil ipsum officiis pariatur laboriosam quas,corrupti vero vitae minus.'; document.body.appendChild(startNode); document.body.appendChild(endNode); const selection = window.getSelection(); const range = new Range(); range.setStart(startNode!.firstChild!, 10); range.setEnd(endNode!.firstChild!, 2); selection?.addRange(range); }); await waitForRAF(ctx.page); const selectionData = ctx.events .filter(({ type, data }) => { return ( type === EventType.IncrementalSnapshot && data.source === IncrementalSource.Selection ); }) .map((ev) => ev.data as selectionData); expect(selectionData.length).toEqual(1); expect(selectionData[0].ranges[0].startOffset).toEqual(10); expect(selectionData[0].ranges[0].endOffset).toEqual(2); }); 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); await 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((addRules[1].data as styleSheetRuleData).adds).toEqual([ { rule: 'body { color: #ccc; }', }, ]); expect(removeRuleCount).toEqual(1); await assertSnapshot(ctx.events); }); it('captures stylesheet rules with deprecated addRule & removeRule properties', 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.addRule('body', 'background: #000;'); const ruleIdx1 = styleSheet.addRule('body', 'background: #111;'); styleSheet.removeRule(ruleIdx1); // end: pre-serialization setTimeout(() => { styleSheet.addRule('body', 'color: #fff;'); }, 0); setTimeout(() => { styleSheet.removeRule(ruleIdx0); }, 5); setTimeout(() => { styleSheet.addRule('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([ { index: 1, rule: 'body { color: #fff; }', }, ]); expect((addRules[1].data as styleSheetRuleData).adds).toEqual([ { index: 1, rule: 'body { color: #ccc; }', }, ]); expect(removeRuleCount).toEqual(1); await 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); await 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, ignoreCSSAttributes: new Set(['color']), }); const styleElement = document.createElement('style'); document.head.appendChild(styleElement); const styleSheet = styleElement.sheet; styleSheet.insertRule('body { background: #000; }'); setTimeout(() => { // should be ignored (styleSheet.cssRules[0] as CSSStyleRule).style.setProperty( 'color', 'green', ); // should be captured because we did not block it (styleSheet.cssRules[0] as CSSStyleRule).style.setProperty( 'border-color', 'green', ); (styleSheet.cssRules[0] as CSSStyleRule).style.removeProperty( 'background', ); }, 0); }); await ctx.page.waitForTimeout(50); await assertSnapshot(ctx.events); }); it('captures inserted style text nodes correctly', async () => { await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; const styleEl = document.createElement(`style`); styleEl.append(document.createTextNode('div { color: red; }')); styleEl.append(document.createTextNode('section { color: blue; }')); document.head.appendChild(styleEl); record({ emit: (window as unknown as IWindow).emit, }); styleEl.append(document.createTextNode('span { color: orange; }')); styleEl.append(document.createTextNode('h1 { color: pink; }')); }); await waitForRAF(ctx.page); await assertSnapshot(ctx.events); }); it('captures stylesheets with `blob:` url', async () => { await ctx.page.evaluate(() => { const link1 = document.createElement('link'); link1.setAttribute('rel', 'stylesheet'); link1.setAttribute( 'href', URL.createObjectURL( new Blob(['body { color: pink; }'], { type: 'text/css', }), ), ); document.head.appendChild(link1); }); await waitForRAF(ctx.page); await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ inlineStylesheet: true, emit: (window as unknown as IWindow).emit, }); }); await waitForRAF(ctx.page); await assertSnapshot(ctx.events); }); it('captures mutations on adopted stylesheets', async () => { await ctx.page.evaluate(() => { return new Promise((resolve) => { document.body.innerHTML = `
div in outermost document
`; const sheet = new CSSStyleSheet(); // Add stylesheet to a document. document.adoptedStyleSheets = [sheet]; const iframe = document.querySelector('iframe'); const sheet2 = new ( iframe!.contentWindow! as Window & typeof globalThis ).CSSStyleSheet(); // Add stylesheet to an IFrame document. iframe!.contentDocument!.adoptedStyleSheets = [sheet2]; iframe!.contentDocument!.body.innerHTML = '

h1 in iframe

'; const { rrweb, emit } = window as unknown as IWindow; rrweb.record({ emit, }); setTimeout(() => { sheet.replace!('div { color: yellow; }'); sheet2.replace!('h1 { color: blue; }'); }, 0); setTimeout(() => { sheet.replaceSync!('div { display: inline ; }'); sheet2.replaceSync!('h1 { font-size: large; }'); }, 5); setTimeout(() => { (sheet.cssRules[0] as CSSStyleRule).style.setProperty( 'color', 'green', ); (sheet.cssRules[0] as CSSStyleRule).style.removeProperty('display'); (sheet2.cssRules[0] as CSSStyleRule).style.setProperty( 'font-size', 'medium', 'important', ); sheet2.insertRule('h2 { color: red; }'); }, 10); setTimeout(() => { sheet.insertRule('body { border: 2px solid blue; }', 1); sheet2.deleteRule(0); }, 15); setTimeout(() => { resolve(undefined); }, 20); }); }); await waitForRAF(ctx.page); await assertSnapshot(ctx.events); }); it('captures adopted stylesheets in nested shadow doms and iframes', async () => { await ctx.page.evaluate(() => { document.body.innerHTML = `
entry
`; let shadowHost = document.querySelector('div')!; shadowHost!.attachShadow({ mode: 'open' }); let iframeDocument: Document; const NestedDepth = 4; // construct nested shadow doms and iframe elements for (let i = 1; i <= NestedDepth; i++) { const shadowRoot = shadowHost.shadowRoot!; const iframeElement = document.createElement('iframe'); shadowRoot.appendChild(iframeElement); iframeElement.id = `iframe-${i}`; iframeDocument = iframeElement.contentDocument!; shadowHost = iframeDocument.createElement('div'); shadowHost.id = `shadow-host-${i + 1}`; iframeDocument.body.append(shadowHost); shadowHost!.attachShadow({ mode: 'open' }); } const iframeWin = iframeDocument!.defaultView!; const sheet1 = new iframeWin.CSSStyleSheet(); sheet1.replaceSync!('h1 {color: blue;}'); iframeDocument!.adoptedStyleSheets = [sheet1]; const sheet2 = new iframeWin.CSSStyleSheet(); sheet2.replaceSync!('div {font-size: large;}'); shadowHost.shadowRoot!.adoptedStyleSheets = [sheet2]; const { rrweb, emit } = window as unknown as IWindow; rrweb.record({ emit, }); setTimeout(() => { sheet1.insertRule!('div { display: inline ; }', 1); sheet2.replaceSync!('h1 { font-size: large; }'); }, 100); setTimeout(() => { const sheet3 = new iframeWin.CSSStyleSheet(); sheet3.replaceSync!('span {background-color: red;}'); iframeDocument!.adoptedStyleSheets = [sheet3, sheet2]; shadowHost.shadowRoot!.adoptedStyleSheets = [sheet1, sheet3]; }, 150); }); await ctx.page.waitForTimeout(200); await assertSnapshot(ctx.events); }); it('captures adopted stylesheets of shadow doms in checkout full snapshot', async () => { await ctx.page.evaluate(() => { return new Promise((resolve) => { document.body.innerHTML = `
entry
`; let shadowHost = document.querySelector('div')!; shadowHost!.attachShadow({ mode: 'open' }); const sheet = new CSSStyleSheet(); sheet.replaceSync!('h1 {color: blue;}'); shadowHost.shadowRoot!.adoptedStyleSheets = [sheet]; const { rrweb, emit } = window as unknown as IWindow; rrweb.record({ emit, }); setTimeout(() => { // When a full snapshot is checked out manually, all adoptedStylesheets should also be captured. rrweb.record.takeFullSnapshot(true); resolve(undefined); }, 10); }); }); await waitForRAF(ctx.page); await assertSnapshot(ctx.events); }); it('captures stylesheets in iframes with `blob:` url', async () => { await ctx.page.evaluate(() => { const iframe = document.createElement('iframe'); iframe.setAttribute('src', 'about:blank'); document.body.appendChild(iframe); const linkEl = document.createElement('link'); linkEl.setAttribute('rel', 'stylesheet'); linkEl.setAttribute( 'href', URL.createObjectURL( new Blob(['body { color: pink; }'], { type: 'text/css', }), ), ); const iframeDoc = iframe.contentDocument!; iframeDoc.head.appendChild(linkEl); }); await waitForRAF(ctx.page); await ctx.page.evaluate(() => { const { record } = (window as unknown as IWindow).rrweb; record({ inlineStylesheet: true, emit: (window as unknown as IWindow).emit, }); }); await waitForRAF(ctx.page); await assertSnapshot(ctx.events); }); it('aggregates mutations', async () => { await ctx.page.evaluate(() => { return new Promise((resolve) => { const { record, freezePage } = (window as unknown as IWindow).rrweb; record({ emit: (window as unknown as IWindow).emit, }); freezePage(); setTimeout(() => { const div = document.createElement('div'); div.setAttribute('id', 'here-and-gone'); document.body.appendChild(div); }, 0); setTimeout(() => { const div = document.getElementById('here-and-gone'); if (div) { div.setAttribute('data-test', 'x'); } }, 10); setTimeout(() => { const div = document.getElementById('here-and-gone'); if (div) { div.parentNode?.removeChild(div as HTMLElement); } }, 15); setTimeout(() => { // 'unfreeze' happens upon a user event // however, we expect none of the above mutations to produce any effect document.body.click(); }, 20); setTimeout(() => { resolve(null); }, 25); }); }); await waitForRAF(ctx.page); // wait till events get sent const mutationEvents = ctx.events.filter( (e) => e.type === EventType.IncrementalSnapshot && e.data.source === IncrementalSource.Mutation, ); expect(mutationEvents.length).toEqual(0); // there was no aggregate effect await assertSnapshot(ctx.events); }); it('no need for attribute mutations on adds', async () => { await ctx.page.evaluate(() => { const { record, freezePage } = (window as unknown as IWindow).rrweb; record({ emit: (window as unknown as IWindow).emit, }); freezePage(); setTimeout(() => { const div = document.createElement('div'); div.setAttribute('id', 'here'); div.innerText = 'as-created'; div.setAttribute('data-test', 'as-created'); document.body.appendChild(div); }, 0); setTimeout(() => { const div = document.getElementById('here'); if (div) { div.setAttribute('data-test', 'x'); (div.childNodes[0] as Text).replaceData(0, 'as-created'.length, 'y'); } }, 10); setTimeout(() => { // 'unfreeze' happens upon a user event document.body.click(); }, 20); }); await ctx.page.waitForTimeout(50); // wait till setTimeout is called await waitForRAF(ctx.page); // wait till events get sent const mutationEvents = ctx.events.filter( (e) => e.type === EventType.IncrementalSnapshot && e.data.source === IncrementalSource.Mutation, ); expect(mutationEvents.length).toEqual(1); await assertSnapshot(ctx.events); }); describe('loading stylesheets', () => { let server: Server; let serverURL: string; beforeAll(async () => { server = await startServer(); serverURL = getServerURL(server); }); beforeEach(async () => { ctx.page = await ctx.browser.newPage(); await ctx.page.goto(`${serverURL}/html/hello-world.html`); 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())); }); afterAll(async () => { await server.close(); }); it('captures stylesheets that are still loading', async () => { ctx.page.evaluate((serverURL) => { const { record } = (window as unknown as IWindow).rrweb; record({ inlineStylesheet: true, emit: (window as unknown as IWindow).emit, }); const link1 = document.createElement('link'); link1.setAttribute('rel', 'stylesheet'); link1.setAttribute('href', `${serverURL}/html/assets/style.css`); document.head.appendChild(link1); }, serverURL); await ctx.page.waitForResponse(`${serverURL}/html/assets/style.css`); await waitForRAF(ctx.page); await assertSnapshot(ctx.events); }); it('captures stylesheets in iframes that are still loading', async () => { ctx.page.evaluate(() => { const iframe = document.createElement('iframe'); iframe.setAttribute('src', `/html/hello-world.html?2`); document.body.appendChild(iframe); const { record } = (window as unknown as IWindow).rrweb; record({ inlineStylesheet: true, emit: (window as unknown as IWindow).emit, }); }); await ctx.page.waitForResponse(`${serverURL}/html/hello-world.html?2`); await waitForRAF(ctx.page); ctx.page.evaluate(() => { const iframe = document.querySelector('iframe')!; const iframeDoc = iframe.contentDocument!; const linkEl = document.createElement('link'); linkEl.setAttribute('rel', 'stylesheet'); linkEl.setAttribute('href', `/html/assets/style.css`); iframeDoc.head.appendChild(linkEl); }); await ctx.page.waitForResponse(`${serverURL}/html/assets/style.css`); await waitForRAF(ctx.page); await assertSnapshot(ctx.events); }); }); it('captures CORS stylesheets that are still loading', async () => { const corsStylesheetURL = 'https://cdn.jsdelivr.net/npm/pure@2.85.0/index.css'; // do not `await` the following function, otherwise `waitForResponse` _might_ not be called void ctx.page.evaluate((corsStylesheetURL) => { const { record } = (window as unknown as IWindow).rrweb; record({ inlineStylesheet: true, emit: (window as unknown as IWindow).emit, }); const link1 = document.createElement('link'); link1.setAttribute('rel', 'stylesheet'); link1.setAttribute('href', corsStylesheetURL); document.head.appendChild(link1); }, corsStylesheetURL); await ctx.page.waitForResponse(corsStylesheetURL); // wait for stylesheet to be loaded await waitForRAF(ctx.page); // wait for rrweb to emit events await assertSnapshot(ctx.events); }); it('captures adopted stylesheets in shadow doms and iframe', async () => { await ctx.page.evaluate(() => { return new Promise((resolve) => { document.body.innerHTML = `
div in outermost document
`; const sheet = new CSSStyleSheet(); sheet.replaceSync!( 'div { color: yellow; } h2 { color: orange; } h3 { font-size: larger;}', ); // Add stylesheet to a document. document.adoptedStyleSheets = [sheet]; // Add stylesheet to a shadow host. const host = document.querySelector('#shadow-host1'); const shadow = host!.attachShadow({ mode: 'open' }); shadow.innerHTML = '
div in shadow dom 1
span in shadow dom 1'; const sheet2 = new CSSStyleSheet(); sheet2.replaceSync!('span { color: red; }'); shadow.adoptedStyleSheets = [sheet, sheet2]; // Add stylesheet to an IFrame document. const iframe = document.querySelector('iframe'); const sheet3 = new ( iframe!.contentWindow! as IWindow & typeof globalThis ).CSSStyleSheet(); sheet3.replaceSync!('h1 { color: blue; }'); iframe!.contentDocument!.adoptedStyleSheets = [sheet3]; const ele = iframe!.contentDocument!.createElement('h1'); ele.innerText = 'h1 in iframe'; iframe!.contentDocument!.body.appendChild(ele); (window as unknown as IWindow).rrweb.record({ emit: (window.top as unknown as IWindow).emit, }); // Make incremental changes to shadow dom. setTimeout(() => { const host = document.querySelector('#shadow-host2'); const shadow = host!.attachShadow({ mode: 'open' }); shadow.innerHTML = '
div in shadow dom 2
span in shadow dom 2'; const sheet4 = new CSSStyleSheet(); sheet4.replaceSync!('span { color: green; }'); shadow.adoptedStyleSheets = [sheet, sheet4]; document.adoptedStyleSheets = [sheet4, sheet, sheet2]; const sheet5 = new ( iframe!.contentWindow! as IWindow & typeof globalThis ).CSSStyleSheet(); sheet5.replaceSync!('h2 { color: purple; }'); iframe!.contentDocument!.adoptedStyleSheets = [sheet5, sheet3]; }, 10); setTimeout(() => { resolve(null); }, 20); }); }); await waitForRAF(ctx.page); // wait till events get sent await assertSnapshot(ctx.events); }); it('does not throw error when stopping recording after iframe becomes cross-origin', async () => { await ctx.page.evaluate(async () => { const { record } = (window as unknown as IWindow).rrweb; const stopRecord = record({ emit: (window as unknown as IWindow).emit, }); const iframe = document.createElement('iframe'); (window as any).stopRecord = stopRecord; (window as any).iframe = iframe; document.body.appendChild(iframe); }); await waitForRAF(ctx.page); await ctx.page.evaluate(async () => { (window as any).iframe.src = 'https://www.example.com'; // Change the same origin iframe to a cross origin iframe after it's recorded }); await waitForRAF(ctx.page); await ctx.page.evaluate(() => { (window as any).stopRecord?.(); }); }); }); describe('record iframes', function (this: ISuite) { vi.setConfig({ testTimeout: 10_000 }); const ctx: ISuite = setup.call( this, ` " `; 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]: picture-blob.html 1`] = ` " \\"This " `; exports[`integration tests > [html file]: picture-blob-in-frame.html 1`] = ` " " `; exports[`integration tests > [html file]: picture-in-frame.html 1`] = ` " " `; exports[`integration tests > [html file]: picture-with-inline-onload.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[`integration tests > should be able to record elements even when .childNodes has been monkey patched 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\\": \\"Document\\", \\"id\\": 11 } ], \\"id\\": 10 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 12 }, { \\"type\\": 2, \\"tagName\\": \\"script\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", \\"id\\": 14 } ], \\"id\\": 13 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 15 } ], \\"id\\": 4 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 16 }, { \\"type\\": 2, \\"tagName\\": \\"body\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 18 }, { \\"type\\": 2, \\"tagName\\": \\"ul\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 20 }, { \\"type\\": 2, \\"tagName\\": \\"li\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"a\\", \\"id\\": 22 } ], \\"id\\": 21 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 23 }, { \\"type\\": 2, \\"tagName\\": \\"li\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"b\\", \\"id\\": 25 } ], \\"id\\": 24 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 26 }, { \\"type\\": 2, \\"tagName\\": \\"li\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"c\\", \\"id\\": 28 } ], \\"id\\": 27 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 29 }, { \\"type\\": 2, \\"tagName\\": \\"li\\", \\"attributes\\": {}, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"d\\", \\"id\\": 31 } ], \\"id\\": 30 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", \\"id\\": 32 } ], \\"id\\": 19 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n\\\\n\\", \\"id\\": 33 } ], \\"id\\": 17 } ], \\"id\\": 3 } ], \\"id\\": 1 }" `; 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\\": { \\"_cssText\\": \\":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; }\\" }, \\"childNodes\\": [ { \\"type\\": 3, \\"textContent\\": \\"\\", \\"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 } ], \\"isCustom\\": 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 }" `; ================================================ FILE: packages/rrweb-snapshot/test/alt-css/alt-style.css ================================================ body { margin: 0; background: url('../should-be-in-root-folder.jpg'); border-image: url('data:image/svg+xml;utf8,'); } p { color: red; background: url('./should-be-in-alt-css-folder.jpg'); } body > p { color: yellow; } ================================================ FILE: packages/rrweb-snapshot/test/css/benchmark.css ================================================ /*!----------------------------------------------------------- * 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%} ================================================ FILE: packages/rrweb-snapshot/test/css/style-with-import.css ================================================ @import '//fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&family=Roboto:wght@100;300;400;500;700&display=swap"'; @import './style.css'; @import '../alt-css/alt-style.css'; ================================================ FILE: packages/rrweb-snapshot/test/css/style.css ================================================ body { margin: 0; background: url('../should-be-in-root-folder.jpg'); border-image: url('data:image/svg+xml;utf8,'); } p { color: red; background: url('./should-be-in-css-folder.jpg'); } body > p { color: yellow; } ================================================ FILE: packages/rrweb-snapshot/test/css.test.ts ================================================ /** * @vitest-environment jsdom */ import { describe, it, beforeEach, expect } from 'vitest'; import { mediaSelectorPlugin, pseudoClassPlugin } from '../src/css'; import postcss, { type AcceptedPlugin } from 'postcss'; import { JSDOM } from 'jsdom'; import { splitCssText, stringifyStylesheet } from './../src/utils'; import { applyCssSplits } from './../src/rebuild'; import * as fs from 'fs'; import * as path from 'path'; import type { serializedElementNodeWithId, BuildCache, textNode, } from '../src/types'; import { NodeType } from '@rrweb/types'; import { Window } from 'happy-dom'; describe('css parser', () => { function parse(plugin: AcceptedPlugin, input: string): string { const ast = postcss([plugin]).process(input, {}); return ast.css; } describe('mediaSelectorPlugin', () => { it('selectors without device remain unchanged', () => { const cssText = '@media only screen and (min-width: 1200px) { .a { width: 10px; }}'; expect(parse(mediaSelectorPlugin, cssText)).toEqual(cssText); }); it('can adapt media rules to replay context', () => { [ ['min', 'width'], ['min', 'height'], ['max', 'width'], ['max', 'height'], ].forEach(([first, second]) => { expect( parse( mediaSelectorPlugin, `@media only screen and (${first}-device-${second}: 1200px) { .a { width: 10px; }}`, ), ).toEqual( `@media only screen and (${first}-${second}: 1200px) { .a { width: 10px; }}`, ); }); }); }); describe('pseudoClassPlugin', () => { it('parses nested commas in selectors correctly', () => { const cssText = 'body > ul :is(li:not(:first-of-type) a.current, li:not(:first-of-type).active a) {background: red;}'; expect(parse(pseudoClassPlugin, cssText)).toEqual(cssText); }); it("doesn't ignore :hover within :is brackets", () => { const cssText = 'body > ul :is(li:not(:first-of-type) a:hover, li:not(:first-of-type).active a) {background: red;}'; expect(parse(pseudoClassPlugin, cssText)) .toEqual(`body > ul :is(li:not(:first-of-type) a:hover, li:not(:first-of-type).active a), body > ul :is(li:not(:first-of-type) a.\\:hover, li:not(:first-of-type).active a) {background: red;}`); }); it('should parse selector with comma nested inside ()', () => { const cssText = '[_nghost-ng-c4172599085]:not(.fit-content).aim-select:hover:not(:disabled, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--disabled, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--invalid, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--active) { border-color: rgb(84, 84, 84); }'; expect(parse(pseudoClassPlugin, cssText)) .toEqual(`[_nghost-ng-c4172599085]:not(.fit-content).aim-select:hover:not(:disabled, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--disabled, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--invalid, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--active), [_nghost-ng-c4172599085]:not(.fit-content).aim-select.\\:hover:not(:disabled, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--disabled, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--invalid, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--active) { border-color: rgb(84, 84, 84); }`); }); it('ignores ( in strings', () => { const cssText = 'li[attr="weirdly("] a:hover, li[attr="weirdly)"] a {background-color: red;}'; expect(parse(pseudoClassPlugin, cssText)) .toEqual(`li[attr="weirdly("] a:hover, li[attr="weirdly)"] a, li[attr="weirdly("] a.\\:hover {background-color: red;}`); }); it('ignores escaping in strings', () => { const cssText = `li[attr="weirder\\"("] a:hover, li[attr="weirder\\")"] a {background-color: red;}`; expect(parse(pseudoClassPlugin, cssText)) .toEqual(`li[attr="weirder\\"("] a:hover, li[attr="weirder\\")"] a, li[attr="weirder\\"("] a.\\:hover {background-color: red;}`); }); it('ignores comma in string', () => { const cssText = 'li[attr="has,comma"] a:hover {background: red;}'; expect(parse(pseudoClassPlugin, cssText)).toEqual( `li[attr="has,comma"] a:hover, li[attr="has,comma"] a.\\:hover {background: red;}`, ); }); }); }); describe('css splitter', () => { it('finds css textElement splits correctly', () => { const window = new Window({ url: 'https://localhost:8080' }); const document = window.document; document.head.innerHTML = ''; const style = document.querySelector('style'); if (style) { // as authored, e.g. no spaces style.append('.a{background-color:black;}'); // test how normalization finds the right sections style.append('.b {background-color:black;}'); style.append('.c{ background-color: black}'); // how it is currently stringified (spaces present) const expected = [ '.a { background-color: red; }', '.a { background-color: black; }', '.b { background-color: black; }', '.c { background-color: black; }', ]; const browserSheet = expected.join(''); expect(stringifyStylesheet(style.sheet!)).toEqual(browserSheet); expect(splitCssText(browserSheet, style)).toEqual(expected); } }); it('finds css textElement splits correctly when comments are present', () => { const window = new Window({ url: 'https://localhost:8080' }); const document = window.document; // as authored, with comment, missing semicolons document.head.innerHTML = ''; const style = document.querySelector('style'); if (style) { style.append('/* author comment */.a{color:red}.b{color:green}'); // how it is currently stringified (spaces present) const expected = [ '.a { color: red; } .b { color: blue; }', '.a { color: red; } .b { color: green; }', ]; const browserSheet = expected.join(''); expect(splitCssText(browserSheet, style)).toEqual(expected); } }); it('finds css textElement splits correctly with two identical text nodes', () => { const window = new Window({ url: 'https://localhost:8080' }); const document = window.document; // as authored, with comment, missing semicolons const textContent = '.a { color:red; } .b { color:blue; }'; document.head.innerHTML = ''; const style = document.querySelector('style'); if (style) { style.append(textContent); style.append(textContent); const expected = [textContent, textContent]; const browserSheet = expected.join(''); expect(splitCssText(browserSheet, style)).toEqual(expected); style.append(textContent); const expected3 = [textContent, textContent, textContent]; const browserSheet3 = expected3.join(''); expect(splitCssText(browserSheet3, style)).toEqual(expected3); } }); it('finds css textElement splits correctly when vendor prefixed rules have been removed', () => { const style = JSDOM.fragment(``).querySelector('style'); if (style) { // as authored, with newlines style.appendChild( JSDOM.fragment(`.x { -webkit-transition: all 4s ease; content: 'try to keep a newline'; transition: all 4s ease; }`), ); style.appendChild( JSDOM.fragment(`.y { -moz-transition: all 5s ease; transition: all 5s ease; }`), ); // browser .rules would usually omit the vendored versions and modifies the transition value const expected = [ '.x { content: "try to keep a newline"; background: red; transition: 4s; }', '.y { transition: 5s; }', ]; const browserSheet = expected.join(''); // can't do this as JSDOM doesn't have style.sheet // also happy-dom doesn't strip out vendor-prefixed rules like a real browser does //expect(stringifyStylesheet(style.sheet!)).toEqual(browserSheet); expect(splitCssText(browserSheet, style)).toEqual(expected); } }); it('efficiently finds split points in large files', () => { const cssText = fs.readFileSync( path.resolve(__dirname, './css/benchmark.css'), 'utf8', ); const parts = cssText.split('}'); const sections = []; for (let i = 0; i < parts.length - 1; i++) { if (i % 100 === 0) { sections.push(parts[i] + '}'); } else { sections[sections.length - 1] += parts[i] + '}'; } } sections[sections.length - 1] += parts[parts.length - 1]; expect(cssText.length).toEqual(sections.join('').length); const style = JSDOM.fragment(``).querySelector('style'); if (style) { sections.forEach((section) => { style.appendChild(JSDOM.fragment(section)); }); } expect(splitCssText(cssText, style)).toEqual(sections); }); it('finds css textElement splits correctly, with substring matching going from many to none', () => { const window = new Window({ url: 'https://localhost:8080' }); const document = window.document; document.head.innerHTML = ``; const style = document.querySelector('style'); if (style) { // happydom? bug avoid: strangely a greater than symbol in the template string below // e.g. '.prose > :last-child' causes more than one child to be appended style.append(`ass~="not-prose"] *)) { margin-top: 0; /* cssRules transforms this to '0px' which was preventing matching prior to normalization */ } .section-news-v3-detail .news-cnt-wrapper .plugins-wrapper2 :where(.prose :last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { margin-bottom: 0; } .section-news-v3-detail .news-cnt-wrapper .plugins-wrapper2 { width: 100%; overflow-wrap: break-word; } .section-home { height: 100%; overflow-y: auto; } `); expect(style.childNodes.length).toEqual(2); const expected = [ '.section-news-v3-detail .news-cnt-wrapper :where(p):not(:where([class~="not-prose"], [class~="not-prose"] *)) { margin-top: 0px; margin-bottom: 0px; }.section-news-v3-detail .news-cnt-wrapper .plugins-wrapper2 :where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)) { margin-top: 2em; margin-bottom: 2em; }.section-news-v3-detail .news-cnt-wrapper .plugins-wrapper2 :where(.prose > :first-child):not(:where([class~="not-prose"],[cl', 'ass~="not-prose"] *)) { margin-top: 0px; }.section-news-v3-detail .news-cnt-wrapper .plugins-wrapper2 :where(.prose :last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { margin-bottom: 0px; }.section-news-v3-detail .news-cnt-wrapper .plugins-wrapper2 { width: 100%; overflow-wrap: break-word; }.section-home { height: 100%; overflow-y: auto; }', ]; const browserSheet = expected.join(''); expect(stringifyStylesheet(style.sheet!)).toEqual(browserSheet); let _testNoPxNorm = true; // trigger the original motivating scenario for this test expect(splitCssText(browserSheet, style, _testNoPxNorm)).toEqual( expected, ); _testNoPxNorm = false; // this case should also be solved by normalizing '0px' -> '0' expect(splitCssText(browserSheet, style, _testNoPxNorm)).toEqual( expected, ); } }); it('finds css textElement splits correctly, even with repeated sections', () => { const window = new Window({ url: 'https://localhost:8080' }); const document = window.document; document.head.innerHTML = ''; const style = document.querySelector('style'); if (style) { style.append('.x{background-color:red;}'); style.append('.b {background-color:black;}'); style.append('.x{background-color:red;}'); style.append('.c{ background-color: black}'); const expected = [ '.a { background-color: black; }', '.x { background-color: red; }', '.b { background-color: black; }', '.x { background-color: red; }', '.c { background-color: black; }', ]; const browserSheet = expected.join(''); expect(stringifyStylesheet(style.sheet!)).toEqual(browserSheet); expect(splitCssText(browserSheet, style)).toEqual(expected); } }); }); describe('applyCssSplits css rejoiner', function () { const mockLastUnusedArg = null as unknown as BuildCache; const halfCssText = '.a { background-color: red; }'; const otherHalfCssText = halfCssText.replace('.a', '.x'); const markedCssText = [halfCssText, otherHalfCssText].join('/* rr_split */'); let sn: serializedElementNodeWithId; beforeEach(() => { sn = { type: NodeType.Element, tagName: 'style', childNodes: [ { type: NodeType.Text, textContent: '', }, { type: NodeType.Text, textContent: '', }, ], } as serializedElementNodeWithId; }); it('applies css splits correctly', () => { // happy path applyCssSplits(sn, markedCssText, false, mockLastUnusedArg); expect((sn.childNodes[0] as textNode).textContent).toEqual(halfCssText); expect((sn.childNodes[1] as textNode).textContent).toEqual( otherHalfCssText, ); }); it('applies css splits correctly even when there are too many child nodes', () => { let sn3 = { type: NodeType.Element, tagName: 'style', childNodes: [ { type: NodeType.Text, textContent: '', }, { type: NodeType.Text, textContent: '', }, { type: NodeType.Text, textContent: '', }, ], } as serializedElementNodeWithId; applyCssSplits(sn3, markedCssText, false, mockLastUnusedArg); expect((sn3.childNodes[0] as textNode).textContent).toEqual(halfCssText); expect((sn3.childNodes[1] as textNode).textContent).toEqual( otherHalfCssText, ); expect((sn3.childNodes[2] as textNode).textContent).toEqual(''); }); it('applies css splits correctly when split parts are invalid by themselves', () => { const badFirstHalf = 'a:hov'; const badSecondHalf = 'er { color: red; }'; const markedCssText = [badFirstHalf, badSecondHalf].join('/* rr_split */'); applyCssSplits(sn, markedCssText, true, mockLastUnusedArg); expect( (sn.childNodes[0] as textNode).textContent + (sn.childNodes[1] as textNode).textContent, ).toEqual('a:hover,\na.\\:hover { color: red; }'); }); it('applies css splits correctly when split parts are invalid by themselves x3', () => { let sn3 = { type: NodeType.Element, tagName: 'style', childNodes: [ { type: NodeType.Text, textContent: '', }, { type: NodeType.Text, textContent: '', }, { type: NodeType.Text, textContent: '', }, ], } as serializedElementNodeWithId; const badStartThird = '.a:hover { background-color'; const badMidThird = ': red; } input:hover {'; const badEndThird = 'border: 1px solid purple; }'; const markedCssText = [badStartThird, badMidThird, badEndThird].join( '/* rr_split */', ); applyCssSplits(sn3, markedCssText, true, mockLastUnusedArg); expect((sn3.childNodes[0] as textNode).textContent).toEqual( badStartThird.replace('.a:hover', '.a:hover,\n.a.\\:hover'), ); expect((sn3.childNodes[1] as textNode).textContent).toEqual( badMidThird.replace('input:hover', 'input:hover,\ninput.\\:hover'), ); expect((sn3.childNodes[2] as textNode).textContent).toEqual(badEndThird); }); it('maintains entire css text when there are too few child nodes', () => { let sn1 = { type: NodeType.Element, tagName: 'style', childNodes: [ { type: NodeType.Text, textContent: '', }, ], } as serializedElementNodeWithId; applyCssSplits(sn1, markedCssText, false, mockLastUnusedArg); expect((sn1.childNodes[0] as textNode).textContent).toEqual( halfCssText + otherHalfCssText, ); }); }); ================================================ FILE: packages/rrweb-snapshot/test/html/about-mozilla.html ================================================ 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)

================================================ FILE: packages/rrweb-snapshot/test/html/background-clip-text.html ================================================ Document

The background is clipped to the foreground text.

================================================ FILE: packages/rrweb-snapshot/test/html/basic.html ================================================ Document

Title

================================================ FILE: packages/rrweb-snapshot/test/html/block-element.html ================================================ Document
block 1
record 2
block 3
block 3
================================================ FILE: packages/rrweb-snapshot/test/html/compat-mode.html ================================================ Compat Mode; image resizing

================================================ FILE: packages/rrweb-snapshot/test/html/cors-style-sheet.html ================================================ with style sheet ================================================ FILE: packages/rrweb-snapshot/test/html/dialog.html ================================================ I'm a dialog ================================================ FILE: packages/rrweb-snapshot/test/html/dynamic-stylesheet.html ================================================ dynamic stylesheet

p tag

================================================ FILE: packages/rrweb-snapshot/test/html/form-fields.html ================================================ form fields
================================================ FILE: packages/rrweb-snapshot/test/html/hover.html ================================================ hover selector
hover me
================================================ FILE: packages/rrweb-snapshot/test/html/iframe-inner.html ================================================ ================================================ FILE: packages/rrweb-snapshot/test/html/iframe.html ================================================ iframe ================================================ FILE: packages/rrweb-snapshot/test/html/invalid-attribute.html ================================================ ================================================ FILE: packages/rrweb-snapshot/test/html/invalid-doctype.html ================================================ Invalid Doctype ================================================ FILE: packages/rrweb-snapshot/test/html/invalid-tagname.html ================================================ Document Hello Hello ================================================ FILE: packages/rrweb-snapshot/test/html/mask-text.html ================================================ Document

mask 1

mask 2
mask 3
================================================ FILE: packages/rrweb-snapshot/test/html/monkey-patched-elements.html ================================================ Document
  • a
  • b
  • c
  • d
================================================ FILE: packages/rrweb-snapshot/test/html/picture-blob-in-frame.html ================================================ ================================================ FILE: packages/rrweb-snapshot/test/html/picture-blob.html ================================================ This is a robot ================================================ FILE: packages/rrweb-snapshot/test/html/picture-in-frame.html ================================================ ================================================ FILE: packages/rrweb-snapshot/test/html/picture-with-inline-onload.html ================================================ This is a robot ================================================ FILE: packages/rrweb-snapshot/test/html/picture.html ================================================ This is a robot ================================================ FILE: packages/rrweb-snapshot/test/html/preload.html ================================================ Document ================================================ FILE: packages/rrweb-snapshot/test/html/shadow-dom.html ================================================ shadow DOM
content panel 1
content panel 2
content panel 3
================================================ FILE: packages/rrweb-snapshot/test/html/svg.html ================================================ IcoMoon - SVG Icons

Grid Size: 0

Icon-behance
Icon-linkedin
================================================ FILE: packages/rrweb-snapshot/test/html/video.html ================================================ video ================================================ FILE: packages/rrweb-snapshot/test/html/with-relative-res.html ================================================ Document Hello Hello Hello ================================================ FILE: packages/rrweb-snapshot/test/html/with-script.html ================================================ with script ================================================ FILE: packages/rrweb-snapshot/test/html/with-style-sheet-with-import.html ================================================ with style sheet with import ================================================ FILE: packages/rrweb-snapshot/test/html/with-style-sheet.html ================================================ with style sheet ================================================ FILE: packages/rrweb-snapshot/test/iframe-html/frame1.html ================================================ Frame 1 frame 1 ================================================ FILE: packages/rrweb-snapshot/test/iframe-html/frame2.html ================================================ Frame 2 frame 2 ================================================ FILE: packages/rrweb-snapshot/test/iframe-html/main.html ================================================ Main ================================================ FILE: packages/rrweb-snapshot/test/integration.test.ts ================================================ import * as fs from 'fs'; import * as http from 'http'; import * as path from 'path'; import * as puppeteer from 'puppeteer'; import * as url from 'url'; import { afterAll, assert, beforeAll, beforeEach, describe, expect, it, vi, } from 'vitest'; import { getServerURL, waitForRAF } from './utils'; 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 = (defaultPort: number = 3030) => 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(defaultPort) .on('listening', () => { resolve(s); }) .on('error', (e) => { s.listen().on('listening', () => { resolve(s); }); }); }); function sanitizeSnapshot(snapshot: string): string { return snapshot.replace(/localhost:[0-9]+/g, 'localhost:3030'); } async function snapshot(page: puppeteer.Page, code: string): Promise { await waitForRAF(page); const result = (await page.evaluate(`${code} const snapshot = rrwebSnapshot.snapshot(document); JSON.stringify(snapshot, null, 2); `)) as string; return result; } function assertSnapshot(snapshot: string): void { expect(sanitizeSnapshot(snapshot)).toMatchSnapshot(); } interface ISuite { server: http.Server; serverURL: string; browser: puppeteer.Browser; page: puppeteer.Page; code: string; } describe('integration tests', function (this: ISuite) { vi.setConfig({ testTimeout: 30_000 }); let server: ISuite['server']; let serverURL: ISuite['serverURL']; let browser: ISuite['browser']; let code: ISuite['code']; beforeAll(async () => { server = await startServer(); serverURL = getServerURL(server); browser = await puppeteer.launch({ // headless: false, args: ['--no-sandbox', '--disable-setuid-sandbox'], }); code = fs.readFileSync( path.resolve(__dirname, '../dist/rrweb-snapshot.umd.cjs'), 'utf-8', ); }); afterAll(async () => { await browser.close(); await server.close(); }); for (const html of htmls) { if (html.filePath.substring(html.filePath.length - 1) === '~') { continue; } // monkey patching breaks rebuild code if (html.filePath.includes('monkey-patched-elements.html')) continue; const title = '[html file]: ' + html.filePath; it(title, async () => { const page: puppeteer.Page = await browser.newPage(); // console for debug 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(`${serverURL}/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(`${serverURL}/html`); await page.setContent(html.src, { waitUntil: 'load', }); } await waitForRAF(page); const rebuildHtml = ( (await page.evaluate(`${code} const x = new XMLSerializer(); const snap = rrwebSnapshot.snapshot(document); let out = x.serializeToString(rrwebSnapshot.rebuild(snap, { doc: document })); 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 `)) as string ) .replace(/\n\n/g, '') .replace( /blob:http:\/\/localhost:\d+\/[0-9a-z\-]+/, 'blob:http://localhost:xxxx/...', ); await assertSnapshot(rebuildHtml); }); } it('correctly triggers backCompat mode and rendering', async () => { const page: puppeteer.Page = await browser.newPage(); // console for debug page.on('console', (msg) => console.log(msg.text())); await page.goto(`${serverURL}/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', )) as number; // 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 = rrwebSnapshot.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 = rrwebSnapshot.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(`${serverURL}/html/picture.html`, { waitUntil: 'load', }); await page.waitForSelector('img', { timeout: 1000 }); await page.evaluate(`${code} var snapshot = rrwebSnapshot.snapshot(document, { dataURLOptions: { type: "image/webp", quality: 0.8 }, inlineImages: true, inlineStylesheet: false })`); // don't wait, as we want to ensure that the same-origin image can be inlined immediately const bodyChildren = (await page.evaluate(` snapshot.childNodes[0].childNodes[1].childNodes.filter((cn) => cn.type === 2); `)) as any[]; expect(bodyChildren[1]).toEqual( expect.objectContaining({ tagName: 'img', attributes: { src: expect.stringMatching(/images\/robot.png$/), alt: 'This is a robot', rr_dataURL: expect.stringMatching(/^data:image\/webp;base64,/), }, }), ); }); it('correctly saves cross-origin images offline', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank', { waitUntil: 'load', }); await page.setContent( ` CORS restricted but has access-control-allow-origin: * `, { waitUntil: 'load', }, ); await page.waitForSelector('img', { timeout: 1000 }); await page.evaluate(`${code}var snapshot = rrwebSnapshot.snapshot(document, { dataURLOptions: { type: "image/webp", quality: 0.8 }, inlineImages: true, inlineStylesheet: false })`); await waitForRAF(page); // need a small wait, as after the crossOrigin="anonymous" change, the snapshot triggers a reload of the image (after which, the snapshot is mutated) const bodyChildren = (await page.evaluate(` snapshot.childNodes[0].childNodes[1].childNodes.filter((cn) => cn.type === 2); `)) as any[]; expect(bodyChildren[0]).toEqual( expect.objectContaining({ tagName: 'img', attributes: { src: getServerURL(server) + '/images/rrweb-favicon-20x20.png', alt: 'CORS restricted but has access-control-allow-origin: *', rr_dataURL: expect.stringMatching(/^data:image\/webp;base64,/), }, }), ); }); it('correctly saves blob:images offline', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto(`${serverURL}/html/picture-blob.html`, { waitUntil: 'load', }); await page.waitForSelector('img', { timeout: 1000 }); await page.evaluate(`${code}var snapshot = rrwebSnapshot.snapshot(document, { dataURLOptions: { type: "image/webp", quality: 0.8 }, inlineImages: true, inlineStylesheet: false })`); await waitForRAF(page); const snapshot = (await page.evaluate( 'JSON.stringify(snapshot, null, 2);', )) as string; assert(snapshot.includes('"rr_dataURL"')); assert(snapshot.includes('data:image/webp;base64,')); }); it('correctly saves images in iframes offline', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto(`${serverURL}/html/picture-in-frame.html`, { waitUntil: 'load', }); await page.waitForSelector('iframe', { timeout: 1000 }); await waitForRAF(page); // wait for page to render await page.evaluate(`${code} rrwebSnapshot.snapshot(document, { dataURLOptions: { type: "image/webp", quality: 0.8 }, inlineImages: true, inlineStylesheet: false, onIframeLoad: function(iframe, sn) { window.snapshot = sn; } })`); await waitForRAF(page); const snapshot = (await page.evaluate( 'JSON.stringify(window.snapshot, null, 2);', )) as string; assert(snapshot.includes('"rr_dataURL"')); assert(snapshot.includes('data:image/webp;base64,')); }); it('correctly saves blob:images in iframes offline', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto(`${serverURL}/html/picture-blob-in-frame.html`, { waitUntil: 'load', }); await page.waitForSelector('iframe', { timeout: 1000 }); await waitForRAF(page); // wait for page to render await page.evaluate(`${code} rrwebSnapshot.snapshot(document, { dataURLOptions: { type: "image/webp", quality: 0.8 }, inlineImages: true, inlineStylesheet: false, onIframeLoad: function(iframe, sn) { window.snapshot = sn; } })`); await waitForRAF(page); const snapshot = (await page.evaluate( 'JSON.stringify(window.snapshot, null, 2);', )) as string; assert(snapshot.includes('"rr_dataURL"')); assert(snapshot.includes('data:image/webp;base64,')); }); it('should save background-clip: text; as the more compatible -webkit-background-clip: test;', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto(`${serverURL}/html/background-clip-text.html`, { waitUntil: 'load', }); await waitForRAF(page); // wait for page to render await page.evaluate(`${code} window.snapshot = rrwebSnapshot.snapshot(document, { inlineStylesheet: true, })`); await waitForRAF(page); const snapshot = (await page.evaluate( 'JSON.stringify(window.snapshot, null, 2);', )) as string; assert(snapshot.includes('-webkit-background-clip: text;')); }); it('images with inline onload should work', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto(`${serverURL}/html/picture-with-inline-onload.html`, { waitUntil: 'load', }); await page.waitForSelector('img', { timeout: 2000 }); await page.evaluate(`${code}`); await page.evaluate(` var snapshot = rrwebSnapshot.snapshot(document, { dataURLOptions: { type: "image/webp", quality: 0.8 }, inlineImages: true, inlineStylesheet: false })`); await waitForRAF(page); const fnName = (await page.evaluate( 'document.querySelector("img").onload.name', )) as string; assert(fnName === 'onload'); }); it('should be able to record elements even when .childNodes has been monkey patched', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto(`${serverURL}/html/monkey-patched-elements.html`, { waitUntil: 'load', }); await waitForRAF(page); // wait for page to render const snapshotResult = JSON.stringify( await page.evaluate(`${code}; rrwebSnapshot.snapshot(document); `), null, 2, ); expect(snapshotResult).toMatchSnapshot(); }); }); describe('iframe integration tests', function (this: ISuite) { vi.setConfig({ testTimeout: 30_000 }); let server: ISuite['server']; let serverURL: ISuite['serverURL']; let browser: ISuite['browser']; let code: ISuite['code']; beforeAll(async () => { server = await startServer(); serverURL = getServerURL(server); browser = await puppeteer.launch({ // headless: false, args: ['--no-sandbox', '--disable-setuid-sandbox'], }); code = fs.readFileSync( path.resolve(__dirname, '../dist/rrweb-snapshot.umd.cjs'), 'utf-8', ); }); afterAll(async () => { await browser.close(); await server.close(); }); it('snapshot async iframes', async () => { const page: puppeteer.Page = await browser.newPage(); // console for debug page.on('console', (msg) => console.log(msg.text())); await page.goto(`${serverURL}/iframe-html/main.html`, { waitUntil: 'load', }); const snapshotResult = JSON.stringify( await page.evaluate(`${code}; rrwebSnapshot.snapshot(document); `), null, 2, ); await assertSnapshot(snapshotResult); }); }); describe('dialog integration tests', function (this: ISuite) { vi.setConfig({ testTimeout: 30_000 }); let server: ISuite['server']; let serverURL: ISuite['serverURL']; let browser: ISuite['browser']; let code: ISuite['code']; let page: ISuite['page']; beforeAll(async () => { server = await startServer(); serverURL = getServerURL(server); browser = await puppeteer.launch({ // headless: false, args: ['--no-sandbox', '--disable-setuid-sandbox'], }); code = fs.readFileSync( path.resolve(__dirname, '../dist/rrweb-snapshot.umd.cjs'), 'utf-8', ); }); beforeEach(async () => { page = await browser.newPage(); page.on('console', (msg) => console.log(msg.text())); await page.goto(`${serverURL}/html/dialog.html`, { waitUntil: 'load', }); }); afterAll(async () => { await browser.close(); await server.close(); }); it('should capture open attribute for non modal dialogs', async () => { page.evaluate('document.querySelector("dialog").show()'); const snapshotResult = await snapshot(page, code); assertSnapshot(snapshotResult); }); it('should capture open attribute for modal dialogs', async () => { await page.evaluate('document.querySelector("dialog").showModal()'); const snapshotResult = await snapshot(page, code); assertSnapshot(snapshotResult); }); }); describe('shadow DOM integration tests', function (this: ISuite) { vi.setConfig({ testTimeout: 30_000 }); let server: ISuite['server']; let serverURL: ISuite['serverURL']; let browser: ISuite['browser']; let code: ISuite['code']; beforeAll(async () => { server = await startServer(); serverURL = getServerURL(server); browser = await puppeteer.launch({ // headless: false, args: ['--no-sandbox', '--disable-setuid-sandbox'], }); code = fs.readFileSync( path.resolve(__dirname, '../dist/rrweb-snapshot.umd.cjs'), 'utf-8', ); }); afterAll(async () => { await browser.close(); await server.close(); }); it('snapshot shadow DOM', async () => { const page: puppeteer.Page = await browser.newPage(); // console for debug page.on('console', (msg) => console.log(msg.text())); await page.goto(`${serverURL}/html/shadow-dom.html`, { waitUntil: 'load', }); const snapshotResult = JSON.stringify( await page.evaluate(`${code}; rrwebSnapshot.snapshot(document); `), null, 2, ); await assertSnapshot(snapshotResult); }); }); ================================================ FILE: packages/rrweb-snapshot/test/js/a.js ================================================ var a = 1 + 1; ================================================ FILE: packages/rrweb-snapshot/test/rebuild.test.ts ================================================ /** * @vitest-environment jsdom */ import * as fs from 'fs'; import * as path from 'path'; import { beforeEach, describe, expect as _expect, it, vi } from 'vitest'; import { adaptCssForReplay, buildNodeWithSN, createCache, } from '../src/rebuild'; import { NodeType } from '@rrweb/types'; import { createMirror, Mirror, normalizeCssString } from '../src/utils'; const expect = _expect as unknown as { (actual: T): { toMatchCss(expected: string): void; } & ReturnType; } & typeof _expect; expect.extend({ toMatchCss: function (received: string, expected: string) { const pass = normalizeCssString(received) === normalizeCssString(expected); const message: () => string = () => pass ? '' : `Received (${received}) is not the same as expected (${expected})`; return { message, pass, }; }, }); function getDuration(hrtime: [number, number]) { const [seconds, nanoseconds] = hrtime; return seconds * 1000 + nanoseconds / 1000000; } describe('rebuild', function () { let cache: ReturnType; let mirror: Mirror; beforeEach(() => { mirror = createMirror(); cache = createCache(); }); describe('rr_dataURL', function () { it('should rebuild dataURL', function () { const dataURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; const node = buildNodeWithSN( { id: 1, tagName: 'img', type: NodeType.Element, attributes: { rr_dataURL: dataURI, src: 'http://example.com/image.png', }, childNodes: [], }, { doc: document, mirror, hackCss: false, cache, }, ) as HTMLImageElement; expect(node?.src).toBe(dataURI); }); }); describe('rr_width/rr_height', function () { it('rebuild blocked element with correct dimensions', function () { const node = buildNodeWithSN( { id: 1, tagName: 'svg', type: NodeType.Element, isSVG: true, attributes: { rr_width: '50px', rr_height: '50px', }, childNodes: [], }, { doc: document, mirror, hackCss: false, cache, }, ) as HTMLDivElement; expect(node.style.width).toBe('50px'); expect(node.style.height).toBe('50px'); }); }); describe('shadowDom', function () { it('rebuild shadowRoot without siblings', function () { const node = buildNodeWithSN( { id: 1, tagName: 'div', type: NodeType.Element, attributes: {}, childNodes: [ { id: 2, tagName: 'div', type: NodeType.Element, attributes: {}, childNodes: [], isShadow: true, }, ], isShadowHost: true, }, { doc: document, mirror, hackCss: false, cache, }, ) as HTMLDivElement; expect(node.shadowRoot?.childNodes.length).toBe(1); }); }); describe('add hover class to hover selector related rules', function () { it('will do nothing to css text without :hover', () => { const cssText = 'body { color: white }'; expect(adaptCssForReplay(cssText, cache)).toMatchCss(cssText); }); it('can add hover class to css text', () => { const cssText = '.a:hover { color: white }'; expect(adaptCssForReplay(cssText, cache)).toMatchCss( '.a:hover, .a.\\:hover { color: white }', ); }); it('can correctly add hover when in middle of selector', () => { const cssText = 'ul li a:hover img { color: white }'; expect(adaptCssForReplay(cssText, cache)).toMatchCss( 'ul li a:hover img, ul li a.\\:hover img { color: white }', ); }); it('can correctly add hover on multiline selector', () => { const cssText = `ul li.specified a:hover img, ul li.multiline b:hover img, ul li.specified c:hover img { color: white }`; expect(adaptCssForReplay(cssText, cache)).toMatchCss( `ul li.specified a:hover img, ul li.multiline b:hover img, ul li.specified c:hover img, ul li.specified a.\\:hover img, ul li.multiline b.\\:hover img, ul li.specified c.\\:hover img { color: white }`, ); }); it('can add hover class within media query', () => { const cssText = '@media screen { .m:hover { color: white } }'; expect(adaptCssForReplay(cssText, cache)).toMatchCss( '@media screen { .m:hover, .m.\\:hover { color: white } }', ); }); it('can add hover class when there is multi selector', () => { const cssText = '.a, .b:hover, .c { color: white }'; expect(adaptCssForReplay(cssText, cache)).toMatchCss( '.a, .b:hover, .c, .b.\\:hover { 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(adaptCssForReplay(cssText, cache)).toMatchCss( '.a:hover, .a:hover::after, .a.\\:hover, .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(adaptCssForReplay(cssText, cache)).toMatchCss( '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(adaptCssForReplay(cssText, cache)).toMatchCss( 'a:hover b:hover, a.\\:hover b.\\:hover { color: white }', ); }); it('will ignore :hover in css value', () => { const cssText = '.a::after { content: ":hover" }'; expect(adaptCssForReplay(cssText, cache)).toMatchCss(cssText); }); it('can adapt media rules to replay context', () => { const cssText = '@media only screen and (min-device-width : 1200px) { .a { width: 10px; }}'; expect(adaptCssForReplay(cssText, cache)).toMatchCss( '@media only screen and (min-width : 1200px) { .a { width: 10px; }}', ); }); // this benchmark is unreliable when run in parallel with other tests it.skip('benchmark', () => { const cssText = fs.readFileSync( path.resolve(__dirname, './css/benchmark.css'), 'utf8', ); const start = process.hrtime(); adaptCssForReplay(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(); adaptCssForReplay(cssText, cache); const end = process.hrtime(start); const cachedStart = process.hrtime(); adaptCssForReplay(cssText, cache); const cachedEnd = process.hrtime(cachedStart); expect(getDuration(cachedEnd) * factor).toBeLessThan(getDuration(end)); }); }); it('should not incorrectly interpret escaped quotes', () => { // the ':hover' in the below is a decoy which is not part of the selector, // previously that part was being incorrectly consumed by the selector regex const should_not_modify = ".tailwind :is(.before\\:content-\\[\\'\\'\\])::before { --tw-content: \":hover\"; content: var(--tw-content); }.tailwind :is(.\\[\\&\\>li\\]\\:before\\:content-\\[\\'-\\'\\] > li)::before { color: pink; }"; expect(adaptCssForReplay(should_not_modify, cache)).toMatchCss( should_not_modify, ); }); it('should not incorrectly interpret at rules', () => { // the ':hover' in the below is a decoy which is not part of the selector, const should_not_modify = '@import url("https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,400;0,500;0,700;1,400&display=:hover");'; expect(adaptCssForReplay(should_not_modify, cache)).toMatchCss( should_not_modify, ); }); it('handles exceptions from postcss when calling adaptCssForReplay', () => { const consoleWarnSpy = vi .spyOn(console, 'warn') .mockImplementation(() => {}); // trigger CssSyntaxError by passing invalid css const cssText = 'a{'; adaptCssForReplay(cssText, cache); expect(consoleWarnSpy).toHaveBeenLastCalledWith( 'Failed to adapt css for replay', expect.any(Error), ); }); }); ================================================ FILE: packages/rrweb-snapshot/test/snapshot.test.ts ================================================ /** * @vitest-environment jsdom */ import { JSDOM } from 'jsdom'; import { describe, expect, it } from 'vitest'; import snapshot, { _isBlockedElement, serializeNodeWithId, } from '../src/snapshot'; import { elementNode, serializedNodeWithId } from '../src/types'; import { Mirror, absolutifyURLs } from '../src/utils'; const serializeNode = (node: Node): serializedNodeWithId | null => { return serializeNodeWithId(node, { doc: document, mirror: new Mirror(), blockClass: 'blockblock', blockSelector: null, maskTextClass: 'maskmask', maskTextSelector: null, skipChild: false, inlineStylesheet: true, maskTextFn: undefined, maskInputFn: undefined, slimDOMOptions: {}, }); }; describe('absolute url to stylesheet', () => { const href = 'http://localhost/css/style.css'; it('can handle relative path', () => { expect(absolutifyURLs('url(a.jpg)', href)).toEqual( `url(http://localhost/css/a.jpg)`, ); }); it('can handle same level path', () => { expect(absolutifyURLs('url("./a.jpg")', href)).toEqual( `url("http://localhost/css/a.jpg")`, ); }); it('can handle parent level path', () => { expect(absolutifyURLs('url("../a.jpg")', href)).toEqual( `url("http://localhost/a.jpg")`, ); }); it('can handle absolute path', () => { expect(absolutifyURLs('url("/a.jpg")', href)).toEqual( `url("http://localhost/a.jpg")`, ); }); it('can handle external path', () => { expect(absolutifyURLs('url("http://localhost/a.jpg")', href)).toEqual( `url("http://localhost/a.jpg")`, ); }); it('can handle single quote path', () => { expect(absolutifyURLs(`url('./a.jpg')`, href)).toEqual( `url('http://localhost/css/a.jpg')`, ); }); it('can handle no quote path', () => { expect(absolutifyURLs('url(./a.jpg)', href)).toEqual( `url(http://localhost/css/a.jpg)`, ); }); it('can handle multiple no quote paths', () => { expect( absolutifyURLs( '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(absolutifyURLs('url(data:image/gif;base64,ABC)', href)).toEqual( 'url(data:image/gif;base64,ABC)', ); expect( absolutifyURLs( '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( absolutifyURLs( "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( absolutifyURLs( 'url(\'data:image/svg+xml;utf8,\')', href, ), ).toEqual( 'url(\'data:image/svg+xml;utf8,\')', ); expect( absolutifyURLs( 'url("data:image/svg+xml;utf8,")', href, ), ).toEqual( 'url("data:image/svg+xml;utf8,")', ); }); it('can handle empty path', () => { expect(absolutifyURLs(`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 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)).toMatchObject({ rootId: undefined, attributes: { _cssText: 'section {color: blue;}body {color: red;}', }, type: 2, }); }); it('should serialize all rules on stylesheets with mix of insertion type', () => { const styleEl = render(``); styleEl.sheet?.insertRule('section.lost { color: unseeable; }'); // browser throws this away after append styleEl.append(document.createTextNode('section { color: blue; }')); styleEl.sheet?.insertRule('section.working { color: pink; }'); expect(serializeNode(styleEl)).toMatchObject({ rootId: undefined, attributes: { _cssText: 'section.working {color: pink;}body {color: red;}/* rr_split */section {color: blue;}', }, type: 2, }); }); }); describe('scrollTop/scrollLeft', () => { const render = (html: string): HTMLDivElement => { document.write(html); return document.querySelector('div')!; }; it('should serialize scroll positions', () => { const el = render(`
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
`); el.scrollTop = 10; el.scrollLeft = 20; expect(serializeNode(el)).toMatchObject({ attributes: { rr_scrollTop: 10, rr_scrollLeft: 20, }, }); }); }); describe('form', () => { const render = (html: string): HTMLTextAreaElement => { document.write(html); return document.querySelector('textarea')!; }; it('should record textarea values once', () => { const el = render(``); const sel = serializeNode(el) as elementNode; // we serialize according to where the DOM stores the value, not how // the HTML stores it (this is so that maskInputValue can work over // inputs/textareas/selects in a uniform way) expect(sel).toMatchObject({ attributes: { value: 'Lorem ipsum', }, }); expect(sel?.childNodes).toEqual([]); // shouldn't be stored in childNodes while in transit }); }); describe('jsdom snapshot', () => { const render = (html: string): Document => { document.write(html); return document; }; it("doesn't rely on global browser objects", () => { // this test is incomplete in terms of coverage, // but the idea being that we are checking that all features use the // passed-in `doc` object rather than the global `document` // (which is only present in browsers) // in any case, supporting jsdom is not a primary goal const doc = render(`

Hello world

`); const sn = snapshot(doc, { // JSDOM Error: Not implemented: HTMLCanvasElement.prototype.toDataURL (without installing the canvas npm package) //recordCanvas: true, }); expect(sn).toMatchObject({ type: 0, }); }); }); ================================================ FILE: packages/rrweb-snapshot/test/stringify-stylesheet.bench.ts ================================================ /** * @vitest-environment jsdom */ import { bench } from 'vitest'; import * as fs from 'fs'; import * as path from 'path'; import { stringifyStylesheet } from '../src/utils'; import * as CSSOM from 'cssom'; describe('stringifyStylesheet', () => { let benchmarkStylesheet: CSSStyleSheet; const cssText = fs.readFileSync( path.resolve(__dirname, './css/benchmark.css'), 'utf8', ); benchmarkStylesheet = CSSOM.parse(cssText); benchmarkStylesheet.href = 'https://example.com/style.css'; it.skip('stringify', () => { // written just to ensure it's working const cssText = '.x { background: url(./relative.jpg) }'; const styleSheet = CSSOM.parse(cssText); styleSheet.href = 'https://example.com/style.css'; expect(stringifyStylesheet(styleSheet)).toEqual( 'x {background: url(https://example.com/relative.jpg);}', ); }); bench( 'stringify', () => { stringifyStylesheet(benchmarkStylesheet); }, { time: 1000 }, ); }); ================================================ FILE: packages/rrweb-snapshot/test/utils.test.ts ================================================ /** * @vitest-environment jsdom */ import { describe, it, test, expect } from 'vitest'; import { escapeImportStatement, extractFileExtension, fixSafariColons, isNodeMetaEqual, stringifyStylesheet, } from '../src/utils'; import { NodeType } from '@rrweb/types'; import type { serializedNode, serializedNodeWithId } from '@rrweb/types'; describe('utils', () => { describe('isNodeMetaEqual()', () => { const document1: serializedNode = { type: NodeType.Document, compatMode: 'CSS1Compat', childNodes: [], }; const document2: serializedNode = { type: NodeType.Document, compatMode: 'BackCompat', childNodes: [], }; const documentType1: serializedNode = { type: NodeType.DocumentType, name: 'html', publicId: '', systemId: '', }; const documentType2: serializedNode = { type: NodeType.DocumentType, name: 'html', publicId: '', systemId: 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd', }; const text1: serializedNode = { type: NodeType.Text, textContent: 'Hello World', }; const text2: serializedNode = { type: NodeType.Text, textContent: 'Hello world', }; const comment1: serializedNode = { type: NodeType.Comment, textContent: 'Hello World', }; const comment2: serializedNode = { type: NodeType.Comment, textContent: 'Hello world', }; const element1: serializedNode = { type: NodeType.Element, tagName: 'div', attributes: { className: 'test', }, childNodes: [], }; const element2: serializedNode = { type: NodeType.Element, tagName: 'span', attributes: { 'aria-label': 'Hello World', }, childNodes: [], }; const element3: serializedNode = { type: NodeType.Element, tagName: 'div', attributes: { id: 'test' }, childNodes: [comment1 as serializedNodeWithId], }; it('should return false if two nodes have different node types', () => { expect( isNodeMetaEqual( undefined as unknown as serializedNode, null as unknown as serializedNode, ), ).toBeFalsy(); expect(isNodeMetaEqual(document1, element1)).toBeFalsy(); expect(isNodeMetaEqual(document1, documentType1)).toBeFalsy(); expect(isNodeMetaEqual(documentType1, element1)).toBeFalsy(); expect(isNodeMetaEqual(text1, comment1)).toBeFalsy(); expect(isNodeMetaEqual(text1, element1)).toBeFalsy(); expect(isNodeMetaEqual(comment1, element1)).toBeFalsy(); }); it('should compare meta data of two document nodes', () => { expect( isNodeMetaEqual(document1, JSON.parse(JSON.stringify(document1))), ).toBeTruthy(); expect( isNodeMetaEqual(JSON.parse(JSON.stringify(document2)), document2), ).toBeTruthy(); expect(isNodeMetaEqual(document1, document2)).toBeFalsy(); }); it('should compare meta data of two documentType nodes', () => { expect( isNodeMetaEqual( documentType1, JSON.parse(JSON.stringify(documentType1)), ), ).toBeTruthy(); expect( isNodeMetaEqual( JSON.parse(JSON.stringify(documentType2)), documentType2, ), ).toBeTruthy(); expect(isNodeMetaEqual(documentType1, documentType2)).toBeFalsy(); }); it('should compare meta data of two text nodes', () => { expect( isNodeMetaEqual(text1, JSON.parse(JSON.stringify(text1))), ).toBeTruthy(); expect( isNodeMetaEqual(JSON.parse(JSON.stringify(text2)), text2), ).toBeTruthy(); expect(isNodeMetaEqual(text1, text2)).toBeFalsy(); }); it('should compare meta data of two comment nodes', () => { expect( isNodeMetaEqual(comment1, JSON.parse(JSON.stringify(comment1))), ).toBeTruthy(); expect( isNodeMetaEqual(JSON.parse(JSON.stringify(comment2)), comment2), ).toBeTruthy(); expect(isNodeMetaEqual(comment1, comment2)).toBeFalsy(); }); it('should compare meta data of two HTML elements', () => { expect( isNodeMetaEqual(element1, JSON.parse(JSON.stringify(element1))), ).toBeTruthy(); expect( isNodeMetaEqual(JSON.parse(JSON.stringify(element2)), element2), ).toBeTruthy(); expect( isNodeMetaEqual(element1, { ...element1, childNodes: [comment2 as serializedNodeWithId], }), ).toBeTruthy(); expect(isNodeMetaEqual(element1, element2)).toBeFalsy(); expect(isNodeMetaEqual(element1, element3)).toBeFalsy(); expect(isNodeMetaEqual(element2, element3)).toBeFalsy(); }); }); describe('extractFileExtension', () => { test('absolute path', () => { const path = 'https://example.com/styles/main.css'; const extension = extractFileExtension(path); expect(extension).toBe('css'); }); test('relative path', () => { const path = 'styles/main.css'; const baseURL = 'https://example.com/'; const extension = extractFileExtension(path, baseURL); expect(extension).toBe('css'); }); test('path with search parameters', () => { const path = 'https://example.com/scripts/app.js?version=1.0'; const extension = extractFileExtension(path); expect(extension).toBe('js'); }); test('path with fragment', () => { const path = 'https://example.com/styles/main.css#section1'; const extension = extractFileExtension(path); expect(extension).toBe('css'); }); test('path with search parameters and fragment', () => { const path = 'https://example.com/scripts/app.js?version=1.0#section1'; const extension = extractFileExtension(path); expect(extension).toBe('js'); }); test('path without extension', () => { const path = 'https://example.com/path/to/directory/'; const extension = extractFileExtension(path); expect(extension).toBeNull(); }); test('invalid URL', () => { const path = '!@#$%^&*()'; const baseURL = 'invalid'; const extension = extractFileExtension(path, baseURL); expect(extension).toBeNull(); }); test('path with multiple dots', () => { const path = 'https://example.com/scripts/app.min.js?version=1.0'; const extension = extractFileExtension(path); expect(extension).toBe('js'); }); }); describe('escapeImportStatement', () => { it('parses imports with quotes correctly', () => { const out1 = escapeImportStatement({ cssText: `@import url("/foo.css;900;800"");`, href: '/foo.css;900;800"', media: { length: 0, }, layerName: null, supportsText: null, } as unknown as CSSImportRule); expect(out1).toEqual(`@import url("/foo.css;900;800\\"");`); const out2 = escapeImportStatement({ cssText: `@import url("/foo.css;900;800"") supports(display: flex);`, href: '/foo.css;900;800"', media: { length: 0, }, layerName: null, supportsText: 'display: flex', } as unknown as CSSImportRule); expect(out2).toEqual( `@import url("/foo.css;900;800\\"") supports(display: flex);`, ); const out3 = escapeImportStatement({ cssText: `@import url("/foo.css;900;800"");`, href: '/foo.css;900;800"', media: { length: 1, mediaText: 'print, screen', }, layerName: null, supportsText: null, } as unknown as CSSImportRule); expect(out3).toEqual(`@import url("/foo.css;900;800\\"") print, screen;`); const out4 = escapeImportStatement({ cssText: `@import url("/foo.css;900;800"") layer(layer-1);`, href: '/foo.css;900;800"', media: { length: 0, }, layerName: 'layer-1', supportsText: null, } as unknown as CSSImportRule); expect(out4).toEqual( `@import url("/foo.css;900;800\\"") layer(layer-1);`, ); const out5 = escapeImportStatement({ cssText: `@import url("/foo.css;900;800"") layer;`, href: '/foo.css;900;800"', media: { length: 0, }, layerName: '', supportsText: null, } as unknown as CSSImportRule); expect(out5).toEqual(`@import url("/foo.css;900;800\\"") layer;`); }); }); describe('fixSafariColons', () => { it('parses : in attribute selectors correctly', () => { const out1 = fixSafariColons('[data-foo] { color: red; }'); expect(out1).toEqual('[data-foo] { color: red; }'); const out2 = fixSafariColons('[data-foo:other] { color: red; }'); expect(out2).toEqual('[data-foo\\:other] { color: red; }'); const out3 = fixSafariColons('[data-aa\\:other] { color: red; }'); expect(out3).toEqual('[data-aa\\:other] { color: red; }'); }); }); describe('stringifyStylesheet', () => { it('returns null if rules are missing', () => { const mockSheet = { rules: null, cssRules: null, } as unknown as CSSStyleSheet; expect(stringifyStylesheet(mockSheet)).toBeNull(); }); it('stringifies rules using .cssRules if .rules is missing', () => { const mockRule1 = { cssText: 'div { margin: 0; }' } as CSSRule; const mockSheet = { cssRules: [mockRule1], href: 'https://example.com/main.css', } as unknown as CSSStyleSheet; expect(stringifyStylesheet(mockSheet)).toBe('div { margin: 0; }'); }); it('uses ownerNode.baseURI for inline styles', () => { const mockFontFaceRule = { cssText: ` @font-face { font-family: 'MockFont'; src: url('../fonts/mockfont.woff2') format('woff2'); font-weight: normal; font-style: normal; } `, } as CSSRule; const mockOwnerNode = { baseURI: 'https://example.com/fonts/', } as unknown as Node; const mockSheet = { cssRules: [mockFontFaceRule], href: null, ownerNode: mockOwnerNode, } as unknown as CSSStyleSheet; expect( stringifyStylesheet(mockSheet)?.replace(/\s+/g, ' ').trim(), ).toEqual( "@font-face { font-family: 'MockFont'; src: url('https://example.com/fonts/mockfont.woff2') format('woff2'); font-weight: normal; font-style: normal; }", ); }); }); }); ================================================ FILE: packages/rrweb-snapshot/test/utils.ts ================================================ import * as puppeteer from 'puppeteer'; import * as http from 'http'; export async function waitForRAF(page: puppeteer.Page) { return await page.evaluate(() => { return new Promise((resolve) => { requestAnimationFrame(() => { requestAnimationFrame(resolve); }); }); }); } export function getServerURL(server: http.Server): string { const address = server.address(); if (address && typeof address !== 'string') { return `http://localhost:${address.port}`; } else { return `${address}`; } } ================================================ FILE: packages/rrweb-snapshot/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "include": ["src"], "exclude": ["vite.config.ts", "vitest.config.ts", "test"], "compilerOptions": { "rootDir": "src", "tsBuildInfoFile": "./tsconfig.tsbuildinfo" }, "references": [ { "path": "../types" }, { "path": "../utils" } ] } ================================================ FILE: packages/rrweb-snapshot/vite.config.js ================================================ import path from 'path'; import config from '../../vite.config.default'; export default config(path.resolve(__dirname, 'src/index.ts'), 'rrwebSnapshot'); ================================================ FILE: packages/rrweb-snapshot/vitest.config.ts ================================================ /// import { defineProject, mergeConfig } from 'vitest/config'; import configShared from '../../vitest.config.ts'; export default mergeConfig( configShared, defineProject({ test: { globals: true, }, }), ); ================================================ FILE: packages/types/.gitignore ================================================ dist es lib typings ================================================ FILE: packages/types/CHANGELOG.md ================================================ # @rrweb/types ## 2.0.0-alpha.20 ## 2.0.0-alpha.19 ## 2.0.0-alpha.18 ## 2.0.0-alpha.17 ### Minor Changes - [#1503](https://github.com/rrweb-io/rrweb/pull/1503) [`335639a`](https://github.com/rrweb-io/rrweb/commit/335639af9b0ce7f70eb0f38ce113d877c7325158) Thanks [@Juice10](https://github.com/Juice10)! - Support top-layer components. Fixes #1381. ### Patch Changes - Updated dependencies [[`40bbc25`](https://github.com/rrweb-io/rrweb/commit/40bbc25fc287badc317a53f2d3f21b1c9f2b211b), [`335639a`](https://github.com/rrweb-io/rrweb/commit/335639af9b0ce7f70eb0f38ce113d877c7325158), [`d350da8`](https://github.com/rrweb-io/rrweb/commit/d350da8552d8616dd118ee550bdfbce082986562), [`be6bf52`](https://github.com/rrweb-io/rrweb/commit/be6bf52c248c35de1b3491e3a3440ff61f876414)]: - rrweb-snapshot@2.0.0-alpha.17 ## 2.0.0-alpha.16 ### Patch Changes - Updated dependencies [[`a2c8a1a`](https://github.com/rrweb-io/rrweb/commit/a2c8a1a37bfcf8389b280af792262c8263a979a3), [`d08624c`](https://github.com/rrweb-io/rrweb/commit/d08624cb28add386c3618a0e6607424c3f1884d8)]: - rrweb-snapshot@2.0.0-alpha.16 ## 2.0.0-alpha.15 ### Major Changes - [#1497](https://github.com/rrweb-io/rrweb/pull/1497) [`2606a2a`](https://github.com/rrweb-io/rrweb/commit/2606a2a28f2a6d897b8ae4ea3ec40ef0eeacbfaf) Thanks [@Juice10](https://github.com/Juice10)! - Distributed files have new filenames, paths and extensions. **Important: If you reference distributed files or types directly, you might have to update your paths/filenames. E.g. you import from `rrweb/typings/...` or `rrdom/es`. However you run `import rrweb from 'rrweb'` you won't notice a difference with this change.** If you include rrweb files directly in a script tag, you might have to update that path to include a the `.umd.cjs` files instead. All `.js` files now use ES modules which can be used in modern browsers, node.js and bundlers that support ES modules. All npm packages now also ship `.cjs` and `.umd.cjs` files. The `.umd.cjs` files are CommonJS modules that bundle all files together to make it easy to ship one file to browser environments (similar to the previous `.js` files). The `.cjs` files are CommonJS modules that can be used in older Node.js environments. Types should be better defined in `package.json` and if you need specific types they might be exported from new packages (for example `PlayerMachineState` and `SpeedMachineState` are now exported from `@rrweb/replay`). Check the `package.json`'s `main` and `exports` field for the available files. ### Patch Changes - Updated dependencies [[`4014305`](https://github.com/rrweb-io/rrweb/commit/40143059446cee5c042c007b1c2e976f36e172f5), [`82f6fec`](https://github.com/rrweb-io/rrweb/commit/82f6fecf36413ecbc994a510144487f1de20d1d5), [`2606a2a`](https://github.com/rrweb-io/rrweb/commit/2606a2a28f2a6d897b8ae4ea3ec40ef0eeacbfaf), [`f3cf092`](https://github.com/rrweb-io/rrweb/commit/f3cf0928df30d5ed5c0d573c524be6e744c0f8d3), [`e08706a`](https://github.com/rrweb-io/rrweb/commit/e08706ae60268b6eb05c6292ef948c71bd423ce3)]: - rrweb-snapshot@2.0.0-alpha.15 ## 2.0.0-alpha.14 ### Patch Changes - Updated dependencies [[`03b5216`](https://github.com/rrweb-io/rrweb/commit/03b5216a9403f1509b4f69d1d71ef9874277fe91), [`46f1b25`](https://github.com/rrweb-io/rrweb/commit/46f1b252a5919c68c68e825bd6089cc2e7d34e7c), [`cbbd1e5`](https://github.com/rrweb-io/rrweb/commit/cbbd1e55f1f7fa2eed9fa11e4152b509bdfd88f7), [`5e7943d`](https://github.com/rrweb-io/rrweb/commit/5e7943dbae6e2cde76c484bdd26bc0b96f1b6dce), [`c0f83af`](https://github.com/rrweb-io/rrweb/commit/c0f83afab8f1565633de0e986b7e96fa56f2d25c), [`e96f668`](https://github.com/rrweb-io/rrweb/commit/e96f668c86bd0ab5dc190bb2957a170271bb2ebc)]: - rrweb-snapshot@2.0.0-alpha.14 ## 2.0.0-alpha.13 ### Patch Changes - [#1432](https://github.com/rrweb-io/rrweb/pull/1432) [`123a81e`](https://github.com/rrweb-io/rrweb/commit/123a81e12d072cd95d701231176d7eb2d03b3961) Thanks [@Juice10](https://github.com/Juice10)! - Add `loop` to `mediaInteractionParam` - [#1369](https://github.com/rrweb-io/rrweb/pull/1369) [`c278d06`](https://github.com/rrweb-io/rrweb/commit/c278d068a0e2f1175cce7cc63920ac1fbf4783cf) Thanks [@stefansundin](https://github.com/stefansundin)! - Fix type error when using `"moduleResolution": "NodeNext"`. - Updated dependencies [[`123a81e`](https://github.com/rrweb-io/rrweb/commit/123a81e12d072cd95d701231176d7eb2d03b3961), [`f7c6973`](https://github.com/rrweb-io/rrweb/commit/f7c6973ae9c21b9ea014bdef7101f976f04d9356)]: - rrweb-snapshot@2.0.0-alpha.13 ## 2.0.0-alpha.12 ### Patch Changes - Updated dependencies [[`58c9104`](https://github.com/rrweb-io/rrweb/commit/58c9104eddc8b7994a067a97daae5684e42f892f), [`a2be77b`](https://github.com/rrweb-io/rrweb/commit/a2be77b82826c4be0e7f3c7c9f7ee50476d5f6f8), [`a7c33f2`](https://github.com/rrweb-io/rrweb/commit/a7c33f2093c4d92faf7ae25e8bb0e088d122c13b), [`8aea5b0`](https://github.com/rrweb-io/rrweb/commit/8aea5b00a4dfe5a6f59bd2ae72bb624f45e51e81), [`314a8dd`](https://github.com/rrweb-io/rrweb/commit/314a8dde5a13095873b89d07bac7c949918bf817), [`7c0dc9d`](https://github.com/rrweb-io/rrweb/commit/7c0dc9dfe1564c9d6624557c5b394e7844955882), [`07ac5c9`](https://github.com/rrweb-io/rrweb/commit/07ac5c9e1371824ec3ffb705f9250bbe10f4b73e)]: - rrweb-snapshot@2.0.0-alpha.12 ## 2.0.0-alpha.11 ### Patch Changes - [#1287](https://github.com/rrweb-io/rrweb/pull/1287) [`efdc167`](https://github.com/rrweb-io/rrweb/commit/efdc167ca6c039d04af83612e3d92498bb9b41a7) Thanks [@Juice10](https://github.com/Juice10)! - Upgrade all projects to typescript 4.9.5 - Updated dependencies [[`11f6567`](https://github.com/rrweb-io/rrweb/commit/11f6567fd81ef9ed0f954a7b6d5e39653f56004f), [`efdc167`](https://github.com/rrweb-io/rrweb/commit/efdc167ca6c039d04af83612e3d92498bb9b41a7), [`efdc167`](https://github.com/rrweb-io/rrweb/commit/efdc167ca6c039d04af83612e3d92498bb9b41a7)]: - rrweb-snapshot@2.0.0-alpha.11 ## 2.0.0-alpha.10 ### Patch Changes - [#1268](https://github.com/rrweb-io/rrweb/pull/1268) [`d872d28`](https://github.com/rrweb-io/rrweb/commit/d872d2809e3ec8d6ff5d3d5f43bc81aff70e7548) Thanks [@eoghanmurray](https://github.com/eoghanmurray)! - Compact style mutation fixes and improvements - fixes when style updates contain a 'var()' on a shorthand property #1246 - further ensures that style mutations are compact by reverting to string method if it is shorter - Updated dependencies [[`c6600e7`](https://github.com/rrweb-io/rrweb/commit/c6600e742b8ec0b6295816bb5de9edcd624d975e)]: - rrweb-snapshot@2.0.0-alpha.10 ## 2.0.0-alpha.9 ### Patch Changes - Updated dependencies [[`d7c72bf`](https://github.com/rrweb-io/rrweb/commit/d7c72bff0724b46a6fa94af455220626a27104fe)]: - rrweb-snapshot@2.0.0-alpha.9 ## 2.0.0-alpha.8 ### Minor Changes - [#1129](https://github.com/rrweb-io/rrweb/pull/1129) [`979d2b1`](https://github.com/rrweb-io/rrweb/commit/979d2b1847a3d05e2731722952e4d6bd8be54f40) Thanks [@eoghanmurray](https://github.com/eoghanmurray)! - click events now include a `.pointerType` attribute which distinguishes between ['pen', 'mouse' and 'touch' events](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType). There is no new PenDown/PenUp events, but these can be detected with a MouseDown/MouseUp + pointerType=pen ### Patch Changes - Updated dependencies [[`bc84246`](https://github.com/rrweb-io/rrweb/commit/bc84246f78849a80dbb8fe9b4e76117afcc5c3f7), [`d0fdc0f`](https://github.com/rrweb-io/rrweb/commit/d0fdc0f273bb156a1faab4782b40fbec8dccf915)]: - rrweb-snapshot@2.0.0-alpha.8 ## 2.0.0-alpha.7 ### Patch Changes - Updated dependencies [[`d2582e9`](https://github.com/rrweb-io/rrweb/commit/d2582e9a81197130cd93bc1dd778e16fddfb0be3), [`e7f0c80`](https://github.com/rrweb-io/rrweb/commit/e7f0c808c3f348fb27d1acd5fa300a5d92b14d00)]: - rrweb-snapshot@2.0.0-alpha.7 ## 2.0.0-alpha.6 ### Patch Changes - Updated dependencies [[`c28ef5f`](https://github.com/rrweb-io/rrweb/commit/c28ef5f658abb93086504581409cf7a376db48dc), [`f6f07e9`](https://github.com/rrweb-io/rrweb/commit/f6f07e953376634a4caf28ff8cbfed5a017c4347), [`eac9b18`](https://github.com/rrweb-io/rrweb/commit/eac9b18bbfa3c350797b99b583dd93a5fc32b828), [`8e47ca1`](https://github.com/rrweb-io/rrweb/commit/8e47ca1021ebb4fc036b37623ef10abf7976d6dd)]: - rrweb-snapshot@2.0.0-alpha.6 ## 2.0.0-alpha.5 ### Patch Changes - Updated dependencies [[`1385f7a`](https://github.com/rrweb-io/rrweb/commit/1385f7acc0052f83be1458a7b00e18c026ee393f), [`227d43a`](https://github.com/rrweb-io/rrweb/commit/227d43abb93d57cadc70c760b28c46911bf7d8ff)]: - rrweb-snapshot@2.0.0-alpha.5 ================================================ FILE: packages/types/README.md ================================================ # @rrweb/types This package contains the shared types used across rrweb packages. See the [guide](../../guide.md) for more info on rrweb. ## Sponsors [Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site. ### Gold Sponsors 🥇
sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Silver Sponsors 🥈
sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Bronze Sponsors 🥉
sponsor sponsor sponsor sponsor sponsor sponsor sponsor sponsor sponsor
### Backers ## Core Team Members

Yuyz0112


Yun Feng


eoghanmurray


Juice10
open for rrweb consulting
## Who's using rrweb?
Smart screen recording for SaaS
The first ever UX automation tool Remote Access & Co-Browsing The open source, fullstack Monitoring Platform. Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions.
Intercept, Modify, Record & Replay HTTP Requests. In-app bug reporting & customer feedback platform. Self-hosted website analytics with heatmaps and session recordings. Interactive product demos for small marketing teams
================================================ FILE: packages/types/package.json ================================================ { "name": "@rrweb/types", "version": "2.0.0-alpha.20", "publishConfig": { "access": "public" }, "keywords": [ "rrweb", "@rrweb/types" ], "scripts": { "dev": "vite build --watch", "build": "yarn turbo run prepublish", "check-types": "tsc -noEmit", "prepublish": "tsc -noEmit && vite build", "lint": "yarn eslint src/**/*.ts" }, "homepage": "https://github.com/rrweb-io/rrweb/tree/main/packages/@rrweb/types#readme", "bugs": { "url": "https://github.com/rrweb-io/rrweb/issues" }, "repository": { "type": "git", "url": "git+https://github.com/rrweb-io/rrweb.git" }, "license": "MIT", "type": "module", "main": "./dist/types.umd.cjs", "module": "./dist/types.js", "unpkg": "./dist/types.umd.cjs", "typings": "dist/index.d.ts", "exports": { ".": { "import": { "types": "./dist/index.d.ts", "default": "./dist/types.js" }, "require": { "types": "./dist/index.d.cts", "default": "./dist/types.umd.cjs" } } }, "files": [ "umd", "dist", "package.json" ], "devDependencies": { "vite": "^6.0.1", "vite-plugin-dts": "^3.9.1" }, "browserslist": [ "supports es6-class" ] } ================================================ FILE: packages/types/src/index.ts ================================================ export enum EventType { DomContentLoaded, Load, FullSnapshot, IncrementalSnapshot, Meta, Custom, Plugin, } export type domContentLoadedEvent = { type: EventType.DomContentLoaded; data: unknown; }; export type loadedEvent = { type: EventType.Load; data: unknown; }; export type fullSnapshotEvent = { type: EventType.FullSnapshot; data: { node: serializedNodeWithId; initialOffset: { top: number; left: number; }; }; }; export type incrementalSnapshotEvent = { type: EventType.IncrementalSnapshot; data: incrementalData; }; export type metaEvent = { type: EventType.Meta; data: { href: string; width: number; height: number; }; }; export type customEvent = { type: EventType.Custom; data: { tag: string; payload: T; }; }; export type pluginEvent = { type: EventType.Plugin; data: { plugin: string; payload: T; }; }; export enum IncrementalSource { Mutation, MouseMove, MouseInteraction, Scroll, ViewportResize, Input, TouchMove, MediaInteraction, StyleSheetRule, CanvasMutation, Font, Log, Drag, StyleDeclaration, Selection, AdoptedStyleSheet, CustomElement, } export type mutationData = { source: IncrementalSource.Mutation; } & mutationCallbackParam; export type mousemoveData = { source: | IncrementalSource.MouseMove | IncrementalSource.TouchMove | IncrementalSource.Drag; positions: mousePosition[]; }; export type mouseInteractionData = { source: IncrementalSource.MouseInteraction; } & mouseInteractionParam; export type scrollData = { source: IncrementalSource.Scroll; } & scrollPosition; export type viewportResizeData = { source: IncrementalSource.ViewportResize; } & viewportResizeDimension; export type inputData = { source: IncrementalSource.Input; id: number; } & inputValue; export type mediaInteractionData = { source: IncrementalSource.MediaInteraction; } & mediaInteractionParam; export type styleSheetRuleData = { source: IncrementalSource.StyleSheetRule; } & styleSheetRuleParam; export type styleDeclarationData = { source: IncrementalSource.StyleDeclaration; } & styleDeclarationParam; export type canvasMutationData = { source: IncrementalSource.CanvasMutation; } & canvasMutationParam; export type fontData = { source: IncrementalSource.Font; } & fontParam; export type selectionData = { source: IncrementalSource.Selection; } & selectionParam; export type adoptedStyleSheetData = { source: IncrementalSource.AdoptedStyleSheet; } & adoptedStyleSheetParam; export type customElementData = { source: IncrementalSource.CustomElement; } & customElementParam; export type incrementalData = | mutationData | mousemoveData | mouseInteractionData | scrollData | viewportResizeData | inputData | mediaInteractionData | styleSheetRuleData | canvasMutationData | fontData | selectionData | styleDeclarationData | adoptedStyleSheetData | customElementData; export type eventWithoutTime = | domContentLoadedEvent | loadedEvent | fullSnapshotEvent | incrementalSnapshotEvent | metaEvent | customEvent | pluginEvent; /** * @deprecated intended for internal use * a synonym for eventWithoutTime */ export type event = eventWithoutTime; export type eventWithTime = eventWithoutTime & { timestamp: number; delay?: number; }; export type canvasEventWithTime = eventWithTime & { type: EventType.IncrementalSnapshot; data: canvasMutationData; }; export type blockClass = string | RegExp; export type maskTextClass = string | RegExp; export type SamplingStrategy = Partial<{ /** * false means not to record mouse/touch move events * number is the throttle threshold of recording mouse/touch move */ mousemove: boolean | number; /** * number is the throttle threshold of mouse/touch move callback */ mousemoveCallback: number; /** * false means not to record mouse interaction events * can also specify record some kinds of mouse interactions */ mouseInteraction: boolean | Record; /** * 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 */ input: 'all' | 'last'; /** * 'all' will record every single canvas call * number between 1 and 60, will record an image snapshots in a web-worker a (maximum) number of times per second. * Number only supported where [`OffscreenCanvas`](http://mdn.io/offscreencanvas) is supported. */ canvas: 'all' | number; }>; export interface ICrossOriginIframeMirror { getId( iframe: HTMLIFrameElement, remoteId: number, parentToRemoteMap?: Map, remoteToParentMap?: Map, ): number; getIds(iframe: HTMLIFrameElement, remoteId: number[]): number[]; getRemoteId( iframe: HTMLIFrameElement, parentId: number, map?: Map, ): number; getRemoteIds(iframe: HTMLIFrameElement, parentId: number[]): number[]; reset(iframe?: HTMLIFrameElement): void; } export type RecordPlugin = { name: string; observer?: ( cb: (...args: Array) => void, win: IWindow, options: TOptions, ) => listenerHandler; eventProcessor?: (event: eventWithTime) => eventWithTime & TExtend; getMirror?: (mirrors: { nodeMirror: IMirror; crossOriginIframeMirror: ICrossOriginIframeMirror; crossOriginIframeStyleMirror: ICrossOriginIframeMirror; }) => void; options: TOptions; }; export type hooksParam = { mutation?: mutationCallBack; mousemove?: mousemoveCallBack; mouseInteraction?: mouseInteractionCallBack; scroll?: scrollCallback; viewportResize?: viewportResizeCallback; input?: inputCallback; mediaInteaction?: mediaInteractionCallback; styleSheetRule?: styleSheetRuleCallback; styleDeclaration?: styleDeclarationCallback; canvasMutation?: canvasMutationCallback; font?: fontCallback; selection?: selectionCallback; customElement?: customElementCallback; }; // https://dom.spec.whatwg.org/#interface-mutationrecord export type mutationRecord = Readonly<{ type: string; target: Node; oldValue: string | null; addedNodes: NodeList; removedNodes: NodeList; attributeName: string | null; }>; export type textCursor = { node: Node; value: string | null; }; export type textMutation = { id: number; value: string | null; }; export type styleOMValue = { [key: string]: styleValueWithPriority | string | false; }; export type styleValueWithPriority = [string, string]; export type attributeCursor = { node: Node; attributes: { [key: string]: string | styleOMValue | null; }; styleDiff: styleOMValue; _unchangedStyles: styleOMValue; }; export type attributeMutation = { id: number; attributes: { [key: string]: string | styleOMValue | null; }; }; export type removedNodeMutation = { parentId: number; id: number; isShadow?: boolean; }; export type addedNodeMutation = { parentId: number; // Newly recorded mutations will not have previousId any more, just for compatibility previousId?: number | null; nextId: number | null; node: serializedNodeWithId; }; export type mutationCallbackParam = { texts: textMutation[]; attributes: attributeMutation[]; removes: removedNodeMutation[]; adds: addedNodeMutation[]; isAttachIframe?: true; }; export type mutationCallBack = (m: mutationCallbackParam) => void; export type mousemoveCallBack = ( p: mousePosition[], source: | IncrementalSource.MouseMove | IncrementalSource.TouchMove | IncrementalSource.Drag, ) => void; export type mousePosition = { x: number; y: number; id: number; timeOffset: number; }; export type mouseMovePos = { x: number; y: number; id: number; debugData: incrementalData; }; export enum MouseInteractions { MouseUp, MouseDown, Click, ContextMenu, DblClick, Focus, Blur, TouchStart, TouchMove_Departed, // we will start a separate observer for touch move event TouchEnd, TouchCancel, } export enum PointerTypes { Mouse, Pen, Touch, } export enum CanvasContext { '2D', WebGL, WebGL2, } export type SerializedCanvasArg = | { rr_type: 'ArrayBuffer'; base64: string; // base64 } | { rr_type: 'Blob'; data: Array; type?: string; } | { rr_type: string; src: string; // url of image } | { rr_type: string; args: Array; } | { rr_type: string; index: number; }; export type CanvasArg = | SerializedCanvasArg | string | number | boolean | null | CanvasArg[]; type mouseInteractionParam = { type: MouseInteractions; id: number; x?: number; y?: number; pointerType?: PointerTypes; }; export type mouseInteractionCallBack = (d: mouseInteractionParam) => void; export type scrollPosition = { id: number; x: number; y: number; }; export type scrollCallback = (p: scrollPosition) => void; export type styleSheetAddRule = { rule: string; index?: number | number[]; }; export type styleSheetDeleteRule = { index: number | number[]; }; export type styleSheetRuleParam = { id?: number; styleId?: number; removes?: styleSheetDeleteRule[]; adds?: styleSheetAddRule[]; replace?: string; replaceSync?: string; }; export type styleSheetRuleCallback = (s: styleSheetRuleParam) => void; export type adoptedStyleSheetParam = { // id indicates the node id of document or shadow DOMs' host element. id: number; // New CSSStyleSheets which have never appeared before. styles?: { styleId: number; rules: styleSheetAddRule[]; }[]; // StyleSheet ids to be adopted. styleIds: number[]; }; export type adoptedStyleSheetCallback = (a: adoptedStyleSheetParam) => void; export type styleDeclarationParam = { id?: number; styleId?: 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 ImageBitmapDataURLWorkerParams = { id: number; bitmap: ImageBitmap; width: number; height: number; dataURLOptions: DataURLOptions; }; export type ImageBitmapDataURLWorkerResponse = | { id: number; } | { id: number; type: string; base64: string; width: number; height: number; }; export type fontParam = { family: string; fontSource: string; buffer: boolean; descriptors?: FontFaceDescriptors; }; export type fontCallback = (p: fontParam) => void; export type viewportResizeDimension = { width: number; height: number; }; export type viewportResizeCallback = (d: viewportResizeDimension) => void; export type inputValue = { 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; export enum MediaInteractions { Play, Pause, Seeked, VolumeChange, RateChange, } export type mediaInteractionParam = { type: MediaInteractions; id: number; currentTime?: number; volume?: number; muted?: boolean; loop?: boolean; playbackRate?: number; }; export type mediaInteractionCallback = (p: mediaInteractionParam) => void; export type DocumentDimension = { x: number; y: number; // scale value relative to its parent iframe relativeScale: number; // scale value relative to the root iframe absoluteScale: number; }; export type SelectionRange = { start: number; startOffset: number; end: number; endOffset: number; }; export type selectionParam = { ranges: Array; }; export type selectionCallback = (p: selectionParam) => void; export type customElementParam = { define?: { name: string; }; }; export type customElementCallback = (c: customElementParam) => void; /** * @deprecated */ interface INode extends Node { __sn: serializedNodeWithId; } export type DeprecatedMirror = { map: { [key: number]: INode; }; getId: (n: Node) => number; getNode: (id: number) => INode | null; removeNodeFromMap: (n: Node) => void; has: (id: number) => boolean; reset: () => void; }; export type throttleOptions = { leading?: boolean; trailing?: boolean; }; export type listenerHandler = () => void; export type hookResetter = () => void; export type playerMetaData = { startTime: number; endTime: number; totalTime: number; }; export type actionWithDelay = { doAction: () => void; delay: number; }; export type Handler = (event?: unknown) => void; export type Emitter = { on(type: string, handler: Handler): void; emit(type: string, event?: unknown): void; off(type: string, handler: Handler): void; }; export type Arguments = T extends (...payload: infer U) => unknown ? U : unknown; export enum ReplayerEvents { Start = 'start', Pause = 'pause', /** * @deprecated use Play instead */ Resume = 'resume', Resize = 'resize', Finish = 'finish', FullsnapshotRebuilded = 'fullsnapshot-rebuilded', LoadStylesheetStart = 'load-stylesheet-start', LoadStylesheetEnd = 'load-stylesheet-end', SkipStart = 'skip-start', SkipEnd = 'skip-end', MouseInteraction = 'mouse-interaction', EventCast = 'event-cast', CustomEvent = 'custom-event', Flush = 'flush', StateChange = 'state-change', PlayBack = 'play-back', Destroy = 'destroy', } export type KeepIframeSrcFn = (src: string) => boolean; declare global { interface Window { FontFace: typeof FontFace; } } export type IWindow = Window & typeof globalThis; export type Optional = Pick, K> & Omit; export type GetTypedKeys = TakeTypeHelper< Obj, ValueType >[keyof TakeTypeHelper]; export type TakeTypeHelper = { [K in keyof Obj]: Obj[K] extends ValueType ? K : never; }; export type TakeTypedKeyValues = Pick< Obj, TakeTypeHelper[keyof TakeTypeHelper] >; export enum NodeType { Document, DocumentType, Element, Text, CDATA, Comment, } export type documentNode = { type: NodeType.Document; childNodes: serializedNodeWithId[]; compatMode?: string; }; export type documentTypeNode = { type: NodeType.DocumentType; name: string; publicId: string; systemId: string; }; type cssTextKeyAttr = { _cssText?: string; }; export type attributes = cssTextKeyAttr & { [key: string]: | string | number // properties e.g. rr_scrollLeft or rr_mediaCurrentTime | true // e.g. checked on | null; // an indication that an attribute was removed (during a mutation) }; export type legacyAttributes = { /** * @deprecated old bug in rrweb was causing these to always be set * @see https://github.com/rrweb-io/rrweb/pull/651 */ selected: false; }; export type mediaAttributes = { rr_mediaState: 'played' | 'paused'; rr_mediaCurrentTime: number; /** * for backwards compatibility this is optional but should always be set */ rr_mediaPlaybackRate?: number; /** * for backwards compatibility this is optional but should always be set */ rr_mediaMuted?: boolean; /** * for backwards compatibility this is optional but should always be set */ rr_mediaLoop?: boolean; /** * for backwards compatibility this is optional but should always be set */ rr_mediaVolume?: number; }; export type elementNode = { type: NodeType.Element; tagName: string; attributes: attributes; childNodes: serializedNodeWithId[]; isSVG?: true; needBlock?: boolean; // This is a custom element or not. isCustom?: true; }; export type textNode = { type: NodeType.Text; textContent: string; /** * @deprecated styles are now always snapshotted against parent