Record canvas snapshots N times per second (#859)
* Only record canvas when recordCanvas is true * All should be compiled first Makes recompiling+debugging a lot faster * Add support for compiling web workes Replaces @rollup/plugin-typescript for rollup-plugin-typescript2 as the former is incompatible with rollup-plugin-web-worker-loader * Update yarn.lock * Upgrade to typescript 4.5.5 * add support for replay of ImageBitmap in 2d canvas * Snapshot canvases in a web-worker on FPS basis * Fix performance of canvas recording and playback * Wait for all images to be preloaded before checking results * flatten base64 strings, as encoding isn't consistent * Cleanup * Add serializing to 2d canvases as well * Disable blob serialize test We don't have any code for it yet * Upgrade @rollup/plugin-commonjs to 21.0.2 Fixes https://linguinecode.com/post/import-export-appear-at-the-top-level * Move canvas recording options to `sampling` Based on: https://github.com/rrweb-io/rrweb/pull/859#discussion_r846582146
This commit is contained in:
@@ -11,6 +11,18 @@ rrweb.record({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Alternatively enable image snapshot recording of Canvas at a maximum of 15 frames per second:
|
||||||
|
|
||||||
|
```js
|
||||||
|
rrweb.record({
|
||||||
|
emit(event) {},
|
||||||
|
recordCanvas: true,
|
||||||
|
sampling: {
|
||||||
|
canvas: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
Enable replaying Canvas:
|
Enable replaying Canvas:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|||||||
@@ -12,6 +12,18 @@ rrweb.record({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
或者启用每秒 15 帧的 Canvas 图像快照记录:
|
||||||
|
|
||||||
|
```js
|
||||||
|
rrweb.record({
|
||||||
|
emit(event) {},
|
||||||
|
recordCanvas: true,
|
||||||
|
sampling: {
|
||||||
|
canvas: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
回放时对 Canvas 进行回放:
|
回放时对 Canvas 进行回放:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ Some common patterns may emit lots of events are:
|
|||||||
- long list
|
- long list
|
||||||
- complex SVG
|
- complex SVG
|
||||||
- element with JS controlled animation
|
- element with JS controlled animation
|
||||||
|
- canvas animations
|
||||||
|
|
||||||
## Sampling
|
## Sampling
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
- 长列表
|
- 长列表
|
||||||
- 复杂的 SVG
|
- 复杂的 SVG
|
||||||
- 包含 JS 控制动画的元素
|
- 包含 JS 控制动画的元素
|
||||||
|
- canvas 动画
|
||||||
|
|
||||||
## 抽样策略
|
## 抽样策略
|
||||||
|
|
||||||
|
|||||||
78
guide.md
78
guide.md
@@ -135,30 +135,30 @@ setInterval(save, 10 * 1000);
|
|||||||
|
|
||||||
The parameter of `rrweb.record` accepts the following options.
|
The parameter of `rrweb.record` accepts the following options.
|
||||||
|
|
||||||
| key | default | description |
|
| key | default | description |
|
||||||
| -------------------- | ------------------ | ------------------------------------------------------------ |
|
| -------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| emit | required | the callback function to get emitted events |
|
| emit | required | the callback function to get emitted events |
|
||||||
| checkoutEveryNth | - | take a full snapshot after every N events<br />refer to the [checkout](#checkout) chapter |
|
| checkoutEveryNth | - | take a full snapshot after every N events<br />refer to the [checkout](#checkout) chapter |
|
||||||
| checkoutEveryNms | - | take a full snapshot after every N ms<br />refer to the [checkout](#checkout) chapter |
|
| checkoutEveryNms | - | take a full snapshot after every N ms<br />refer to the [checkout](#checkout) chapter |
|
||||||
| blockClass | 'rr-block' | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) chapter |
|
| blockClass | 'rr-block' | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) chapter |
|
||||||
| blockSelector | null | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter |
|
| blockSelector | null | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter |
|
||||||
| ignoreClass | 'rr-ignore' | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter |
|
| ignoreClass | 'rr-ignore' | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter |
|
||||||
| maskTextClass | 'rr-mask' | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter |
|
| maskTextClass | 'rr-mask' | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter |
|
||||||
| maskTextSelector | null | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter |
|
| maskTextSelector | null | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter |
|
||||||
| maskAllInputs | false | mask all input content as \* |
|
| maskAllInputs | false | mask all input content as \* |
|
||||||
| maskInputOptions | { password: true } | mask some kinds of input \*<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95) |
|
| maskInputOptions | { password: true } | mask some kinds of input \*<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95) |
|
||||||
| maskInputFn | - | customize mask input content recording logic |
|
| maskInputFn | - | customize mask input content recording logic |
|
||||||
| maskTextFn | - | customize mask text content recording logic |
|
| maskTextFn | - | customize mask text content recording logic |
|
||||||
| slimDOMOptions | {} | remove unnecessary parts of the DOM <br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108) |
|
| slimDOMOptions | {} | remove unnecessary parts of the DOM <br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108) |
|
||||||
| inlineStylesheet | true | whether to inline the stylesheet in the events |
|
| inlineStylesheet | true | whether to inline the stylesheet in the events |
|
||||||
| hooks | {} | hooks for events<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207) |
|
| hooks | {} | hooks for events<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207) |
|
||||||
| packFn | - | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md) |
|
| packFn | - | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md) |
|
||||||
| sampling | - | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md) |
|
| sampling | - | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md) |
|
||||||
| recordCanvas | false | whether to record the canvas element |
|
| recordCanvas | false | Whether to record the canvas element. Available options:<br/>`false`, <br/>`true` |
|
||||||
| inlineImages | false | whether to record the image content |
|
| inlineImages | false | whether to record the image content |
|
||||||
| collectFonts | false | whether to collect fonts in the website |
|
| collectFonts | false | whether to collect fonts in the website |
|
||||||
| userTriggeredOnInput | false | whether to add `userTriggered` on input events that indicates if this event was triggered directly by the user or not. [What is `userTriggered`?](https://github.com/rrweb-io/rrweb/pull/495) |
|
| userTriggeredOnInput | false | whether to add `userTriggered` on input events that indicates if this event was triggered directly by the user or not. [What is `userTriggered`?](https://github.com/rrweb-io/rrweb/pull/495) |
|
||||||
| plugins | [] | load plugins to provide extended record functions. [What is plugins?](./docs/recipes/plugin.md) |
|
| plugins | [] | load plugins to provide extended record functions. [What is plugins?](./docs/recipes/plugin.md) |
|
||||||
|
|
||||||
#### Privacy
|
#### Privacy
|
||||||
|
|
||||||
@@ -286,23 +286,23 @@ replayer.pause(5000);
|
|||||||
|
|
||||||
The replayer accepts options as its constructor's second parameter, and it has the following options:
|
The replayer accepts options as its constructor's second parameter, and it has the following options:
|
||||||
|
|
||||||
| key | default | description |
|
| key | default | description |
|
||||||
| ------------------- | ------------- | ------------------------------------------------------------ |
|
| ------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| speed | 1 | replay speed ratio |
|
| speed | 1 | replay speed ratio |
|
||||||
| root | document.body | the root element of replayer |
|
| root | document.body | the root element of replayer |
|
||||||
| loadTimeout | 0 | timeout of loading remote style sheet |
|
| loadTimeout | 0 | timeout of loading remote style sheet |
|
||||||
| skipInactive | false | whether to skip inactive time |
|
| skipInactive | false | whether to skip inactive time |
|
||||||
| showWarning | true | whether to print warning messages during replay |
|
| showWarning | true | whether to print warning messages during replay |
|
||||||
| showDebug | false | whether to print debug messages during replay |
|
| showDebug | false | whether to print debug messages during replay |
|
||||||
| blockClass | 'rr-block' | element with the class name will display as a blocked area |
|
| blockClass | 'rr-block' | element with the class name will display as a blocked area |
|
||||||
| liveMode | false | whether to enable live mode |
|
| liveMode | false | whether to enable live mode |
|
||||||
| insertStyleRules | [] | accepts multiple CSS rule string, which will be injected into the replay iframe |
|
| insertStyleRules | [] | accepts multiple CSS rule string, which will be injected into the replay iframe |
|
||||||
| triggerFocus | true | whether to trigger focus during replay |
|
| triggerFocus | true | whether to trigger focus during replay |
|
||||||
| UNSAFE_replayCanvas | false | whether to replay the canvas element. **Enable this will remove the sandbox, which is unsafe.** |
|
| UNSAFE_replayCanvas | false | whether to replay the canvas element. **Enable this will remove the sandbox, which is unsafe.** |
|
||||||
| mouseTail | true | whether to show mouse tail during replay. Set to false to disable mouse tail. A complete config can be found in this [type](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L407) |
|
| mouseTail | true | whether to show mouse tail during replay. Set to false to disable mouse tail. A complete config can be found in this [type](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L407) |
|
||||||
| unpackFn | - | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md) |
|
| unpackFn | - | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md) |
|
||||||
| logConfig | - | configuration of console output playback, refer to the [console recipe](./docs/recipes/console.md) |
|
| logConfig | - | configuration of console output playback, refer to the [console recipe](./docs/recipes/console.md) |
|
||||||
| plugins | [] | load plugins to provide extended replay functions. [What is plugins?](./docs/recipes/plugin.md) |
|
| plugins | [] | load plugins to provide extended replay functions. [What is plugins?](./docs/recipes/plugin.md) |
|
||||||
|
|
||||||
#### Use rrweb-player
|
#### Use rrweb-player
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ setInterval(save, 10 * 1000);
|
|||||||
| hooks | {} | 各类事件的回调<br />类型详见[列表](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207) |
|
| hooks | {} | 各类事件的回调<br />类型详见[列表](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207) |
|
||||||
| packFn | - | 数据压缩函数,详见[优化存储策略](./docs/recipes/optimize-storage.zh_CN.md) |
|
| packFn | - | 数据压缩函数,详见[优化存储策略](./docs/recipes/optimize-storage.zh_CN.md) |
|
||||||
| sampling | - | 数据抽样策略,详见[优化存储策略](./docs/recipes/optimize-storage.zh_CN.md) |
|
| sampling | - | 数据抽样策略,详见[优化存储策略](./docs/recipes/optimize-storage.zh_CN.md) |
|
||||||
| recordCanvas | false | 是否记录 canvas 内容 |
|
| recordCanvas | false | 是否记录 canvas 内容, 可用选项:false, true |
|
||||||
| inlineImages | false | 是否将图片内容记内联录制 |
|
| inlineImages | false | 是否将图片内容记内联录制 |
|
||||||
| collectFonts | false | 是否记录页面中的字体文件 |
|
| collectFonts | false | 是否记录页面中的字体文件 |
|
||||||
| userTriggeredOnInput | false | [什么是 `userTriggered`](https://github.com/rrweb-io/rrweb/pull/495) |
|
| userTriggeredOnInput | false | [什么是 `userTriggered`](https://github.com/rrweb-io/rrweb/pull/495) |
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "rrweb-player",
|
"name": "rrweb-player",
|
||||||
"version": "0.7.14",
|
"version": "0.7.14",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^11.0.0",
|
"@rollup/plugin-commonjs": "^21.0.2",
|
||||||
"@rollup/plugin-node-resolve": "^7.0.0",
|
"@rollup/plugin-node-resolve": "^7.0.0",
|
||||||
"@rollup/plugin-typescript": "^4.0.0",
|
"@rollup/plugin-typescript": "^4.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^3.7.0",
|
"@typescript-eslint/eslint-plugin": "^3.7.0",
|
||||||
|
|||||||
@@ -41,14 +41,14 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/rrweb-io/rrweb#readme",
|
"homepage": "https://github.com/rrweb-io/rrweb#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-node-resolve": "^7.0.0",
|
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||||
"@rollup/plugin-typescript": "^8.3.1",
|
|
||||||
"@types/chai": "^4.1.6",
|
"@types/chai": "^4.1.6",
|
||||||
"@types/inquirer": "0.0.43",
|
"@types/inquirer": "0.0.43",
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^27.4.1",
|
||||||
"@types/jest-image-snapshot": "^4.3.1",
|
"@types/jest-image-snapshot": "^4.3.1",
|
||||||
"@types/jsdom": "^16.2.14",
|
"@types/jsdom": "^16.2.14",
|
||||||
"@types/node": "^17.0.21",
|
"@types/node": "^17.0.21",
|
||||||
|
"@types/offscreencanvas": "^2019.6.4",
|
||||||
"@types/prettier": "^2.3.2",
|
"@types/prettier": "^2.3.2",
|
||||||
"@types/puppeteer": "^5.4.4",
|
"@types/puppeteer": "^5.4.4",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
@@ -63,10 +63,12 @@
|
|||||||
"jsdom-global": "^3.0.2",
|
"jsdom-global": "^3.0.2",
|
||||||
"prettier": "2.2.1",
|
"prettier": "2.2.1",
|
||||||
"puppeteer": "^9.1.1",
|
"puppeteer": "^9.1.1",
|
||||||
"rollup": "^2.45.2",
|
"rollup": "^2.68.0",
|
||||||
"rollup-plugin-postcss": "^3.1.1",
|
"rollup-plugin-postcss": "^3.1.1",
|
||||||
"rollup-plugin-rename-node-modules": "^1.1.0",
|
"rollup-plugin-rename-node-modules": "^1.3.1",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
|
"rollup-plugin-typescript2": "^0.31.2",
|
||||||
|
"rollup-plugin-web-worker-loader": "^1.6.1",
|
||||||
"ts-jest": "^27.1.3",
|
"ts-jest": "^27.1.3",
|
||||||
"ts-node": "^10.7.0",
|
"ts-node": "^10.7.0",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import typescript from '@rollup/plugin-typescript';
|
import typescript from 'rollup-plugin-typescript2';
|
||||||
import resolve from '@rollup/plugin-node-resolve';
|
import resolve from '@rollup/plugin-node-resolve';
|
||||||
import { terser } from 'rollup-plugin-terser';
|
import { terser } from 'rollup-plugin-terser';
|
||||||
import postcss from 'rollup-plugin-postcss';
|
import postcss from 'rollup-plugin-postcss';
|
||||||
import renameNodeModules from 'rollup-plugin-rename-node-modules';
|
import renameNodeModules from 'rollup-plugin-rename-node-modules';
|
||||||
|
import webWorkerLoader from 'rollup-plugin-web-worker-loader';
|
||||||
import pkg from './package.json';
|
import pkg from './package.json';
|
||||||
|
|
||||||
function toRecordPath(path) {
|
function toRecordPath(path) {
|
||||||
@@ -45,6 +46,13 @@ function toMinPath(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseConfigs = [
|
const baseConfigs = [
|
||||||
|
// all in one
|
||||||
|
{
|
||||||
|
input: './src/entries/all.ts',
|
||||||
|
name: 'rrweb',
|
||||||
|
pathFn: toAllPath,
|
||||||
|
esm: true,
|
||||||
|
},
|
||||||
// record only
|
// record only
|
||||||
{
|
{
|
||||||
input: './src/record/index.ts',
|
input: './src/record/index.ts',
|
||||||
@@ -75,13 +83,6 @@ const baseConfigs = [
|
|||||||
name: 'rrweb',
|
name: 'rrweb',
|
||||||
pathFn: (p) => p,
|
pathFn: (p) => p,
|
||||||
},
|
},
|
||||||
// all in one
|
|
||||||
{
|
|
||||||
input: './src/entries/all.ts',
|
|
||||||
name: 'rrweb',
|
|
||||||
pathFn: toAllPath,
|
|
||||||
esm: true,
|
|
||||||
},
|
|
||||||
// plugins
|
// plugins
|
||||||
{
|
{
|
||||||
input: './src/plugins/console/record/index.ts',
|
input: './src/plugins/console/record/index.ts',
|
||||||
@@ -110,10 +111,8 @@ let configs = [];
|
|||||||
for (const c of baseConfigs) {
|
for (const c of baseConfigs) {
|
||||||
const basePlugins = [
|
const basePlugins = [
|
||||||
resolve({ browser: true }),
|
resolve({ browser: true }),
|
||||||
typescript({
|
webWorkerLoader(),
|
||||||
// a trick to avoid @rollup/plugin-typescript error
|
typescript(),
|
||||||
outDir: 'es/rrweb',
|
|
||||||
}),
|
|
||||||
];
|
];
|
||||||
const plugins = basePlugins.concat(
|
const plugins = basePlugins.concat(
|
||||||
postcss({
|
postcss({
|
||||||
@@ -200,6 +199,7 @@ if (process.env.BROWSER_ONLY) {
|
|||||||
for (const c of browserOnlyBaseConfigs) {
|
for (const c of browserOnlyBaseConfigs) {
|
||||||
const plugins = [
|
const plugins = [
|
||||||
resolve({ browser: true }),
|
resolve({ browser: true }),
|
||||||
|
webWorkerLoader(),
|
||||||
typescript({
|
typescript({
|
||||||
outDir: null,
|
outDir: null,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -169,7 +169,9 @@ function getCode() {
|
|||||||
});
|
});
|
||||||
await page.evaluate(`${code}
|
await page.evaluate(`${code}
|
||||||
const events = ${JSON.stringify(events)};
|
const events = ${JSON.stringify(events)};
|
||||||
const replayer = new rrweb.Replayer(events);
|
const replayer = new rrweb.Replayer(events, {
|
||||||
|
UNSAFE_replayCanvas: true
|
||||||
|
});
|
||||||
replayer.play();
|
replayer.play();
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ function record<T = eventWithTime>(
|
|||||||
win: window,
|
win: window,
|
||||||
blockClass,
|
blockClass,
|
||||||
mirror,
|
mirror,
|
||||||
|
sampling: sampling.canvas,
|
||||||
});
|
});
|
||||||
|
|
||||||
const shadowDomManager = new ShadowDomManager({
|
const shadowDomManager = new ShadowDomManager({
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
listenerHandler,
|
listenerHandler,
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
import { hookSetter, isBlocked, patch } from '../../../utils';
|
import { hookSetter, isBlocked, patch } from '../../../utils';
|
||||||
|
import { serializeArgs } from './serialize-args';
|
||||||
|
|
||||||
export default function initCanvas2DMutationObserver(
|
export default function initCanvas2DMutationObserver(
|
||||||
cb: canvasManagerMutationCallback,
|
cb: canvasManagerMutationCallback,
|
||||||
@@ -36,27 +37,10 @@ export default function initCanvas2DMutationObserver(
|
|||||||
...args: Array<unknown>
|
...args: Array<unknown>
|
||||||
) {
|
) {
|
||||||
if (!isBlocked(this.canvas, blockClass)) {
|
if (!isBlocked(this.canvas, blockClass)) {
|
||||||
// Using setTimeout as getImageData + JSON.stringify can be heavy
|
// Using setTimeout as toDataURL can be heavy
|
||||||
// and we'd rather not block the main thread
|
// and we'd rather not block the main thread
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const recordArgs = [...args];
|
const recordArgs = serializeArgs([...args], win, this);
|
||||||
if (prop === 'drawImage') {
|
|
||||||
if (
|
|
||||||
recordArgs[0] &&
|
|
||||||
recordArgs[0] instanceof HTMLCanvasElement
|
|
||||||
) {
|
|
||||||
const canvas = recordArgs[0];
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
let imgd = ctx?.getImageData(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
canvas.width,
|
|
||||||
canvas.height,
|
|
||||||
);
|
|
||||||
let pix = imgd?.data;
|
|
||||||
recordArgs[0] = JSON.stringify(pix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cb(this.canvas, {
|
cb(this.canvas, {
|
||||||
type: CanvasContext['2D'],
|
type: CanvasContext['2D'],
|
||||||
property: prop,
|
property: prop,
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import { Mirror } from 'rrweb-snapshot';
|
import { ICanvas, Mirror } from 'rrweb-snapshot';
|
||||||
import {
|
import {
|
||||||
blockClass,
|
blockClass,
|
||||||
|
CanvasContext,
|
||||||
canvasManagerMutationCallback,
|
canvasManagerMutationCallback,
|
||||||
canvasMutationCallback,
|
canvasMutationCallback,
|
||||||
canvasMutationCommand,
|
canvasMutationCommand,
|
||||||
canvasMutationWithType,
|
canvasMutationWithType,
|
||||||
IWindow,
|
IWindow,
|
||||||
listenerHandler,
|
listenerHandler,
|
||||||
|
CanvasArg,
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
import initCanvas2DMutationObserver from './2d';
|
import initCanvas2DMutationObserver from './2d';
|
||||||
import initCanvasContextObserver from './canvas';
|
import initCanvasContextObserver from './canvas';
|
||||||
import initCanvasWebGLMutationObserver from './webgl';
|
import initCanvasWebGLMutationObserver from './webgl';
|
||||||
|
import ImageBitmapDataURLWorker from 'web-worker:../../workers/image-bitmap-data-url-worker.ts';
|
||||||
|
import { ImageBitmapDataURLRequestWorker } from '../../workers/image-bitmap-data-url-worker';
|
||||||
|
|
||||||
export type RafStamps = { latestId: number; invokeId: number | null };
|
export type RafStamps = { latestId: number; invokeId: number | null };
|
||||||
|
|
||||||
@@ -51,17 +55,21 @@ export class CanvasManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(options: {
|
constructor(options: {
|
||||||
recordCanvas: boolean | number;
|
recordCanvas: boolean;
|
||||||
mutationCb: canvasMutationCallback;
|
mutationCb: canvasMutationCallback;
|
||||||
win: IWindow;
|
win: IWindow;
|
||||||
blockClass: blockClass;
|
blockClass: blockClass;
|
||||||
mirror: Mirror;
|
mirror: Mirror;
|
||||||
|
sampling?: 'all' | number;
|
||||||
}) {
|
}) {
|
||||||
|
const { sampling = 'all', win, blockClass, recordCanvas } = options;
|
||||||
this.mutationCb = options.mutationCb;
|
this.mutationCb = options.mutationCb;
|
||||||
this.mirror = options.mirror;
|
this.mirror = options.mirror;
|
||||||
|
|
||||||
if (options.recordCanvas === true)
|
if (recordCanvas && sampling === 'all')
|
||||||
this.initCanvasMutationObserver(options.win, options.blockClass);
|
this.initCanvasMutationObserver(win, blockClass);
|
||||||
|
if (recordCanvas && typeof sampling === 'number')
|
||||||
|
this.initCanvasFPSObserver(sampling, win, blockClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
private processMutation: canvasManagerMutationCallback = function (
|
private processMutation: canvasManagerMutationCallback = function (
|
||||||
@@ -81,6 +89,111 @@ export class CanvasManager {
|
|||||||
this.pendingCanvasMutations.get(target)!.push(mutation);
|
this.pendingCanvasMutations.get(target)!.push(mutation);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private initCanvasFPSObserver(
|
||||||
|
fps: number,
|
||||||
|
win: IWindow,
|
||||||
|
blockClass: blockClass,
|
||||||
|
) {
|
||||||
|
const canvasContextReset = initCanvasContextObserver(win, blockClass);
|
||||||
|
const snapshotInProgressMap: Map<number, boolean> = new Map();
|
||||||
|
const worker = new ImageBitmapDataURLWorker() as ImageBitmapDataURLRequestWorker;
|
||||||
|
worker.onmessage = (e) => {
|
||||||
|
const { id } = e.data;
|
||||||
|
snapshotInProgressMap.set(id, false);
|
||||||
|
|
||||||
|
if (!('base64' in e.data)) return;
|
||||||
|
|
||||||
|
const { base64, type, width, height } = e.data;
|
||||||
|
this.mutationCb({
|
||||||
|
id,
|
||||||
|
type: CanvasContext['2D'],
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
property: 'clearRect', // wipe canvas
|
||||||
|
args: [0, 0, width, height],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
property: 'drawImage', // draws (semi-transparent) image
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
rr_type: 'ImageBitmap',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
rr_type: 'Blob',
|
||||||
|
data: [{ rr_type: 'ArrayBuffer', base64 }],
|
||||||
|
type,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as CanvasArg,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeBetweenSnapshots = 1000 / fps;
|
||||||
|
let lastSnapshotTime = 0;
|
||||||
|
let rafId: number;
|
||||||
|
|
||||||
|
const takeCanvasSnapshots = (timestamp: DOMHighResTimeStamp) => {
|
||||||
|
if (
|
||||||
|
lastSnapshotTime &&
|
||||||
|
timestamp - lastSnapshotTime < timeBetweenSnapshots
|
||||||
|
) {
|
||||||
|
rafId = requestAnimationFrame(takeCanvasSnapshots);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastSnapshotTime = timestamp;
|
||||||
|
|
||||||
|
win.document
|
||||||
|
.querySelectorAll(`canvas:not(.${blockClass} *)`)
|
||||||
|
.forEach(async (canvas: HTMLCanvasElement) => {
|
||||||
|
const id = this.mirror.getId(canvas);
|
||||||
|
if (snapshotInProgressMap.get(id)) return;
|
||||||
|
snapshotInProgressMap.set(id, true);
|
||||||
|
if (['webgl', 'webgl2'].includes((canvas as ICanvas).__context)) {
|
||||||
|
// if the canvas hasn't been modified recently,
|
||||||
|
// its contents won't be in memory and `createImageBitmap`
|
||||||
|
// will return a transparent imageBitmap
|
||||||
|
|
||||||
|
const context = canvas.getContext((canvas as ICanvas).__context) as
|
||||||
|
| WebGLRenderingContext
|
||||||
|
| WebGL2RenderingContext
|
||||||
|
| null;
|
||||||
|
if (
|
||||||
|
context?.getContextAttributes()?.preserveDrawingBuffer === false
|
||||||
|
) {
|
||||||
|
// Hack to load canvas back into memory so `createImageBitmap` can grab it's contents.
|
||||||
|
// Context: https://twitter.com/Juice10/status/1499775271758704643
|
||||||
|
// This hack might change the background color of the canvas in the unlikely event that
|
||||||
|
// the canvas background was changed but clear was not called directly afterwards.
|
||||||
|
context?.clear(context.COLOR_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bitmap = await createImageBitmap(canvas);
|
||||||
|
worker.postMessage(
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
bitmap,
|
||||||
|
width: canvas.width,
|
||||||
|
height: canvas.height,
|
||||||
|
},
|
||||||
|
[bitmap],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
rafId = requestAnimationFrame(takeCanvasSnapshots);
|
||||||
|
};
|
||||||
|
|
||||||
|
rafId = requestAnimationFrame(takeCanvasSnapshots);
|
||||||
|
|
||||||
|
this.resetObservers = () => {
|
||||||
|
canvasContextReset();
|
||||||
|
cancelAnimationFrame(rafId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private initCanvasMutationObserver(
|
private initCanvasMutationObserver(
|
||||||
win: IWindow,
|
win: IWindow,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
import { encode } from 'base64-arraybuffer';
|
import { encode } from 'base64-arraybuffer';
|
||||||
import { IWindow, SerializedWebGlArg } from '../../../types';
|
import { IWindow, CanvasArg } from '../../../types';
|
||||||
|
|
||||||
// TODO: unify with `replay/webgl.ts`
|
// TODO: unify with `replay/webgl.ts`
|
||||||
type GLVarMap = Map<string, any[]>;
|
type CanvasVarMap = Map<string, any[]>;
|
||||||
const webGLVarMap: Map<
|
const canvasVarMap: Map<RenderingContext, CanvasVarMap> = new Map();
|
||||||
WebGLRenderingContext | WebGL2RenderingContext,
|
export function variableListFor(ctx: RenderingContext, ctor: string) {
|
||||||
GLVarMap
|
let contextMap = canvasVarMap.get(ctx);
|
||||||
> = new Map();
|
|
||||||
export function variableListFor(
|
|
||||||
ctx: WebGLRenderingContext | WebGL2RenderingContext,
|
|
||||||
ctor: string,
|
|
||||||
) {
|
|
||||||
let contextMap = webGLVarMap.get(ctx);
|
|
||||||
if (!contextMap) {
|
if (!contextMap) {
|
||||||
contextMap = new Map();
|
contextMap = new Map();
|
||||||
webGLVarMap.set(ctx, contextMap);
|
canvasVarMap.set(ctx, contextMap);
|
||||||
}
|
}
|
||||||
if (!contextMap.has(ctor)) {
|
if (!contextMap.has(ctor)) {
|
||||||
contextMap.set(ctor, []);
|
contextMap.set(ctor, []);
|
||||||
@@ -25,7 +19,7 @@ export function variableListFor(
|
|||||||
export const saveWebGLVar = (
|
export const saveWebGLVar = (
|
||||||
value: any,
|
value: any,
|
||||||
win: IWindow,
|
win: IWindow,
|
||||||
ctx: WebGL2RenderingContext | WebGLRenderingContext,
|
ctx: RenderingContext,
|
||||||
): number | void => {
|
): number | void => {
|
||||||
if (
|
if (
|
||||||
!value ||
|
!value ||
|
||||||
@@ -48,8 +42,8 @@ export const saveWebGLVar = (
|
|||||||
export function serializeArg(
|
export function serializeArg(
|
||||||
value: any,
|
value: any,
|
||||||
win: IWindow,
|
win: IWindow,
|
||||||
ctx: WebGL2RenderingContext | WebGLRenderingContext,
|
ctx: RenderingContext,
|
||||||
): SerializedWebGlArg {
|
): CanvasArg {
|
||||||
if (value instanceof Array) {
|
if (value instanceof Array) {
|
||||||
return value.map((arg) => serializeArg(arg, win, ctx));
|
return value.map((arg) => serializeArg(arg, win, ctx));
|
||||||
} else if (value === null) {
|
} else if (value === null) {
|
||||||
@@ -100,12 +94,27 @@ export function serializeArg(
|
|||||||
rr_type: name,
|
rr_type: name,
|
||||||
src,
|
src,
|
||||||
};
|
};
|
||||||
|
} else if (value instanceof HTMLCanvasElement) {
|
||||||
|
const name = 'HTMLImageElement';
|
||||||
|
// TODO: move `toDataURL` to web worker if possible
|
||||||
|
const src = value.toDataURL(); // heavy on large canvas
|
||||||
|
return {
|
||||||
|
rr_type: name,
|
||||||
|
src,
|
||||||
|
};
|
||||||
} else if (value instanceof ImageData) {
|
} else if (value instanceof ImageData) {
|
||||||
const name = value.constructor.name;
|
const name = value.constructor.name;
|
||||||
return {
|
return {
|
||||||
rr_type: name,
|
rr_type: name,
|
||||||
args: [serializeArg(value.data, win, ctx), value.width, value.height],
|
args: [serializeArg(value.data, win, ctx), value.width, value.height],
|
||||||
};
|
};
|
||||||
|
// } else if (value instanceof Blob) {
|
||||||
|
// const name = value.constructor.name;
|
||||||
|
// return {
|
||||||
|
// rr_type: name,
|
||||||
|
// data: [serializeArg(await value.arrayBuffer(), win, ctx)],
|
||||||
|
// type: value.type,
|
||||||
|
// };
|
||||||
} else if (isInstanceOfWebGLObject(value, win) || typeof value === 'object') {
|
} else if (isInstanceOfWebGLObject(value, win) || typeof value === 'object') {
|
||||||
const name = value.constructor.name;
|
const name = value.constructor.name;
|
||||||
const index = saveWebGLVar(value, win, ctx) as number;
|
const index = saveWebGLVar(value, win, ctx) as number;
|
||||||
@@ -122,7 +131,7 @@ export function serializeArg(
|
|||||||
export const serializeArgs = (
|
export const serializeArgs = (
|
||||||
args: Array<any>,
|
args: Array<any>,
|
||||||
win: IWindow,
|
win: IWindow,
|
||||||
ctx: WebGLRenderingContext | WebGL2RenderingContext,
|
ctx: RenderingContext,
|
||||||
) => {
|
) => {
|
||||||
return [...args].map((arg) => serializeArg(arg, win, ctx));
|
return [...args].map((arg) => serializeArg(arg, win, ctx));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { encode } from 'base64-arraybuffer';
|
||||||
|
import {
|
||||||
|
ImageBitmapDataURLWorkerParams,
|
||||||
|
ImageBitmapDataURLWorkerResponse,
|
||||||
|
} from '../../types';
|
||||||
|
|
||||||
|
const lastBlobMap: Map<number, string> = new Map();
|
||||||
|
const transparentBlobMap: Map<string, string> = new Map();
|
||||||
|
|
||||||
|
export interface ImageBitmapDataURLRequestWorker {
|
||||||
|
postMessage: (
|
||||||
|
message: ImageBitmapDataURLWorkerParams,
|
||||||
|
transfer?: [ImageBitmap],
|
||||||
|
) => void;
|
||||||
|
onmessage: (message: MessageEvent<ImageBitmapDataURLWorkerResponse>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImageBitmapDataURLResponseWorker {
|
||||||
|
onmessage:
|
||||||
|
| null
|
||||||
|
| ((message: MessageEvent<ImageBitmapDataURLWorkerParams>) => void);
|
||||||
|
postMessage(e: ImageBitmapDataURLWorkerResponse): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTransparentBlobFor(
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
): Promise<string> {
|
||||||
|
const id = `${width}-${height}`;
|
||||||
|
if (transparentBlobMap.has(id)) return transparentBlobMap.get(id)!;
|
||||||
|
const offscreen = new OffscreenCanvas(width, height);
|
||||||
|
offscreen.getContext('2d'); // creates rendering context for `converToBlob`
|
||||||
|
const blob = await offscreen.convertToBlob(); // takes a while
|
||||||
|
const arrayBuffer = await blob.arrayBuffer();
|
||||||
|
const base64 = encode(arrayBuffer); // cpu intensive
|
||||||
|
transparentBlobMap.set(id, base64);
|
||||||
|
return base64;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `as any` because: https://github.com/Microsoft/TypeScript/issues/20595
|
||||||
|
const worker: ImageBitmapDataURLResponseWorker = self;
|
||||||
|
|
||||||
|
worker.onmessage = async function (e) {
|
||||||
|
if (!('OffscreenCanvas' in globalThis))
|
||||||
|
return worker.postMessage({ id: e.data.id });
|
||||||
|
|
||||||
|
const { id, bitmap, width, height } = e.data;
|
||||||
|
|
||||||
|
const transparentBase64 = getTransparentBlobFor(width, height);
|
||||||
|
|
||||||
|
const offscreen = new OffscreenCanvas(width, height);
|
||||||
|
const ctx = offscreen.getContext('2d')!;
|
||||||
|
|
||||||
|
ctx.drawImage(bitmap, 0, 0);
|
||||||
|
bitmap.close();
|
||||||
|
const blob = await offscreen.convertToBlob(); // takes a while
|
||||||
|
const type = blob.type;
|
||||||
|
const arrayBuffer = await blob.arrayBuffer();
|
||||||
|
const base64 = encode(arrayBuffer); // cpu intensive
|
||||||
|
|
||||||
|
// on first try we should check if canvas is transparent,
|
||||||
|
// no need to save it's contents in that case
|
||||||
|
if (!lastBlobMap.has(id) && (await transparentBase64) === base64) {
|
||||||
|
lastBlobMap.set(id, base64);
|
||||||
|
return worker.postMessage({ id });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastBlobMap.get(id) === base64) return worker.postMessage({ id }); // unchanged
|
||||||
|
worker.postMessage({
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
base64,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
lastBlobMap.set(id, base64);
|
||||||
|
};
|
||||||
7
packages/rrweb/src/record/workers/tsconfig.json
Normal file
7
packages/rrweb/src/record/workers/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["webworker"]
|
||||||
|
},
|
||||||
|
"exclude": ["workers.d.ts"]
|
||||||
|
}
|
||||||
4
packages/rrweb/src/record/workers/workers.d.ts
vendored
Normal file
4
packages/rrweb/src/record/workers/workers.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
declare module 'web-worker:*' {
|
||||||
|
const WorkerFactory: new () => Worker;
|
||||||
|
export default WorkerFactory;
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Replayer } from '../';
|
import { Replayer } from '../';
|
||||||
import { canvasMutationCommand } from '../../types';
|
import { canvasMutationCommand } from '../../types';
|
||||||
|
import { deserializeArg } from './deserialize-args';
|
||||||
|
|
||||||
export default function canvasMutation({
|
export default async function canvasMutation({
|
||||||
event,
|
event,
|
||||||
mutation,
|
mutation,
|
||||||
target,
|
target,
|
||||||
@@ -13,7 +14,7 @@ export default function canvasMutation({
|
|||||||
target: HTMLCanvasElement;
|
target: HTMLCanvasElement;
|
||||||
imageMap: Replayer['imageMap'];
|
imageMap: Replayer['imageMap'];
|
||||||
errorHandler: Replayer['warnCanvasMutationFailed'];
|
errorHandler: Replayer['warnCanvasMutationFailed'];
|
||||||
}): void {
|
}): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const ctx = target.getContext('2d')!;
|
const ctx = target.getContext('2d')!;
|
||||||
|
|
||||||
@@ -37,10 +38,12 @@ export default function canvasMutation({
|
|||||||
typeof mutation.args[0] === 'string'
|
typeof mutation.args[0] === 'string'
|
||||||
) {
|
) {
|
||||||
const image = imageMap.get(event);
|
const image = imageMap.get(event);
|
||||||
mutation.args[0] = image;
|
|
||||||
original.apply(ctx, mutation.args);
|
original.apply(ctx, mutation.args);
|
||||||
} else {
|
} else {
|
||||||
original.apply(ctx, mutation.args);
|
const args = await Promise.all(
|
||||||
|
mutation.args.map(deserializeArg(imageMap, ctx)),
|
||||||
|
);
|
||||||
|
original.apply(ctx, args);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorHandler(mutation, error);
|
errorHandler(mutation, error);
|
||||||
|
|||||||
92
packages/rrweb/src/replay/canvas/deserialize-args.ts
Normal file
92
packages/rrweb/src/replay/canvas/deserialize-args.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { decode } from 'base64-arraybuffer';
|
||||||
|
import type { Replayer } from '../';
|
||||||
|
import { CanvasArg, SerializedCanvasArg } from '../../types';
|
||||||
|
|
||||||
|
// TODO: add ability to wipe this list
|
||||||
|
type GLVarMap = Map<string, any[]>;
|
||||||
|
const webGLVarMap: Map<
|
||||||
|
CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext,
|
||||||
|
GLVarMap
|
||||||
|
> = new Map();
|
||||||
|
export function variableListFor(
|
||||||
|
ctx:
|
||||||
|
| CanvasRenderingContext2D
|
||||||
|
| WebGLRenderingContext
|
||||||
|
| WebGL2RenderingContext,
|
||||||
|
ctor: string,
|
||||||
|
) {
|
||||||
|
let contextMap = webGLVarMap.get(ctx);
|
||||||
|
if (!contextMap) {
|
||||||
|
contextMap = new Map();
|
||||||
|
webGLVarMap.set(ctx, contextMap);
|
||||||
|
}
|
||||||
|
if (!contextMap.has(ctor)) {
|
||||||
|
contextMap.set(ctor, []);
|
||||||
|
}
|
||||||
|
return contextMap.get(ctor) as any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSerializedArg(arg: unknown): arg is SerializedCanvasArg {
|
||||||
|
return Boolean(arg && typeof arg === 'object' && 'rr_type' in arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deserializeArg(
|
||||||
|
imageMap: Replayer['imageMap'],
|
||||||
|
ctx:
|
||||||
|
| CanvasRenderingContext2D
|
||||||
|
| WebGLRenderingContext
|
||||||
|
| WebGL2RenderingContext
|
||||||
|
| null,
|
||||||
|
preload?: {
|
||||||
|
isUnchanged: boolean;
|
||||||
|
},
|
||||||
|
): (arg: CanvasArg) => Promise<any> {
|
||||||
|
return async (arg: CanvasArg): Promise<any> => {
|
||||||
|
if (arg && typeof arg === 'object' && 'rr_type' in arg) {
|
||||||
|
if (preload) preload.isUnchanged = false;
|
||||||
|
if (arg.rr_type === 'ImageBitmap' && 'args' in arg) {
|
||||||
|
const args = await deserializeArg(imageMap, ctx, preload)(arg.args);
|
||||||
|
return await createImageBitmap.apply(null, args);
|
||||||
|
} else if ('index' in arg) {
|
||||||
|
if (preload || ctx === null) return arg; // we are preloading, ctx is unknown
|
||||||
|
const { rr_type: name, index } = arg;
|
||||||
|
return variableListFor(ctx, name)[index];
|
||||||
|
} else if ('args' in arg) {
|
||||||
|
const { rr_type: name, args } = arg;
|
||||||
|
const ctor = window[name as keyof Window];
|
||||||
|
|
||||||
|
return new ctor(
|
||||||
|
...(await Promise.all(
|
||||||
|
args.map(deserializeArg(imageMap, ctx, preload)),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
} else if ('base64' in arg) {
|
||||||
|
return decode(arg.base64);
|
||||||
|
} else if ('src' in arg) {
|
||||||
|
const image = imageMap.get(arg.src);
|
||||||
|
if (image) {
|
||||||
|
return image;
|
||||||
|
} else {
|
||||||
|
const image = new Image();
|
||||||
|
image.src = arg.src;
|
||||||
|
imageMap.set(arg.src, image);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
} else if ('data' in arg && arg.rr_type === 'Blob') {
|
||||||
|
const blobContents = await Promise.all(
|
||||||
|
arg.data.map(deserializeArg(imageMap, ctx, preload)),
|
||||||
|
);
|
||||||
|
const blob = new Blob(blobContents, {
|
||||||
|
type: arg.type,
|
||||||
|
});
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(arg)) {
|
||||||
|
const result = await Promise.all(
|
||||||
|
arg.map(deserializeArg(imageMap, ctx, preload)),
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,48 +3,59 @@ import {
|
|||||||
CanvasContext,
|
CanvasContext,
|
||||||
canvasMutationCommand,
|
canvasMutationCommand,
|
||||||
canvasMutationData,
|
canvasMutationData,
|
||||||
|
canvasMutationParam,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import webglMutation from './webgl';
|
import webglMutation from './webgl';
|
||||||
import canvas2DMutation from './2d';
|
import canvas2DMutation from './2d';
|
||||||
|
|
||||||
export default function canvasMutation({
|
export default async function canvasMutation({
|
||||||
event,
|
event,
|
||||||
mutation,
|
mutation,
|
||||||
target,
|
target,
|
||||||
imageMap,
|
imageMap,
|
||||||
|
canvasEventMap,
|
||||||
errorHandler,
|
errorHandler,
|
||||||
}: {
|
}: {
|
||||||
event: Parameters<Replayer['applyIncremental']>[0];
|
event: Parameters<Replayer['applyIncremental']>[0];
|
||||||
mutation: canvasMutationData;
|
mutation: canvasMutationData;
|
||||||
target: HTMLCanvasElement;
|
target: HTMLCanvasElement;
|
||||||
imageMap: Replayer['imageMap'];
|
imageMap: Replayer['imageMap'];
|
||||||
|
canvasEventMap: Replayer['canvasEventMap'];
|
||||||
errorHandler: Replayer['warnCanvasMutationFailed'];
|
errorHandler: Replayer['warnCanvasMutationFailed'];
|
||||||
}): void {
|
}): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const mutations: canvasMutationCommand[] =
|
let precomputedMutation: canvasMutationParam =
|
||||||
'commands' in mutation ? mutation.commands : [mutation];
|
canvasEventMap.get(event) || mutation;
|
||||||
|
|
||||||
|
const commands: canvasMutationCommand[] =
|
||||||
|
'commands' in precomputedMutation
|
||||||
|
? precomputedMutation.commands
|
||||||
|
: [precomputedMutation];
|
||||||
|
|
||||||
if ([CanvasContext.WebGL, CanvasContext.WebGL2].includes(mutation.type)) {
|
if ([CanvasContext.WebGL, CanvasContext.WebGL2].includes(mutation.type)) {
|
||||||
return mutations.forEach((command) => {
|
for (let i = 0; i < commands.length; i++) {
|
||||||
webglMutation({
|
const command = commands[i];
|
||||||
|
await webglMutation({
|
||||||
mutation: command,
|
mutation: command,
|
||||||
type: mutation.type,
|
type: mutation.type,
|
||||||
target,
|
target,
|
||||||
imageMap,
|
imageMap,
|
||||||
errorHandler,
|
errorHandler,
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// default is '2d' for backwards compatibility (rrweb below 1.1.x)
|
// default is '2d' for backwards compatibility (rrweb below 1.1.x)
|
||||||
return mutations.forEach((command) => {
|
for (let i = 0; i < commands.length; i++) {
|
||||||
canvas2DMutation({
|
const command = commands[i];
|
||||||
|
await canvas2DMutation({
|
||||||
event,
|
event,
|
||||||
mutation: command,
|
mutation: command,
|
||||||
target,
|
target,
|
||||||
imageMap,
|
imageMap,
|
||||||
errorHandler,
|
errorHandler,
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorHandler(mutation, error);
|
errorHandler(mutation, error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,6 @@
|
|||||||
import { decode } from 'base64-arraybuffer';
|
import type { Replayer } from '../';
|
||||||
import { Replayer } from '../';
|
import { CanvasContext, canvasMutationCommand } from '../../types';
|
||||||
import {
|
import { deserializeArg, variableListFor } from './deserialize-args';
|
||||||
CanvasContext,
|
|
||||||
canvasMutationCommand,
|
|
||||||
SerializedWebGlArg,
|
|
||||||
} from '../../types';
|
|
||||||
|
|
||||||
// TODO: add ability to wipe this list
|
|
||||||
type GLVarMap = Map<string, any[]>;
|
|
||||||
const webGLVarMap: Map<
|
|
||||||
WebGLRenderingContext | WebGL2RenderingContext,
|
|
||||||
GLVarMap
|
|
||||||
> = new Map();
|
|
||||||
export function variableListFor(
|
|
||||||
ctx: WebGLRenderingContext | WebGL2RenderingContext,
|
|
||||||
ctor: string,
|
|
||||||
) {
|
|
||||||
let contextMap = webGLVarMap.get(ctx);
|
|
||||||
if (!contextMap) {
|
|
||||||
contextMap = new Map();
|
|
||||||
webGLVarMap.set(ctx, contextMap);
|
|
||||||
}
|
|
||||||
if (!contextMap.has(ctor)) {
|
|
||||||
contextMap.set(ctor, []);
|
|
||||||
}
|
|
||||||
return contextMap.get(ctor) as any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getContext(
|
function getContext(
|
||||||
target: HTMLCanvasElement,
|
target: HTMLCanvasElement,
|
||||||
@@ -72,41 +47,7 @@ function saveToWebGLVarMap(
|
|||||||
if (!variables.includes(result)) variables.push(result);
|
if (!variables.includes(result)) variables.push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deserializeArg(
|
export default async function webglMutation({
|
||||||
imageMap: Replayer['imageMap'],
|
|
||||||
ctx: WebGLRenderingContext | WebGL2RenderingContext,
|
|
||||||
): (arg: SerializedWebGlArg) => any {
|
|
||||||
return (arg: SerializedWebGlArg): any => {
|
|
||||||
if (arg && typeof arg === 'object' && 'rr_type' in arg) {
|
|
||||||
if ('index' in arg) {
|
|
||||||
const { rr_type: name, index } = arg;
|
|
||||||
return variableListFor(ctx, name)[index];
|
|
||||||
} else if ('args' in arg) {
|
|
||||||
const { rr_type: name, args } = arg;
|
|
||||||
const ctor = window[name as keyof Window];
|
|
||||||
|
|
||||||
return new ctor(...args.map(deserializeArg(imageMap, ctx)));
|
|
||||||
} else if ('base64' in arg) {
|
|
||||||
return decode(arg.base64);
|
|
||||||
} else if ('src' in arg) {
|
|
||||||
const image = imageMap.get(arg.src);
|
|
||||||
if (image) {
|
|
||||||
return image;
|
|
||||||
} else {
|
|
||||||
const image = new Image();
|
|
||||||
image.src = arg.src;
|
|
||||||
imageMap.set(arg.src, image);
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (Array.isArray(arg)) {
|
|
||||||
return arg.map(deserializeArg(imageMap, ctx));
|
|
||||||
}
|
|
||||||
return arg;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function webglMutation({
|
|
||||||
mutation,
|
mutation,
|
||||||
target,
|
target,
|
||||||
type,
|
type,
|
||||||
@@ -118,7 +59,7 @@ export default function webglMutation({
|
|||||||
type: CanvasContext;
|
type: CanvasContext;
|
||||||
imageMap: Replayer['imageMap'];
|
imageMap: Replayer['imageMap'];
|
||||||
errorHandler: Replayer['warnCanvasMutationFailed'];
|
errorHandler: Replayer['warnCanvasMutationFailed'];
|
||||||
}): void {
|
}): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const ctx = getContext(target, type);
|
const ctx = getContext(target, type);
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
@@ -137,7 +78,9 @@ export default function webglMutation({
|
|||||||
mutation.property as Exclude<keyof typeof ctx, 'canvas'>
|
mutation.property as Exclude<keyof typeof ctx, 'canvas'>
|
||||||
] as Function;
|
] as Function;
|
||||||
|
|
||||||
const args = mutation.args.map(deserializeArg(imageMap, ctx));
|
const args = await Promise.all(
|
||||||
|
mutation.args.map(deserializeArg(imageMap, ctx)),
|
||||||
|
);
|
||||||
const result = original.apply(ctx, args);
|
const result = original.apply(ctx, args);
|
||||||
saveToWebGLVarMap(ctx, result);
|
saveToWebGLVarMap(ctx, result);
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
mouseMovePos,
|
mouseMovePos,
|
||||||
IWindow,
|
IWindow,
|
||||||
canvasMutationCommand,
|
canvasMutationCommand,
|
||||||
|
canvasMutationParam,
|
||||||
textMutation,
|
textMutation,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import {
|
import {
|
||||||
@@ -64,6 +65,7 @@ import {
|
|||||||
getPositionsAndIndex,
|
getPositionsAndIndex,
|
||||||
} from './virtual-styles';
|
} from './virtual-styles';
|
||||||
import canvasMutation from './canvas';
|
import canvasMutation from './canvas';
|
||||||
|
import { deserializeArg } from './canvas/deserialize-args';
|
||||||
|
|
||||||
const SKIP_TIME_THRESHOLD = 10 * 1000;
|
const SKIP_TIME_THRESHOLD = 10 * 1000;
|
||||||
const SKIP_TIME_INTERVAL = 5 * 1000;
|
const SKIP_TIME_INTERVAL = 5 * 1000;
|
||||||
@@ -123,6 +125,7 @@ export class Replayer {
|
|||||||
private cache: BuildCache = createCache();
|
private cache: BuildCache = createCache();
|
||||||
|
|
||||||
private imageMap: Map<eventWithTime | string, HTMLImageElement> = new Map();
|
private imageMap: Map<eventWithTime | string, HTMLImageElement> = new Map();
|
||||||
|
private canvasEventMap: Map<eventWithTime, canvasMutationParam> = new Map();
|
||||||
|
|
||||||
private mirror: Mirror = createMirror();
|
private mirror: Mirror = createMirror();
|
||||||
|
|
||||||
@@ -854,24 +857,30 @@ export class Replayer {
|
|||||||
/**
|
/**
|
||||||
* pause when there are some canvas drawImage args need to be loaded
|
* pause when there are some canvas drawImage args need to be loaded
|
||||||
*/
|
*/
|
||||||
private preloadAllImages() {
|
private async preloadAllImages(): Promise<void[]> {
|
||||||
let beforeLoadState = this.service.state;
|
let beforeLoadState = this.service.state;
|
||||||
const stateHandler = () => {
|
const stateHandler = () => {
|
||||||
beforeLoadState = this.service.state;
|
beforeLoadState = this.service.state;
|
||||||
};
|
};
|
||||||
this.emitter.on(ReplayerEvents.Start, stateHandler);
|
this.emitter.on(ReplayerEvents.Start, stateHandler);
|
||||||
this.emitter.on(ReplayerEvents.Pause, stateHandler);
|
this.emitter.on(ReplayerEvents.Pause, stateHandler);
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
for (const event of this.service.state.context.events) {
|
for (const event of this.service.state.context.events) {
|
||||||
if (
|
if (
|
||||||
event.type === EventType.IncrementalSnapshot &&
|
event.type === EventType.IncrementalSnapshot &&
|
||||||
event.data.source === IncrementalSource.CanvasMutation
|
event.data.source === IncrementalSource.CanvasMutation
|
||||||
)
|
) {
|
||||||
if ('commands' in event.data) {
|
promises.push(
|
||||||
event.data.commands.forEach((c) => this.preloadImages(c, event));
|
this.deserializeAndPreloadCanvasEvents(event.data, event),
|
||||||
} else {
|
);
|
||||||
this.preloadImages(event.data, event);
|
const commands =
|
||||||
}
|
'commands' in event.data ? event.data.commands : [event.data];
|
||||||
|
commands.forEach((c) => {
|
||||||
|
this.preloadImages(c, event);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
private preloadImages(data: canvasMutationCommand, event: eventWithTime) {
|
private preloadImages(data: canvasMutationCommand, event: eventWithTime) {
|
||||||
@@ -886,12 +895,34 @@ export class Replayer {
|
|||||||
let d = imgd?.data;
|
let d = imgd?.data;
|
||||||
d = JSON.parse(data.args[0]);
|
d = JSON.parse(data.args[0]);
|
||||||
ctx?.putImageData(imgd!, 0, 0);
|
ctx?.putImageData(imgd!, 0, 0);
|
||||||
} else if (this.hasImageArg(data.args)) {
|
}
|
||||||
this.getImageArgs(data.args).forEach((url) => {
|
}
|
||||||
const image = new Image();
|
private async deserializeAndPreloadCanvasEvents(
|
||||||
image.src = url; // this preloads the image
|
data: canvasMutationData,
|
||||||
this.imageMap.set(url, image);
|
event: eventWithTime,
|
||||||
});
|
) {
|
||||||
|
if (!this.canvasEventMap.has(event)) {
|
||||||
|
const status = {
|
||||||
|
isUnchanged: true,
|
||||||
|
};
|
||||||
|
if ('commands' in data) {
|
||||||
|
const commands = await Promise.all(
|
||||||
|
data.commands.map(async (c) => {
|
||||||
|
const args = await Promise.all(
|
||||||
|
c.args.map(deserializeArg(this.imageMap, null, status)),
|
||||||
|
);
|
||||||
|
return { ...c, args };
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (status.isUnchanged === false)
|
||||||
|
this.canvasEventMap.set(event, { ...data, commands });
|
||||||
|
} else {
|
||||||
|
const args = await Promise.all(
|
||||||
|
data.args.map(deserializeArg(this.imageMap, null, status)),
|
||||||
|
);
|
||||||
|
if (status.isUnchanged === false)
|
||||||
|
this.canvasEventMap.set(event, { ...data, args });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1282,6 +1313,7 @@ export class Replayer {
|
|||||||
mutation: d,
|
mutation: d,
|
||||||
target: target as HTMLCanvasElement,
|
target: target as HTMLCanvasElement,
|
||||||
imageMap: this.imageMap,
|
imageMap: this.imageMap,
|
||||||
|
canvasEventMap: this.canvasEventMap,
|
||||||
errorHandler: this.warnCanvasMutationFailed.bind(this),
|
errorHandler: this.warnCanvasMutationFailed.bind(this),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,12 @@ export type SamplingStrategy = Partial<{
|
|||||||
* 'last' will only record the last input value while input a sequence of chars
|
* 'last' will only record the last input value while input a sequence of chars
|
||||||
*/
|
*/
|
||||||
input: 'all' | 'last';
|
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 type RecordPlugin<TOptions = unknown> = {
|
export type RecordPlugin<TOptions = unknown> = {
|
||||||
@@ -416,28 +422,36 @@ export enum CanvasContext {
|
|||||||
WebGL2,
|
WebGL2,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedWebGlArg =
|
export type SerializedCanvasArg =
|
||||||
| {
|
| {
|
||||||
rr_type: 'ArrayBuffer';
|
rr_type: 'ArrayBuffer';
|
||||||
base64: string; // base64
|
base64: string; // base64
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
rr_type: 'Blob';
|
||||||
|
data: Array<CanvasArg>;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
rr_type: string;
|
rr_type: string;
|
||||||
src: string; // url of image
|
src: string; // url of image
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
rr_type: string;
|
rr_type: string;
|
||||||
args: SerializedWebGlArg[];
|
args: Array<CanvasArg>;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
rr_type: string;
|
rr_type: string;
|
||||||
index: number;
|
index: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export type CanvasArg =
|
||||||
|
| SerializedCanvasArg
|
||||||
| string
|
| string
|
||||||
| number
|
| number
|
||||||
| boolean
|
| boolean
|
||||||
| null
|
| null
|
||||||
| SerializedWebGlArg[];
|
| CanvasArg[];
|
||||||
|
|
||||||
type mouseInteractionParam = {
|
type mouseInteractionParam = {
|
||||||
type: MouseInteractions;
|
type: MouseInteractions;
|
||||||
@@ -516,6 +530,25 @@ export type canvasManagerMutationCallback = (
|
|||||||
p: canvasMutationWithType,
|
p: canvasMutationWithType,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
|
export type ImageBitmapDataURLWorkerParams = {
|
||||||
|
id: number;
|
||||||
|
bitmap: ImageBitmap;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ImageBitmapDataURLWorkerResponse =
|
||||||
|
| {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
base64: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type fontParam = {
|
export type fontParam = {
|
||||||
family: string;
|
family: string;
|
||||||
fontSource: string;
|
fontSource: string;
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ describe('e2e webgl', () => {
|
|||||||
});
|
});
|
||||||
replayer.play(500);
|
replayer.play(500);
|
||||||
`);
|
`);
|
||||||
await page.waitForTimeout(50);
|
await waitForRAF(page);
|
||||||
|
|
||||||
const element = await page.$('iframe');
|
const element = await page.$('iframe');
|
||||||
const frameImage = await element!.screenshot();
|
const frameImage = await element!.screenshot();
|
||||||
@@ -165,7 +165,7 @@ describe('e2e webgl', () => {
|
|||||||
// wait for iframe to get added and `preloadAllImages` to ge called
|
// wait for iframe to get added and `preloadAllImages` to ge called
|
||||||
await page.waitForSelector('iframe');
|
await page.waitForSelector('iframe');
|
||||||
await page.evaluate(`replayer.play(500);`);
|
await page.evaluate(`replayer.play(500);`);
|
||||||
await page.waitForTimeout(50);
|
await waitForRAF(page);
|
||||||
|
|
||||||
const element = await page.$('iframe');
|
const element = await page.$('iframe');
|
||||||
const frameImage = await element!.screenshot();
|
const frameImage = await element!.screenshot();
|
||||||
|
|||||||
@@ -32,79 +32,84 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
// example from https://www.creativebloq.com/javascript/get-started-webgl-draw-square-7112981
|
setTimeout(() => {
|
||||||
const canvas = document.getElementById('myCanvas');
|
// example from https://www.creativebloq.com/javascript/get-started-webgl-draw-square-7112981
|
||||||
const ctx = canvas.getContext('webgl2');
|
const canvas = document.getElementById('myCanvas');
|
||||||
ctx.viewport(0, 0, canvas.width, canvas.height);
|
const ctx = canvas.getContext('webgl2');
|
||||||
ctx.clearColor(0, 0.5, 0, 1);
|
ctx.viewport(0, 0, canvas.width, canvas.height);
|
||||||
ctx.clear(ctx.COLOR_BUFFER_BIT);
|
ctx.clearColor(0, 0.5, 0, 1);
|
||||||
|
ctx.clear(ctx.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
const v = document.getElementById('vertex').firstChild.nodeValue;
|
const v = document.getElementById('vertex').firstChild.nodeValue;
|
||||||
const f = document.getElementById('fragment').firstChild.nodeValue;
|
const f = document.getElementById('fragment').firstChild.nodeValue;
|
||||||
|
|
||||||
const vs = ctx.createShader(ctx.VERTEX_SHADER);
|
const vs = ctx.createShader(ctx.VERTEX_SHADER);
|
||||||
ctx.shaderSource(vs, v);
|
ctx.shaderSource(vs, v);
|
||||||
ctx.compileShader(vs);
|
ctx.compileShader(vs);
|
||||||
|
|
||||||
const fs = ctx.createShader(ctx.FRAGMENT_SHADER);
|
const fs = ctx.createShader(ctx.FRAGMENT_SHADER);
|
||||||
ctx.shaderSource(fs, f);
|
ctx.shaderSource(fs, f);
|
||||||
ctx.compileShader(fs);
|
ctx.compileShader(fs);
|
||||||
|
|
||||||
program = ctx.createProgram();
|
program = ctx.createProgram();
|
||||||
ctx.attachShader(program, vs);
|
ctx.attachShader(program, vs);
|
||||||
ctx.attachShader(program, fs);
|
ctx.attachShader(program, fs);
|
||||||
ctx.linkProgram(program);
|
ctx.linkProgram(program);
|
||||||
|
|
||||||
if (!ctx.getShaderParameter(vs, ctx.COMPILE_STATUS))
|
if (!ctx.getShaderParameter(vs, ctx.COMPILE_STATUS))
|
||||||
console.log(ctx.getShaderInfoLog(vs));
|
console.log(ctx.getShaderInfoLog(vs));
|
||||||
|
|
||||||
if (!ctx.getShaderParameter(fs, ctx.COMPILE_STATUS))
|
if (!ctx.getShaderParameter(fs, ctx.COMPILE_STATUS))
|
||||||
console.log(ctx.getShaderInfoLog(fs));
|
console.log(ctx.getShaderInfoLog(fs));
|
||||||
|
|
||||||
if (!ctx.getProgramParameter(program, ctx.LINK_STATUS))
|
if (!ctx.getProgramParameter(program, ctx.LINK_STATUS))
|
||||||
console.log(ctx.getProgramInfoLog(program));
|
console.log(ctx.getProgramInfoLog(program));
|
||||||
|
|
||||||
const aspect = canvas.width / canvas.height;
|
const aspect = canvas.width / canvas.height;
|
||||||
|
|
||||||
const vertices = new Float32Array([
|
const vertices = new Float32Array([
|
||||||
-0.5,
|
-0.5,
|
||||||
0.5 * aspect,
|
0.5 * aspect,
|
||||||
0.5,
|
0.5,
|
||||||
0.5 * aspect,
|
0.5 * aspect,
|
||||||
0.5,
|
0.5,
|
||||||
-0.5 * aspect, // Triangle 1
|
-0.5 * aspect, // Triangle 1
|
||||||
-0.5,
|
-0.5,
|
||||||
0.5 * aspect,
|
0.5 * aspect,
|
||||||
0.5,
|
0.5,
|
||||||
-0.5 * aspect,
|
-0.5 * aspect,
|
||||||
-0.5,
|
-0.5,
|
||||||
-0.5 * aspect, // Triangle 2
|
-0.5 * aspect, // Triangle 2
|
||||||
]);
|
]);
|
||||||
|
|
||||||
vbuffer = ctx.createBuffer();
|
vbuffer = ctx.createBuffer();
|
||||||
ctx.bindBuffer(ctx.ARRAY_BUFFER, vbuffer);
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, vbuffer);
|
||||||
ctx.bufferData(ctx.ARRAY_BUFFER, vertices, ctx.STATIC_DRAW);
|
ctx.bufferData(ctx.ARRAY_BUFFER, vertices, ctx.STATIC_DRAW);
|
||||||
|
|
||||||
itemSize = 2;
|
itemSize = 2;
|
||||||
numItems = vertices.length / itemSize;
|
numItems = vertices.length / itemSize;
|
||||||
|
|
||||||
ctx.useProgram(program);
|
ctx.useProgram(program);
|
||||||
|
|
||||||
const uColor = ctx.getUniformLocation(program, 'uColor');
|
const uColor = ctx.getUniformLocation(program, 'uColor');
|
||||||
ctx.uniform4fv(uColor, [0.0, 0.3, 0.0, 1.0]);
|
ctx.uniform4fv(uColor, [0.0, 0.3, 0.0, 1.0]);
|
||||||
|
|
||||||
const aVertexPosition = ctx.getAttribLocation(program, 'aVertexPosition');
|
const aVertexPosition = ctx.getAttribLocation(
|
||||||
ctx.enableVertexAttribArray(aVertexPosition);
|
program,
|
||||||
ctx.vertexAttribPointer(
|
'aVertexPosition',
|
||||||
aVertexPosition,
|
);
|
||||||
itemSize,
|
ctx.enableVertexAttribArray(aVertexPosition);
|
||||||
ctx.FLOAT,
|
ctx.vertexAttribPointer(
|
||||||
false,
|
aVertexPosition,
|
||||||
0,
|
itemSize,
|
||||||
0,
|
ctx.FLOAT,
|
||||||
);
|
false,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
ctx.drawArrays(ctx.TRIANGLES, 0, numItems);
|
ctx.drawArrays(ctx.TRIANGLES, 0, numItems);
|
||||||
|
}, 1);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
IncrementalSource,
|
IncrementalSource,
|
||||||
styleSheetRuleData,
|
styleSheetRuleData,
|
||||||
} from '../src/types';
|
} from '../src/types';
|
||||||
import { assertSnapshot, launchPuppeteer } from './utils';
|
import { assertSnapshot, launchPuppeteer, waitForRAF } from './utils';
|
||||||
|
|
||||||
interface ISuite {
|
interface ISuite {
|
||||||
code: string;
|
code: string;
|
||||||
@@ -368,7 +368,7 @@ describe('record iframes', function (this: ISuite) {
|
|||||||
emit: ((window as unknown) as IWindow).emit,
|
emit: ((window as unknown) as IWindow).emit,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await ctx.page.waitForTimeout(10);
|
await waitForRAF(ctx.page);
|
||||||
// console.log(JSON.stringify(ctx.events));
|
// console.log(JSON.stringify(ctx.events));
|
||||||
|
|
||||||
expect(ctx.events.length).toEqual(3);
|
expect(ctx.events.length).toEqual(3);
|
||||||
|
|||||||
@@ -1,5 +1,164 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
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\\": [],
|
||||||
|
\\"id\\": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"body\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"canvas\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"id\\": \\"canvas\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"id\\": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\\\n \\\\n \\",
|
||||||
|
\\"id\\": 8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 1
|
||||||
|
},
|
||||||
|
\\"initialOffset\\": {
|
||||||
|
\\"left\\": 0,
|
||||||
|
\\"top\\": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 9,
|
||||||
|
\\"id\\": 7,
|
||||||
|
\\"type\\": 0,
|
||||||
|
\\"commands\\": [
|
||||||
|
{
|
||||||
|
\\"property\\": \\"clearRect\\",
|
||||||
|
\\"args\\": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
300,
|
||||||
|
150
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"property\\": \\"drawImage\\",
|
||||||
|
\\"args\\": [
|
||||||
|
{
|
||||||
|
\\"rr_type\\": \\"ImageBitmap\\",
|
||||||
|
\\"args\\": [
|
||||||
|
{
|
||||||
|
\\"rr_type\\": \\"Blob\\",
|
||||||
|
\\"data\\": [
|
||||||
|
{
|
||||||
|
\\"rr_type\\": \\"ArrayBuffer\\",
|
||||||
|
\\"base64\\": \\"base64-0\\"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"type\\": \\"image/png\\"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 9,
|
||||||
|
\\"id\\": 7,
|
||||||
|
\\"type\\": 0,
|
||||||
|
\\"commands\\": [
|
||||||
|
{
|
||||||
|
\\"property\\": \\"clearRect\\",
|
||||||
|
\\"args\\": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
300,
|
||||||
|
150
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"property\\": \\"drawImage\\",
|
||||||
|
\\"args\\": [
|
||||||
|
{
|
||||||
|
\\"rr_type\\": \\"ImageBitmap\\",
|
||||||
|
\\"args\\": [
|
||||||
|
{
|
||||||
|
\\"rr_type\\": \\"Blob\\",
|
||||||
|
\\"data\\": [
|
||||||
|
{
|
||||||
|
\\"rr_type\\": \\"ArrayBuffer\\",
|
||||||
|
\\"base64\\": \\"base64-1\\"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"type\\": \\"image/png\\"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`record webgl should batch events by RAF 1`] = `
|
exports[`record webgl should batch events by RAF 1`] = `
|
||||||
"[
|
"[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -149,6 +149,16 @@ describe('serializeArg', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 () => {
|
it('should serialize ImageData', async () => {
|
||||||
const arr = new Uint8ClampedArray(40000);
|
const arr = new Uint8ClampedArray(40000);
|
||||||
|
|
||||||
@@ -176,4 +186,19 @@ describe('serializeArg', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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' }],
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ import {
|
|||||||
IncrementalSource,
|
IncrementalSource,
|
||||||
CanvasContext,
|
CanvasContext,
|
||||||
} from '../../src/types';
|
} from '../../src/types';
|
||||||
import { assertSnapshot, launchPuppeteer, waitForRAF } from '../utils';
|
import {
|
||||||
|
assertSnapshot,
|
||||||
|
launchPuppeteer,
|
||||||
|
stripBase64,
|
||||||
|
waitForRAF,
|
||||||
|
} from '../utils';
|
||||||
import { ICanvas } from 'rrweb-snapshot';
|
import { ICanvas } from 'rrweb-snapshot';
|
||||||
|
|
||||||
interface ISuite {
|
interface ISuite {
|
||||||
@@ -31,7 +36,11 @@ interface IWindow extends Window {
|
|||||||
emit: (e: eventWithTime) => undefined;
|
emit: (e: eventWithTime) => undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const setup = function (this: ISuite, content: string): ISuite {
|
const setup = function (
|
||||||
|
this: ISuite,
|
||||||
|
content: string,
|
||||||
|
canvasSample: 'all' | number = 'all',
|
||||||
|
): ISuite {
|
||||||
const ctx = {} as ISuite;
|
const ctx = {} as ISuite;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@@ -56,13 +65,16 @@ const setup = function (this: ISuite, content: string): ISuite {
|
|||||||
|
|
||||||
ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
|
ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
|
||||||
|
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate((canvasSample) => {
|
||||||
const { record } = ((window as unknown) as IWindow).rrweb;
|
const { record } = ((window as unknown) as IWindow).rrweb;
|
||||||
record({
|
record({
|
||||||
recordCanvas: true,
|
recordCanvas: true,
|
||||||
|
sampling: {
|
||||||
|
canvas: canvasSample,
|
||||||
|
},
|
||||||
emit: ((window as unknown) as IWindow).emit,
|
emit: ((window as unknown) as IWindow).emit,
|
||||||
});
|
});
|
||||||
});
|
}, canvasSample);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -257,4 +269,52 @@ describe('record webgl', function (this: ISuite) {
|
|||||||
assertSnapshot(ctx.events);
|
assertSnapshot(ctx.events);
|
||||||
expect(ctx.events.length).toEqual(5);
|
expect(ctx.events.length).toEqual(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('recordCanvas FPS', function (this: ISuite) {
|
||||||
|
jest.setTimeout(10_000);
|
||||||
|
|
||||||
|
const maxFPS = 60;
|
||||||
|
|
||||||
|
const ctx: ISuite = setup.call(
|
||||||
|
this,
|
||||||
|
`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`,
|
||||||
|
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
|
||||||
|
assertSnapshot(stripBase64(ctx.events));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
* @jest-environment jsdom
|
* @jest-environment jsdom
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { deserializeArg } from '../../src/replay/canvas/deserialize-args';
|
||||||
import { polyfillWebGLGlobals } from '../utils';
|
import { polyfillWebGLGlobals } from '../utils';
|
||||||
polyfillWebGLGlobals();
|
polyfillWebGLGlobals();
|
||||||
|
|
||||||
import { deserializeArg } from '../../src/replay/canvas/webgl';
|
|
||||||
|
|
||||||
let context: WebGLRenderingContext | WebGL2RenderingContext;
|
let context: WebGLRenderingContext | WebGL2RenderingContext;
|
||||||
describe('deserializeArg', () => {
|
describe('deserializeArg', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -14,7 +13,7 @@ describe('deserializeArg', () => {
|
|||||||
});
|
});
|
||||||
it('should deserialize Float32Array values', async () => {
|
it('should deserialize Float32Array values', async () => {
|
||||||
expect(
|
expect(
|
||||||
deserializeArg(
|
await deserializeArg(
|
||||||
new Map(),
|
new Map(),
|
||||||
context,
|
context,
|
||||||
)({
|
)({
|
||||||
@@ -26,7 +25,7 @@ describe('deserializeArg', () => {
|
|||||||
|
|
||||||
it('should deserialize Float64Array values', async () => {
|
it('should deserialize Float64Array values', async () => {
|
||||||
expect(
|
expect(
|
||||||
deserializeArg(
|
await deserializeArg(
|
||||||
new Map(),
|
new Map(),
|
||||||
context,
|
context,
|
||||||
)({
|
)({
|
||||||
@@ -39,7 +38,7 @@ describe('deserializeArg', () => {
|
|||||||
it('should deserialize ArrayBuffer values', async () => {
|
it('should deserialize ArrayBuffer values', async () => {
|
||||||
const contents = [1, 2, 0, 4];
|
const contents = [1, 2, 0, 4];
|
||||||
expect(
|
expect(
|
||||||
deserializeArg(
|
await deserializeArg(
|
||||||
new Map(),
|
new Map(),
|
||||||
context,
|
context,
|
||||||
)({
|
)({
|
||||||
@@ -51,7 +50,7 @@ describe('deserializeArg', () => {
|
|||||||
|
|
||||||
it('should deserialize DataView values', async () => {
|
it('should deserialize DataView values', async () => {
|
||||||
expect(
|
expect(
|
||||||
deserializeArg(
|
await deserializeArg(
|
||||||
new Map(),
|
new Map(),
|
||||||
context,
|
context,
|
||||||
)({
|
)({
|
||||||
@@ -70,7 +69,7 @@ describe('deserializeArg', () => {
|
|||||||
|
|
||||||
it('should leave arrays intact', async () => {
|
it('should leave arrays intact', async () => {
|
||||||
const array = [1, 2, 3, 4];
|
const array = [1, 2, 3, 4];
|
||||||
expect(deserializeArg(new Map(), context)(array)).toEqual(array);
|
expect(await deserializeArg(new Map(), context)(array)).toEqual(array);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should deserialize complex objects', async () => {
|
it('should deserialize complex objects', async () => {
|
||||||
@@ -89,22 +88,20 @@ describe('deserializeArg', () => {
|
|||||||
5,
|
5,
|
||||||
6,
|
6,
|
||||||
];
|
];
|
||||||
expect(deserializeArg(new Map(), context)(serializedArg)).toStrictEqual([
|
expect(
|
||||||
new DataView(new ArrayBuffer(16), 0, 16),
|
await deserializeArg(new Map(), context)(serializedArg),
|
||||||
5,
|
).toStrictEqual([new DataView(new ArrayBuffer(16), 0, 16), 5, 6]);
|
||||||
6,
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should leave null as-is', async () => {
|
it('should leave null as-is', async () => {
|
||||||
expect(deserializeArg(new Map(), context)(null)).toStrictEqual(null);
|
expect(await deserializeArg(new Map(), context)(null)).toStrictEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support HTMLImageElements', async () => {
|
it('should support HTMLImageElements', async () => {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
image.src = 'http://example.com/image.png';
|
image.src = 'http://example.com/image.png';
|
||||||
expect(
|
expect(
|
||||||
deserializeArg(
|
await deserializeArg(
|
||||||
new Map(),
|
new Map(),
|
||||||
context,
|
context,
|
||||||
)({
|
)({
|
||||||
@@ -121,7 +118,7 @@ describe('deserializeArg', () => {
|
|||||||
imageMap.set(image.src, image);
|
imageMap.set(image.src, image);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
deserializeArg(
|
await deserializeArg(
|
||||||
imageMap,
|
imageMap,
|
||||||
context,
|
context,
|
||||||
)({
|
)({
|
||||||
@@ -130,4 +127,77 @@ describe('deserializeArg', () => {
|
|||||||
}),
|
}),
|
||||||
).toBe(image);
|
).toBe(image);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support blobs', async () => {
|
||||||
|
const arrayBuffer = new Uint8Array([1, 2, 0, 4]).buffer;
|
||||||
|
const expected = new Blob([arrayBuffer], { type: 'image/png' });
|
||||||
|
|
||||||
|
const deserialized = await deserializeArg(
|
||||||
|
new Map(),
|
||||||
|
context,
|
||||||
|
)({
|
||||||
|
rr_type: 'Blob',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
rr_type: 'ArrayBuffer',
|
||||||
|
base64: 'AQIABA==',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'image/png',
|
||||||
|
});
|
||||||
|
|
||||||
|
// `expect(blob).toEqual(otherBlob)` doesn't really do anything yet
|
||||||
|
// jest hasn't implemented a propper way to compare blobs
|
||||||
|
// more info: https://github.com/facebook/jest/issues/7372
|
||||||
|
// because JSDOM doesn't support most functions needed for comparison:
|
||||||
|
// more info: https://github.com/jsdom/jsdom/issues/2555
|
||||||
|
expect(deserialized).toEqual(expected);
|
||||||
|
// thats why we test size of the blob as well
|
||||||
|
expect(deserialized.size).toEqual(expected.size);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isUnchanged', () => {
|
||||||
|
it('should set isUnchanged:true when non of the args are changed', async () => {
|
||||||
|
const status = {
|
||||||
|
isUnchanged: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
await deserializeArg(new Map(), context, status)(true);
|
||||||
|
expect(status.isUnchanged).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set isUnchanged: false when args are deserialzed', async () => {
|
||||||
|
const status = {
|
||||||
|
isUnchanged: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
await deserializeArg(
|
||||||
|
new Map(),
|
||||||
|
context,
|
||||||
|
status,
|
||||||
|
)({
|
||||||
|
rr_type: 'Float64Array',
|
||||||
|
args: [[-1, -1, 3, -1, -1, 3]],
|
||||||
|
});
|
||||||
|
expect(status.isUnchanged).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set isUnchanged: false when nested args are deserialzed', async () => {
|
||||||
|
const status = {
|
||||||
|
isUnchanged: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
await deserializeArg(
|
||||||
|
new Map(),
|
||||||
|
context,
|
||||||
|
status,
|
||||||
|
)([
|
||||||
|
{
|
||||||
|
rr_type: 'Float64Array',
|
||||||
|
args: [[-1, -1, 3, -1, -1, 3]],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(status.isUnchanged).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Replayer } from '../../src/replay';
|
|||||||
import {} from '../../src/types';
|
import {} from '../../src/types';
|
||||||
import {
|
import {
|
||||||
CanvasContext,
|
CanvasContext,
|
||||||
SerializedWebGlArg,
|
CanvasArg,
|
||||||
IncrementalSource,
|
IncrementalSource,
|
||||||
EventType,
|
EventType,
|
||||||
eventWithTime,
|
eventWithTime,
|
||||||
@@ -16,9 +16,7 @@ import {
|
|||||||
|
|
||||||
let replayer: Replayer;
|
let replayer: Replayer;
|
||||||
|
|
||||||
const canvasMutationEventWithArgs = (
|
const canvasMutationEventWithArgs = (args: CanvasArg[]): eventWithTime => {
|
||||||
args: SerializedWebGlArg[],
|
|
||||||
): eventWithTime => {
|
|
||||||
return {
|
return {
|
||||||
timestamp: 100,
|
timestamp: 100,
|
||||||
type: EventType.IncrementalSnapshot,
|
type: EventType.IncrementalSnapshot,
|
||||||
@@ -67,11 +65,11 @@ describe('preloadAllImages', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should preload nested image', () => {
|
it('should preload nested image', async () => {
|
||||||
replayer.service.state.context.events = [
|
replayer.service.state.context.events = [
|
||||||
canvasMutationEventWithArgs([
|
canvasMutationEventWithArgs([
|
||||||
{
|
{
|
||||||
rr_type: 'something',
|
rr_type: 'Array',
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
rr_type: 'HTMLImageElement',
|
rr_type: 'HTMLImageElement',
|
||||||
@@ -82,7 +80,7 @@ describe('preloadAllImages', () => {
|
|||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
|
||||||
(replayer as any).preloadAllImages();
|
await (replayer as any).preloadAllImages();
|
||||||
|
|
||||||
const expectedImage = new Image();
|
const expectedImage = new Image();
|
||||||
expectedImage.src = 'http://example.com';
|
expectedImage.src = 'http://example.com';
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
import { polyfillWebGLGlobals } from '../utils';
|
import { polyfillWebGLGlobals } from '../utils';
|
||||||
polyfillWebGLGlobals();
|
polyfillWebGLGlobals();
|
||||||
|
|
||||||
import webglMutation, { variableListFor } from '../../src/replay/canvas/webgl';
|
import webglMutation from '../../src/replay/canvas/webgl';
|
||||||
import { CanvasContext } from '../../src/types';
|
import { CanvasContext } from '../../src/types';
|
||||||
|
import { variableListFor } from '../../src/replay/canvas/deserialize-args';
|
||||||
|
|
||||||
let canvas: HTMLCanvasElement;
|
let canvas: HTMLCanvasElement;
|
||||||
describe('webglMutation', () => {
|
describe('webglMutation', () => {
|
||||||
@@ -30,7 +31,7 @@ describe('webglMutation', () => {
|
|||||||
|
|
||||||
expect(variableListFor(context, 'WebGLShader')).toHaveLength(0);
|
expect(variableListFor(context, 'WebGLShader')).toHaveLength(0);
|
||||||
|
|
||||||
webglMutation({
|
await webglMutation({
|
||||||
mutation: {
|
mutation: {
|
||||||
property: 'createShader',
|
property: 'createShader',
|
||||||
args: [35633],
|
args: [35633],
|
||||||
|
|||||||
@@ -220,6 +220,39 @@ export async function assertDomSnapshot(
|
|||||||
expect(stringifyDomSnapshot(data)).toMatchSnapshot();
|
expect(stringifyDomSnapshot(data)).toMatchSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stripBase64(events: eventWithTime[]) {
|
||||||
|
const base64Strings: string[] = [];
|
||||||
|
function walk<T>(obj: T): T {
|
||||||
|
if (!obj || typeof obj !== 'object') return obj;
|
||||||
|
if (Array.isArray(obj)) return (obj.map((e) => walk(e)) as unknown) as T;
|
||||||
|
const newObj: Partial<T> = {};
|
||||||
|
for (let prop in obj) {
|
||||||
|
const value = obj[prop];
|
||||||
|
if (prop === 'base64' && typeof value === 'string') {
|
||||||
|
let index = base64Strings.indexOf(value);
|
||||||
|
if (index === -1) {
|
||||||
|
index = base64Strings.push(value) - 1;
|
||||||
|
}
|
||||||
|
(newObj as any)[prop] = `base64-${index}`;
|
||||||
|
} else {
|
||||||
|
(newObj as any)[prop] = walk(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newObj as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
return events.map((event) => {
|
||||||
|
if (
|
||||||
|
event.type === EventType.IncrementalSnapshot &&
|
||||||
|
event.data.source === IncrementalSource.CanvasMutation
|
||||||
|
) {
|
||||||
|
const newData = walk(event.data);
|
||||||
|
return { ...event, data: newData };
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
export const sampleEvents: eventWithTime[] = [
|
export const sampleEvents: eventWithTime[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,13 +18,15 @@ export declare class CanvasManager {
|
|||||||
lock(): void;
|
lock(): void;
|
||||||
unlock(): void;
|
unlock(): void;
|
||||||
constructor(options: {
|
constructor(options: {
|
||||||
recordCanvas: boolean | number;
|
recordCanvas: boolean;
|
||||||
mutationCb: canvasMutationCallback;
|
mutationCb: canvasMutationCallback;
|
||||||
win: IWindow;
|
win: IWindow;
|
||||||
blockClass: blockClass;
|
blockClass: blockClass;
|
||||||
mirror: Mirror;
|
mirror: Mirror;
|
||||||
|
sampling?: 'all' | number;
|
||||||
});
|
});
|
||||||
private processMutation;
|
private processMutation;
|
||||||
|
private initCanvasFPSObserver;
|
||||||
private initCanvasMutationObserver;
|
private initCanvasMutationObserver;
|
||||||
private startPendingCanvasMutationFlusher;
|
private startPendingCanvasMutationFlusher;
|
||||||
private startRAFTimestamping;
|
private startRAFTimestamping;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IWindow, SerializedWebGlArg } from '../../../types';
|
import { IWindow, CanvasArg } from '../../../types';
|
||||||
export declare function variableListFor(ctx: WebGLRenderingContext | WebGL2RenderingContext, ctor: string): any[];
|
export declare function variableListFor(ctx: RenderingContext, ctor: string): any[];
|
||||||
export declare const saveWebGLVar: (value: any, win: IWindow, ctx: WebGL2RenderingContext | WebGLRenderingContext) => number | void;
|
export declare const saveWebGLVar: (value: any, win: IWindow, ctx: RenderingContext) => number | void;
|
||||||
export declare function serializeArg(value: any, win: IWindow, ctx: WebGL2RenderingContext | WebGLRenderingContext): SerializedWebGlArg;
|
export declare function serializeArg(value: any, win: IWindow, ctx: RenderingContext): CanvasArg;
|
||||||
export declare const serializeArgs: (args: Array<any>, win: IWindow, ctx: WebGLRenderingContext | WebGL2RenderingContext) => SerializedWebGlArg[];
|
export declare const serializeArgs: (args: Array<any>, win: IWindow, ctx: RenderingContext) => CanvasArg[];
|
||||||
export declare const isInstanceOfWebGLObject: (value: any, win: IWindow) => value is WebGLTexture | WebGLShader | WebGLBuffer | WebGLVertexArrayObject | WebGLProgram | WebGLActiveInfo | WebGLUniformLocation | WebGLFramebuffer | WebGLRenderbuffer | WebGLShaderPrecisionFormat;
|
export declare const isInstanceOfWebGLObject: (value: any, win: IWindow) => value is WebGLTexture | WebGLShader | WebGLBuffer | WebGLVertexArrayObject | WebGLProgram | WebGLActiveInfo | WebGLUniformLocation | WebGLFramebuffer | WebGLRenderbuffer | WebGLShaderPrecisionFormat;
|
||||||
|
|||||||
5
packages/rrweb/typings/record/workers/image-bitmap-data-url-worker.d.ts
vendored
Normal file
5
packages/rrweb/typings/record/workers/image-bitmap-data-url-worker.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { ImageBitmapDataURLWorkerParams, ImageBitmapDataURLWorkerResponse } from '../../types';
|
||||||
|
export interface ImageBitmapDataURLRequestWorker {
|
||||||
|
postMessage: (message: ImageBitmapDataURLWorkerParams, transfer?: [ImageBitmap]) => void;
|
||||||
|
onmessage: (message: MessageEvent<ImageBitmapDataURLWorkerResponse>) => void;
|
||||||
|
}
|
||||||
2
packages/rrweb/typings/replay/canvas/2d.d.ts
vendored
2
packages/rrweb/typings/replay/canvas/2d.d.ts
vendored
@@ -6,4 +6,4 @@ export default function canvasMutation({ event, mutation, target, imageMap, erro
|
|||||||
target: HTMLCanvasElement;
|
target: HTMLCanvasElement;
|
||||||
imageMap: Replayer['imageMap'];
|
imageMap: Replayer['imageMap'];
|
||||||
errorHandler: Replayer['warnCanvasMutationFailed'];
|
errorHandler: Replayer['warnCanvasMutationFailed'];
|
||||||
}): void;
|
}): Promise<void>;
|
||||||
|
|||||||
7
packages/rrweb/typings/replay/canvas/deserialize-args.d.ts
vendored
Normal file
7
packages/rrweb/typings/replay/canvas/deserialize-args.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { Replayer } from '../';
|
||||||
|
import { CanvasArg, SerializedCanvasArg } from '../../types';
|
||||||
|
export declare function variableListFor(ctx: CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext, ctor: string): any[];
|
||||||
|
export declare function isSerializedArg(arg: unknown): arg is SerializedCanvasArg;
|
||||||
|
export declare function deserializeArg(imageMap: Replayer['imageMap'], ctx: CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext | null, preload?: {
|
||||||
|
isUnchanged: boolean;
|
||||||
|
}): (arg: CanvasArg) => Promise<any>;
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Replayer } from '..';
|
import { Replayer } from '..';
|
||||||
import { canvasMutationData } from '../../types';
|
import { canvasMutationData } from '../../types';
|
||||||
export default function canvasMutation({ event, mutation, target, imageMap, errorHandler, }: {
|
export default function canvasMutation({ event, mutation, target, imageMap, canvasEventMap, errorHandler, }: {
|
||||||
event: Parameters<Replayer['applyIncremental']>[0];
|
event: Parameters<Replayer['applyIncremental']>[0];
|
||||||
mutation: canvasMutationData;
|
mutation: canvasMutationData;
|
||||||
target: HTMLCanvasElement;
|
target: HTMLCanvasElement;
|
||||||
imageMap: Replayer['imageMap'];
|
imageMap: Replayer['imageMap'];
|
||||||
|
canvasEventMap: Replayer['canvasEventMap'];
|
||||||
errorHandler: Replayer['warnCanvasMutationFailed'];
|
errorHandler: Replayer['warnCanvasMutationFailed'];
|
||||||
}): void;
|
}): Promise<void>;
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { Replayer } from '../';
|
import type { Replayer } from '../';
|
||||||
import { CanvasContext, canvasMutationCommand, SerializedWebGlArg } from '../../types';
|
import { CanvasContext, canvasMutationCommand } from '../../types';
|
||||||
export declare function variableListFor(ctx: WebGLRenderingContext | WebGL2RenderingContext, ctor: string): any[];
|
|
||||||
export declare function deserializeArg(imageMap: Replayer['imageMap'], ctx: WebGLRenderingContext | WebGL2RenderingContext): (arg: SerializedWebGlArg) => any;
|
|
||||||
export default function webglMutation({ mutation, target, type, imageMap, errorHandler, }: {
|
export default function webglMutation({ mutation, target, type, imageMap, errorHandler, }: {
|
||||||
mutation: canvasMutationCommand;
|
mutation: canvasMutationCommand;
|
||||||
target: HTMLCanvasElement;
|
target: HTMLCanvasElement;
|
||||||
type: CanvasContext;
|
type: CanvasContext;
|
||||||
imageMap: Replayer['imageMap'];
|
imageMap: Replayer['imageMap'];
|
||||||
errorHandler: Replayer['warnCanvasMutationFailed'];
|
errorHandler: Replayer['warnCanvasMutationFailed'];
|
||||||
}): void;
|
}): Promise<void>;
|
||||||
|
|||||||
2
packages/rrweb/typings/replay/index.d.ts
vendored
2
packages/rrweb/typings/replay/index.d.ts
vendored
@@ -22,6 +22,7 @@ export declare class Replayer {
|
|||||||
private virtualStyleRulesMap;
|
private virtualStyleRulesMap;
|
||||||
private cache;
|
private cache;
|
||||||
private imageMap;
|
private imageMap;
|
||||||
|
private canvasEventMap;
|
||||||
private mirror;
|
private mirror;
|
||||||
private firstFullSnapshot;
|
private firstFullSnapshot;
|
||||||
private newDocumentQueue;
|
private newDocumentQueue;
|
||||||
@@ -56,6 +57,7 @@ export declare class Replayer {
|
|||||||
private getImageArgs;
|
private getImageArgs;
|
||||||
private preloadAllImages;
|
private preloadAllImages;
|
||||||
private preloadImages;
|
private preloadImages;
|
||||||
|
private deserializeAndPreloadCanvasEvents;
|
||||||
private applyIncremental;
|
private applyIncremental;
|
||||||
private applyMutation;
|
private applyMutation;
|
||||||
private applyScroll;
|
private applyScroll;
|
||||||
|
|||||||
27
packages/rrweb/typings/types.d.ts
vendored
27
packages/rrweb/typings/types.d.ts
vendored
@@ -124,6 +124,7 @@ export declare type SamplingStrategy = Partial<{
|
|||||||
scroll: number;
|
scroll: number;
|
||||||
media: number;
|
media: number;
|
||||||
input: 'all' | 'last';
|
input: 'all' | 'last';
|
||||||
|
canvas: 'all' | number;
|
||||||
}>;
|
}>;
|
||||||
export declare type RecordPlugin<TOptions = unknown> = {
|
export declare type RecordPlugin<TOptions = unknown> = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -291,19 +292,24 @@ export declare enum CanvasContext {
|
|||||||
WebGL = 1,
|
WebGL = 1,
|
||||||
WebGL2 = 2
|
WebGL2 = 2
|
||||||
}
|
}
|
||||||
export declare type SerializedWebGlArg = {
|
export declare type SerializedCanvasArg = {
|
||||||
rr_type: 'ArrayBuffer';
|
rr_type: 'ArrayBuffer';
|
||||||
base64: string;
|
base64: string;
|
||||||
|
} | {
|
||||||
|
rr_type: 'Blob';
|
||||||
|
data: Array<CanvasArg>;
|
||||||
|
type?: string;
|
||||||
} | {
|
} | {
|
||||||
rr_type: string;
|
rr_type: string;
|
||||||
src: string;
|
src: string;
|
||||||
} | {
|
} | {
|
||||||
rr_type: string;
|
rr_type: string;
|
||||||
args: SerializedWebGlArg[];
|
args: Array<CanvasArg>;
|
||||||
} | {
|
} | {
|
||||||
rr_type: string;
|
rr_type: string;
|
||||||
index: number;
|
index: number;
|
||||||
} | string | number | boolean | null | SerializedWebGlArg[];
|
};
|
||||||
|
export declare type CanvasArg = SerializedCanvasArg | string | number | boolean | null | CanvasArg[];
|
||||||
declare type mouseInteractionParam = {
|
declare type mouseInteractionParam = {
|
||||||
type: MouseInteractions;
|
type: MouseInteractions;
|
||||||
id: number;
|
id: number;
|
||||||
@@ -361,6 +367,21 @@ export declare type canvasMutationWithType = {
|
|||||||
} & canvasMutationCommand;
|
} & canvasMutationCommand;
|
||||||
export declare type canvasMutationCallback = (p: canvasMutationParam) => void;
|
export declare type canvasMutationCallback = (p: canvasMutationParam) => void;
|
||||||
export declare type canvasManagerMutationCallback = (target: HTMLCanvasElement, p: canvasMutationWithType) => void;
|
export declare type canvasManagerMutationCallback = (target: HTMLCanvasElement, p: canvasMutationWithType) => void;
|
||||||
|
export declare type ImageBitmapDataURLWorkerParams = {
|
||||||
|
id: number;
|
||||||
|
bitmap: ImageBitmap;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
export declare type ImageBitmapDataURLWorkerResponse = {
|
||||||
|
id: number;
|
||||||
|
} | {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
base64: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
export declare type fontParam = {
|
export declare type fontParam = {
|
||||||
family: string;
|
family: string;
|
||||||
fontSource: string;
|
fontSource: string;
|
||||||
|
|||||||
193
yarn.lock
193
yarn.lock
@@ -1926,19 +1926,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@polka/url/-/url-0.5.0.tgz#b21510597fd601e5d7c95008b76bf0d254ebfd31"
|
resolved "https://registry.yarnpkg.com/@polka/url/-/url-0.5.0.tgz#b21510597fd601e5d7c95008b76bf0d254ebfd31"
|
||||||
integrity sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==
|
integrity sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==
|
||||||
|
|
||||||
"@rollup/plugin-commonjs@^11.0.0":
|
|
||||||
version "11.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz#60636c7a722f54b41e419e1709df05c7234557ef"
|
|
||||||
integrity sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==
|
|
||||||
dependencies:
|
|
||||||
"@rollup/pluginutils" "^3.0.8"
|
|
||||||
commondir "^1.0.1"
|
|
||||||
estree-walker "^1.0.1"
|
|
||||||
glob "^7.1.2"
|
|
||||||
is-reference "^1.1.2"
|
|
||||||
magic-string "^0.25.2"
|
|
||||||
resolve "^1.11.0"
|
|
||||||
|
|
||||||
"@rollup/plugin-commonjs@^20.0.0":
|
"@rollup/plugin-commonjs@^20.0.0":
|
||||||
version "20.0.0"
|
version "20.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-20.0.0.tgz#3246872dcbcb18a54aaa6277a8c7d7f1b155b745"
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-20.0.0.tgz#3246872dcbcb18a54aaa6277a8c7d7f1b155b745"
|
||||||
@@ -1952,6 +1939,19 @@
|
|||||||
magic-string "^0.25.7"
|
magic-string "^0.25.7"
|
||||||
resolve "^1.17.0"
|
resolve "^1.17.0"
|
||||||
|
|
||||||
|
"@rollup/plugin-commonjs@^21.0.2":
|
||||||
|
version "21.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.2.tgz#0b9c539aa1837c94abfaf87945838b0fc8564891"
|
||||||
|
integrity sha512-d/OmjaLVO4j/aQX69bwpWPpbvI3TJkQuxoAk7BH8ew1PyoMBLTOuvJTjzG8oEoW7drIIqB0KCJtfFLu/2GClWg==
|
||||||
|
dependencies:
|
||||||
|
"@rollup/pluginutils" "^3.1.0"
|
||||||
|
commondir "^1.0.1"
|
||||||
|
estree-walker "^2.0.1"
|
||||||
|
glob "^7.1.6"
|
||||||
|
is-reference "^1.2.1"
|
||||||
|
magic-string "^0.25.7"
|
||||||
|
resolve "^1.17.0"
|
||||||
|
|
||||||
"@rollup/plugin-node-resolve@^13.0.4":
|
"@rollup/plugin-node-resolve@^13.0.4":
|
||||||
version "13.0.6"
|
version "13.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.6.tgz#29629070bb767567be8157f575cfa8f2b8e9ef77"
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.6.tgz#29629070bb767567be8157f575cfa8f2b8e9ef77"
|
||||||
@@ -1964,6 +1964,18 @@
|
|||||||
is-module "^1.0.0"
|
is-module "^1.0.0"
|
||||||
resolve "^1.19.0"
|
resolve "^1.19.0"
|
||||||
|
|
||||||
|
"@rollup/plugin-node-resolve@^13.1.3":
|
||||||
|
version "13.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz#2ed277fb3ad98745424c1d2ba152484508a92d79"
|
||||||
|
integrity sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==
|
||||||
|
dependencies:
|
||||||
|
"@rollup/pluginutils" "^3.1.0"
|
||||||
|
"@types/resolve" "1.17.1"
|
||||||
|
builtin-modules "^3.1.0"
|
||||||
|
deepmerge "^4.2.2"
|
||||||
|
is-module "^1.0.0"
|
||||||
|
resolve "^1.19.0"
|
||||||
|
|
||||||
"@rollup/plugin-node-resolve@^7.0.0":
|
"@rollup/plugin-node-resolve@^7.0.0":
|
||||||
version "7.1.3"
|
version "7.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca"
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca"
|
||||||
@@ -1991,14 +2003,6 @@
|
|||||||
"@rollup/pluginutils" "^3.1.0"
|
"@rollup/pluginutils" "^3.1.0"
|
||||||
resolve "^1.17.0"
|
resolve "^1.17.0"
|
||||||
|
|
||||||
"@rollup/plugin-typescript@^8.3.1":
|
|
||||||
version "8.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.3.1.tgz#b7dc75ed6b4876e260b9e80624fab23bc98e4ac1"
|
|
||||||
integrity sha512-84rExe3ICUBXzqNX48WZV2Jp3OddjTMX97O2Py6D1KJaGSwWp0mDHXj+bCGNJqWHIEKDIT2U0sDjhP4czKi6cA==
|
|
||||||
dependencies:
|
|
||||||
"@rollup/pluginutils" "^3.1.0"
|
|
||||||
resolve "^1.17.0"
|
|
||||||
|
|
||||||
"@rollup/pluginutils@4", "@rollup/pluginutils@^4.1.0":
|
"@rollup/pluginutils@4", "@rollup/pluginutils@^4.1.0":
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec"
|
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec"
|
||||||
@@ -2016,6 +2020,14 @@
|
|||||||
estree-walker "^1.0.1"
|
estree-walker "^1.0.1"
|
||||||
picomatch "^2.2.2"
|
picomatch "^2.2.2"
|
||||||
|
|
||||||
|
"@rollup/pluginutils@^4.1.2":
|
||||||
|
version "4.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.2.tgz#ed5821c15e5e05e32816f5fb9ec607cdf5a75751"
|
||||||
|
integrity sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==
|
||||||
|
dependencies:
|
||||||
|
estree-walker "^2.0.1"
|
||||||
|
picomatch "^2.2.2"
|
||||||
|
|
||||||
"@sinonjs/commons@^1.7.0":
|
"@sinonjs/commons@^1.7.0":
|
||||||
version "1.8.3"
|
version "1.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
|
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
|
||||||
@@ -2248,6 +2260,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/nwsapi/-/nwsapi-2.2.2.tgz#1b1dccfc38b2b7e1b9ea71d5285796878375e862"
|
resolved "https://registry.yarnpkg.com/@types/nwsapi/-/nwsapi-2.2.2.tgz#1b1dccfc38b2b7e1b9ea71d5285796878375e862"
|
||||||
integrity sha512-C4G47l3cAra4729xbhL9y3PjTpO7LJwXd47Fn1mbnZ6WcTkFPo8iDJPyMGCIudxpc7aeM8K1Fmw+lZfOb5ya9g==
|
integrity sha512-C4G47l3cAra4729xbhL9y3PjTpO7LJwXd47Fn1mbnZ6WcTkFPo8iDJPyMGCIudxpc7aeM8K1Fmw+lZfOb5ya9g==
|
||||||
|
|
||||||
|
"@types/offscreencanvas@^2019.6.4":
|
||||||
|
version "2019.6.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/offscreencanvas/-/offscreencanvas-2019.6.4.tgz#64f6d120b53925028299c744fcdd32d2cd525963"
|
||||||
|
integrity sha512-u8SAgdZ8ROtkTF+mfZGOscl0or6BSj9A4g37e6nvxDc+YB/oDut0wHkK2PBBiC2bNR8TS0CPV+1gAk4fNisr1Q==
|
||||||
|
|
||||||
"@types/parse-json@^4.0.0":
|
"@types/parse-json@^4.0.0":
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||||
@@ -2288,9 +2305,9 @@
|
|||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/puppeteer@^5.4.4":
|
"@types/puppeteer@^5.4.4":
|
||||||
version "5.4.4"
|
version "5.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.4.tgz#e92abeccc4f46207c3e1b38934a1246be080ccd0"
|
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.5.tgz#154e3850a77bfd3967f036680de8ddc88eb3a12b"
|
||||||
integrity sha512-3Nau+qi69CN55VwZb0ATtdUAlYlqOOQ3OfQfq0Hqgc4JMFXiQT/XInlwQ9g6LbicDslE6loIFsXFklGh5XmI6Q==
|
integrity sha512-lxCjpDEY+DZ66+W3x5Af4oHnEmUXt0HuaRzkBGE2UZiZEp/V1d3StpLPlmNVu/ea091bdNmVPl44lu8Wy/0ZCA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
@@ -2522,6 +2539,15 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@xstate/fsm/-/fsm-1.6.1.tgz#c92972b835540c4e3c5e14277f40dbcbdaee9571"
|
resolved "https://registry.yarnpkg.com/@xstate/fsm/-/fsm-1.6.1.tgz#c92972b835540c4e3c5e14277f40dbcbdaee9571"
|
||||||
integrity sha512-xYKDNuPR36/fUK+jmhM+oauBmbdUAfuJKnDjg3/7NbN+Pj03TX7e94LXnzkwGgAR+U/HWoMqM5UPTuGIYfIx9g==
|
integrity sha512-xYKDNuPR36/fUK+jmhM+oauBmbdUAfuJKnDjg3/7NbN+Pj03TX7e94LXnzkwGgAR+U/HWoMqM5UPTuGIYfIx9g==
|
||||||
|
|
||||||
|
"@yarn-tool/resolve-package@^1.0.40":
|
||||||
|
version "1.0.45"
|
||||||
|
resolved "https://registry.yarnpkg.com/@yarn-tool/resolve-package/-/resolve-package-1.0.45.tgz#4d9716a67903f46a76c8691eff546dafe55bf66f"
|
||||||
|
integrity sha512-xnfY8JceApkSTliZtr7X6yl1wZYhGbRp0beBMi1OtmvTVTm/ZSt3881Fw1M3ZwhHqr7OEfl8828LJK2q62BvoQ==
|
||||||
|
dependencies:
|
||||||
|
pkg-dir "< 6 >= 5"
|
||||||
|
tslib "^2.3.1"
|
||||||
|
upath2 "^3.1.12"
|
||||||
|
|
||||||
JSONStream@^1.0.4:
|
JSONStream@^1.0.4:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
||||||
@@ -5049,7 +5075,7 @@ finalhandler@~1.1.2:
|
|||||||
statuses "~1.5.0"
|
statuses "~1.5.0"
|
||||||
unpipe "~1.0.0"
|
unpipe "~1.0.0"
|
||||||
|
|
||||||
find-cache-dir@^3.3.1:
|
find-cache-dir@^3.3.1, find-cache-dir@^3.3.2:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b"
|
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b"
|
||||||
integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==
|
integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==
|
||||||
@@ -5073,6 +5099,14 @@ find-up@^4.0.0, find-up@^4.1.0:
|
|||||||
locate-path "^5.0.0"
|
locate-path "^5.0.0"
|
||||||
path-exists "^4.0.0"
|
path-exists "^4.0.0"
|
||||||
|
|
||||||
|
find-up@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
|
||||||
|
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
|
||||||
|
dependencies:
|
||||||
|
locate-path "^6.0.0"
|
||||||
|
path-exists "^4.0.0"
|
||||||
|
|
||||||
findup-sync@~0.3.0:
|
findup-sync@~0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16"
|
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16"
|
||||||
@@ -5168,6 +5202,15 @@ fs-extra@8.1.0:
|
|||||||
jsonfile "^4.0.0"
|
jsonfile "^4.0.0"
|
||||||
universalify "^0.1.0"
|
universalify "^0.1.0"
|
||||||
|
|
||||||
|
fs-extra@^10.0.0:
|
||||||
|
version "10.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.1.tgz#27de43b4320e833f6867cc044bfce29fdf0ef3b8"
|
||||||
|
integrity sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.2.0"
|
||||||
|
jsonfile "^6.0.1"
|
||||||
|
universalify "^2.0.0"
|
||||||
|
|
||||||
fs-extra@^9.1.0:
|
fs-extra@^9.1.0:
|
||||||
version "9.1.0"
|
version "9.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
|
||||||
@@ -6197,7 +6240,7 @@ is-redirect@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
|
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
|
||||||
integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=
|
integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=
|
||||||
|
|
||||||
is-reference@^1.1.2, is-reference@^1.2.1:
|
is-reference@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
|
resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
|
||||||
integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
|
integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
|
||||||
@@ -8047,6 +8090,13 @@ locate-path@^5.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate "^4.1.0"
|
p-locate "^4.1.0"
|
||||||
|
|
||||||
|
locate-path@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
|
||||||
|
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
|
||||||
|
dependencies:
|
||||||
|
p-locate "^5.0.0"
|
||||||
|
|
||||||
lodash._reinterpolate@^3.0.0:
|
lodash._reinterpolate@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||||
@@ -8127,7 +8177,7 @@ lru-cache@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist "^4.0.0"
|
yallist "^4.0.0"
|
||||||
|
|
||||||
magic-string@^0.25.2, magic-string@^0.25.7:
|
magic-string@^0.25.7:
|
||||||
version "0.25.7"
|
version "0.25.7"
|
||||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
|
||||||
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
|
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
|
||||||
@@ -9001,6 +9051,13 @@ p-limit@^2.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-try "^2.0.0"
|
p-try "^2.0.0"
|
||||||
|
|
||||||
|
p-limit@^3.0.2:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
|
||||||
|
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
|
||||||
|
dependencies:
|
||||||
|
yocto-queue "^0.1.0"
|
||||||
|
|
||||||
p-locate@^2.0.0:
|
p-locate@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
|
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
|
||||||
@@ -9015,6 +9072,13 @@ p-locate@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-limit "^2.2.0"
|
p-limit "^2.2.0"
|
||||||
|
|
||||||
|
p-locate@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
|
||||||
|
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
|
||||||
|
dependencies:
|
||||||
|
p-limit "^3.0.2"
|
||||||
|
|
||||||
p-map-series@^2.1.0:
|
p-map-series@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-2.1.0.tgz#7560d4c452d9da0c07e692fdbfe6e2c81a2a91f2"
|
resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-2.1.0.tgz#7560d4c452d9da0c07e692fdbfe6e2c81a2a91f2"
|
||||||
@@ -9196,6 +9260,13 @@ path-is-inside@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
||||||
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
|
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
|
||||||
|
|
||||||
|
path-is-network-drive@^1.0.13:
|
||||||
|
version "1.0.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-is-network-drive/-/path-is-network-drive-1.0.13.tgz#c9aa0183eb72c328aa83f43def93ddcb9d7ec4d4"
|
||||||
|
integrity sha512-Hg74mRN6mmXV+gTm3INjFK40ncAmC/Lo4qoQaSZ+GT3hZzlKdWQSqAjqyPeW0SvObP2W073WyYEBWY9d3wOm3A==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.3.1"
|
||||||
|
|
||||||
path-key@^2.0.0, path-key@^2.0.1:
|
path-key@^2.0.0, path-key@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
|
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
|
||||||
@@ -9211,6 +9282,13 @@ path-parse@^1.0.6, path-parse@^1.0.7:
|
|||||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||||
|
|
||||||
|
path-strip-sep@^1.0.10:
|
||||||
|
version "1.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-strip-sep/-/path-strip-sep-1.0.10.tgz#2be4e789406b298af8709ff79af716134b733b98"
|
||||||
|
integrity sha512-JpCy+8LAJQQTO1bQsb/84s1g+/Stm3h39aOpPRBQ/paMUGVPPZChLTOTKHoaCkc/6sKuF7yVsnq5Pe1S6xQGcA==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.3.1"
|
||||||
|
|
||||||
path-to-regexp@0.1.7:
|
path-to-regexp@0.1.7:
|
||||||
version "0.1.7"
|
version "0.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||||
@@ -9304,6 +9382,13 @@ pixelmatch@^5.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
pngjs "^4.0.1"
|
pngjs "^4.0.1"
|
||||||
|
|
||||||
|
"pkg-dir@< 6 >= 5":
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760"
|
||||||
|
integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==
|
||||||
|
dependencies:
|
||||||
|
find-up "^5.0.0"
|
||||||
|
|
||||||
pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
|
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
|
||||||
@@ -10231,7 +10316,7 @@ resolve@1.1.7:
|
|||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
||||||
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
|
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
|
||||||
|
|
||||||
resolve@1.20.0, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.14.1, resolve@^1.14.2, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0:
|
resolve@1.20.0, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.14.1, resolve@^1.14.2, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0:
|
||||||
version "1.20.0"
|
version "1.20.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
|
||||||
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
|
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
|
||||||
@@ -10332,10 +10417,10 @@ rollup-plugin-postcss@^3.1.1:
|
|||||||
safe-identifier "^0.4.1"
|
safe-identifier "^0.4.1"
|
||||||
style-inject "^0.3.0"
|
style-inject "^0.3.0"
|
||||||
|
|
||||||
rollup-plugin-rename-node-modules@^1.1.0:
|
rollup-plugin-rename-node-modules@^1.3.1:
|
||||||
version "1.2.0"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/rollup-plugin-rename-node-modules/-/rollup-plugin-rename-node-modules-1.2.0.tgz#f1f1bb2192d1bbec258569bf6bda097002d7dbdf"
|
resolved "https://registry.yarnpkg.com/rollup-plugin-rename-node-modules/-/rollup-plugin-rename-node-modules-1.3.1.tgz#d80091fc817ce726e7cdfc388ec2f4da286e280f"
|
||||||
integrity sha512-IKsS3eJXHLAMXIndzNso9ijWJw1V3mqubRc2gb67v7VuLX9t41LObXqciM0JC3j7/WrHeptG47cejFU0qxXUJA==
|
integrity sha512-46TUPqO94GXuACYqVZjdbzNXTQAp+wTdZg/vUx2gaINb0da/ZPdaOtno2RGUOKBF4sbVM9v2ZqV98r4TQbp1UA==
|
||||||
dependencies:
|
dependencies:
|
||||||
estree-walker "^2.0.1"
|
estree-walker "^2.0.1"
|
||||||
magic-string "^0.25.7"
|
magic-string "^0.25.7"
|
||||||
@@ -10369,6 +10454,23 @@ rollup-plugin-typescript2@^0.30.0:
|
|||||||
resolve "1.20.0"
|
resolve "1.20.0"
|
||||||
tslib "2.1.0"
|
tslib "2.1.0"
|
||||||
|
|
||||||
|
rollup-plugin-typescript2@^0.31.2:
|
||||||
|
version "0.31.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.31.2.tgz#463aa713a7e2bf85b92860094b9f7fb274c5a4d8"
|
||||||
|
integrity sha512-hRwEYR1C8xDGVVMFJQdEVnNAeWRvpaY97g5mp3IeLnzhNXzSVq78Ye/BJ9PAaUfN4DXa/uDnqerifMOaMFY54Q==
|
||||||
|
dependencies:
|
||||||
|
"@rollup/pluginutils" "^4.1.2"
|
||||||
|
"@yarn-tool/resolve-package" "^1.0.40"
|
||||||
|
find-cache-dir "^3.3.2"
|
||||||
|
fs-extra "^10.0.0"
|
||||||
|
resolve "^1.20.0"
|
||||||
|
tslib "^2.3.1"
|
||||||
|
|
||||||
|
rollup-plugin-web-worker-loader@^1.6.1:
|
||||||
|
version "1.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/rollup-plugin-web-worker-loader/-/rollup-plugin-web-worker-loader-1.6.1.tgz#9d7a27575b64b0780fe4e8b3bc87470d217e485f"
|
||||||
|
integrity sha512-4QywQSz1NXFHKdyiou16mH3ijpcfLtLGOrAqvAqu1Gx+P8+zj+3gwC2BSL/VW1d+LW4nIHC8F7d7OXhs9UdR2A==
|
||||||
|
|
||||||
rollup-pluginutils@^2.8.2:
|
rollup-pluginutils@^2.8.2:
|
||||||
version "2.8.2"
|
version "2.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
|
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
|
||||||
@@ -10390,6 +10492,13 @@ rollup@^2.56.3:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
rollup@^2.68.0:
|
||||||
|
version "2.68.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.68.0.tgz#6ccabfd649447f8f21d62bf41662e5caece3bd66"
|
||||||
|
integrity sha512-XrMKOYK7oQcTio4wyTz466mucnd8LzkiZLozZ4Rz0zQD+HeX4nUK4B8GrTX/2EvN2/vBF/i2WnaXboPxo0JylA==
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
run-async@^2.2.0, run-async@^2.4.0:
|
run-async@^2.2.0, run-async@^2.4.0:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
|
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
|
||||||
@@ -11620,6 +11729,15 @@ unzip-response@^2.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
|
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
|
||||||
integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=
|
integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=
|
||||||
|
|
||||||
|
upath2@^3.1.12:
|
||||||
|
version "3.1.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/upath2/-/upath2-3.1.12.tgz#441b3dfbadde21731017bd1b7beb169498efd0a9"
|
||||||
|
integrity sha512-yC3eZeCyCXFWjy7Nu4pgjLhXNYjuzuUmJiRgSSw6TJp8Emc+E4951HGPJf+bldFC5SL7oBLeNbtm1fGzXn2gxw==
|
||||||
|
dependencies:
|
||||||
|
path-is-network-drive "^1.0.13"
|
||||||
|
path-strip-sep "^1.0.10"
|
||||||
|
tslib "^2.3.1"
|
||||||
|
|
||||||
upath@^2.0.1:
|
upath@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b"
|
resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b"
|
||||||
@@ -11959,9 +12077,9 @@ ws@^6.1.0:
|
|||||||
async-limiter "~1.0.0"
|
async-limiter "~1.0.0"
|
||||||
|
|
||||||
ws@^7.2.3:
|
ws@^7.2.3:
|
||||||
version "7.5.6"
|
version "7.5.7"
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67"
|
||||||
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
|
integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==
|
||||||
|
|
||||||
ws@^7.4.3, ws@^7.4.5:
|
ws@^7.4.3, ws@^7.4.5:
|
||||||
version "7.5.3"
|
version "7.5.3"
|
||||||
@@ -12071,3 +12189,8 @@ yn@^2.0.0:
|
|||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a"
|
resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a"
|
||||||
integrity sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=
|
integrity sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=
|
||||||
|
|
||||||
|
yocto-queue@^0.1.0:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||||
|
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||||
|
|||||||
Reference in New Issue
Block a user