Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WebGL support #756

Merged
merged 100 commits into from
Feb 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
e9335db
Add very basic webgl support
Juice10 Nov 9, 2021
8664816
document the default
Juice10 Nov 9, 2021
4d77c09
only capture rr_dataURL in 2d canvas contexts
Juice10 Nov 24, 2021
39eb35a
rr_dataURL no longer part of webgl snapshot
Juice10 Nov 24, 2021
f23cb9c
Merge branch 'master' of git://github.com/rrweb-io/rrweb into webgl
Juice10 Nov 24, 2021
6a87e79
ignore __diff_output__ from jest-image-snapshot
Juice10 Nov 24, 2021
423bab1
Rename generic "Monorepo" to "RRWeb Monorepo"
Juice10 Nov 24, 2021
6a614f6
Serialize WebGL variables
Juice10 Nov 25, 2021
4c123b7
Move rrweb test port number to unique port
Juice10 Nov 25, 2021
f71a598
Prepare for WebGL2
Juice10 Nov 25, 2021
59fec71
Split up canvas replay and record webgl vars
Juice10 Nov 25, 2021
49d2b58
fix typo
Juice10 Nov 25, 2021
d9a03f2
fix typo part 2
Juice10 Nov 25, 2021
c03f53b
fix typo
Juice10 Nov 25, 2021
9f0e7d2
Handle non-variables too
Juice10 Nov 25, 2021
3b1bf2a
provide correct context for warning
Juice10 Nov 25, 2021
a2c27a6
(De)Serialize a lot of different objects
Juice10 Nov 26, 2021
45847b0
monorepo root should be the first in the list
Juice10 Nov 29, 2021
d349900
Upgrade puppeteer to 11.x
Juice10 Nov 29, 2021
5c7be1f
Correctly de-serialize webgl variables
Juice10 Nov 29, 2021
42693ca
Encode arrayBuffers contents to base64
Juice10 Nov 30, 2021
ecb4d6b
rename contents to base64
Juice10 Nov 30, 2021
79e42a7
add webgl2 support and serialize HTMLImageElements
Juice10 Nov 30, 2021
766a478
Support serializing ImageData
Juice10 Dec 2, 2021
35b6050
Correctly classify WebGL2 events
Juice10 Dec 2, 2021
af42d0f
Serialize format changed
Juice10 Dec 2, 2021
637e358
check if canvas has contents before we save the dataURL
Juice10 Dec 2, 2021
e2e7150
Remove blank dataURL
Juice10 Dec 2, 2021
6910276
reference original file not type defintion file
Juice10 Dec 6, 2021
9f64c28
Merge branch 'master' of git://github.com/rrweb-io/rrweb into webgl
Juice10 Dec 6, 2021
7463e30
update types
Juice10 Dec 6, 2021
76b8efb
rename code worspace
Juice10 Dec 6, 2021
e2cfdbb
update dependencies
Juice10 Dec 6, 2021
5d14caa
add spector to inspect webgl
Juice10 Dec 6, 2021
fabb033
remove live server settings from code workspace
Juice10 Dec 6, 2021
2641e29
Save canvas context in the node
Juice10 Dec 8, 2021
bc7f2ba
remove extra braces
Juice10 Dec 8, 2021
fbf67a0
add ICanvas type
Juice10 Dec 8, 2021
63adef7
use ICanvas from rrweb-snapshot in rrweb instead of OgmentedCanvas
Juice10 Dec 8, 2021
d958d5e
add snapshots and webgl 2 tests
Juice10 Dec 8, 2021
f730f57
Upgrade to puppeteer 12.0.1
Juice10 Dec 9, 2021
cc7c055
Revert back to puppeteer 9.1.1
Juice10 Dec 9, 2021
313adb4
Keep index order consistent between replay and record
Juice10 Dec 14, 2021
723acf8
keep correct index order in webgl2
Juice10 Dec 14, 2021
3f9eebd
fixed forgotten import
Juice10 Dec 14, 2021
4a6b556
buffer up pending canvas mutations
Juice10 Dec 14, 2021
970ee44
unify the way webgl and webgl2 get patched
Juice10 Dec 15, 2021
0d42d83
fix parsing error
Juice10 Dec 15, 2021
d26676e
Add types for serialize-args
Juice10 Dec 16, 2021
2b2e11f
Add debugging for webgl replay
Juice10 Dec 16, 2021
8ca984e
Move start-server to utils
Juice10 Dec 16, 2021
88de0f0
turn off debug mode by default
Juice10 Dec 16, 2021
317d2ab
Move pendingCanvasMutations to local object and fix if/else statement
Juice10 Dec 16, 2021
8f862e7
Always save pending mutations
Juice10 Dec 17, 2021
8c72988
only use assert snapshot as it's clearer whats going on
Juice10 Dec 17, 2021
e8eeba2
Merge branch 'master' of git://github.com/rrweb-io/rrweb into webgl
Juice10 Dec 17, 2021
67648cd
Ugly fix for now
Juice10 Dec 17, 2021
ceb97c6
Making the tests more DRY
Juice10 Dec 20, 2021
5b37d2e
flush at the end of each request animation frame
Juice10 Dec 20, 2021
abf7064
Looks like the promise made this test more predictable
Juice10 Dec 20, 2021
aea5e3f
Merge branch 'master' of git://github.com/rrweb-io/rrweb into webgl
Juice10 Dec 20, 2021
ee6ec1f
add waitForRAF
Juice10 Dec 21, 2021
21e55be
Make nested iframe recording robust no matter the test speed
Juice10 Dec 21, 2021
662fae5
mute noisy error in test
Juice10 Dec 21, 2021
1844311
force a requestAnimationFrame
Juice10 Dec 21, 2021
114a00d
Bundle events within one frame together as much as possible
Juice10 Dec 22, 2021
8058c3a
Rename RafStamps
Juice10 Dec 22, 2021
fbaf188
Override event.delay
Juice10 Dec 22, 2021
7917158
cleanup
Juice10 Dec 22, 2021
3d1ff81
Add tests for addDelay
Juice10 Dec 22, 2021
0a2690e
Add webgl e2e test
Juice10 Dec 22, 2021
dd8054a
Remove settimeout
Juice10 Dec 22, 2021
9b0d849
DRY-up test
Juice10 Dec 23, 2021
47d7166
Preload images in webgl
Juice10 Dec 23, 2021
dcd97e2
Add e2e test for webgl image preloading
Juice10 Dec 23, 2021
f284177
don't turn on devtools by default!
Juice10 Dec 23, 2021
dd94aa9
Remove spector
Juice10 Dec 23, 2021
6cd6cc8
close server after use
Juice10 Dec 23, 2021
4abdfff
Add imageMap parameter
Juice10 Dec 23, 2021
354754e
Make e2e image test more robust
Juice10 Dec 23, 2021
b8b38c2
Merge branch 'master' of git://github.com/rrweb-io/rrweb into webgl
Juice10 Jan 3, 2022
148aaea
Merge branch 'master' of git://github.com/rrweb-io/rrweb into webgl
Juice10 Jan 4, 2022
ad0f9f6
document debug mode
Juice10 Jan 5, 2022
d42e7d4
cleanup
Juice10 Jan 5, 2022
5c2e1bd
WebGL recording in iframes & Safari 14 support
Juice10 Jan 5, 2022
1e95891
fix tests
Juice10 Jan 6, 2022
caa577d
don't save null objects as WebGLVar
Juice10 Jan 6, 2022
1175063
group (de)serialized webgl variables by context
Juice10 Jan 6, 2022
d0ac3da
Fix test
Juice10 Jan 7, 2022
602eced
fix tests
Juice10 Jan 7, 2022
a3b6e0c
bundle webgl mutations on request animation frame
Juice10 Jan 7, 2022
18e1115
Merge branch 'master' into webgl
Juice10 Jan 23, 2022
f999d49
Add canvas element to mutation observer file
Juice10 Jan 31, 2022
35da32d
Add Canvas (Mutation) Manager
Juice10 Feb 1, 2022
2180998
Merge branch 'master' into webgl
Juice10 Feb 1, 2022
3317129
cleanup
Juice10 Feb 1, 2022
9b0c63f
Make sure the correct </body> gets replaced
Juice10 Feb 1, 2022
d75879f
Perf: Speed up check to see if canvas is blank
Juice10 Feb 2, 2022
e32f1c9
Access unpatched getImageData
Juice10 Feb 2, 2022
bca29b3
Use is2DCanvasBlank only for 2d context
Juice10 Feb 2, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.vscode/*
!/.vscode/monorepo.code-workspace
!/.vscode/rrweb-monorepo.code-workspace
.idea
node_modules
package-lock.json
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
{
"folders": [
{
"name": "Monorepo",
"name": " rrweb monorepo", // added a space to bump it to the top
"path": ".."
},
{
"name": "rrdom (package)",
"path": "../packages/rrdom"
},
{
"name": "rrweb (package)",
"path": "../packages/rrweb"
},
{
"name": "rrweb-player (package)",
"path": "../packages/rrweb-player"
},
{
"name": "rrweb-snapshot (package)",
"path": "../packages/rrweb-snapshot"
}
],
"settings": {
"jest.disabledWorkspaceFolders": [
"Monorepo",
"rrweb-player"
" rrweb monorepo",
"rrweb-player (package)",
"rrdom (package)"
]
}
}
34 changes: 31 additions & 3 deletions packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ import {
MaskTextFn,
MaskInputFn,
KeepIframeSrcFn,
ICanvas,
} from './types';
import { isElement, isShadowRoot, maskInputValue } from './utils';
import {
is2DCanvasBlank,
isElement,
isShadowRoot,
maskInputValue,
} from './utils';

let _id = 1;
const tagNameRegex = new RegExp('[^a-z0-9-_:]');
Expand Down Expand Up @@ -504,7 +510,26 @@ function serializeNode(
}
// canvas image data
if (tagName === 'canvas' && recordCanvas) {
attributes.rr_dataURL = (n as HTMLCanvasElement).toDataURL();
if ((n as ICanvas).__context === '2d') {
// only record this on 2d canvas
if (!is2DCanvasBlank(n as HTMLCanvasElement)) {
attributes.rr_dataURL = (n as HTMLCanvasElement).toDataURL();
}
} else if (!('__context' in n)) {
// context is unknown, better not call getContext to trigger it
const canvasDataURL = (n as HTMLCanvasElement).toDataURL();

// create blank canvas of same dimensions
const blankCanvas = document.createElement('canvas');
blankCanvas.width = (n as HTMLCanvasElement).width;
blankCanvas.height = (n as HTMLCanvasElement).height;
const blankCanvasDataURL = blankCanvas.toDataURL();

// no need to save dataURL if it's the same as blank canvas
if (canvasDataURL !== blankCanvasDataURL) {
attributes.rr_dataURL = canvasDataURL;
}
}
}
// save image offline
if (tagName === 'img' && inlineImages) {
Expand Down Expand Up @@ -592,7 +617,10 @@ function serializeNode(
);
}
} catch (err) {
console.warn(`Cannot get CSS styles from text's parentNode. Error: ${err}`, n);
console.warn(
`Cannot get CSS styles from text's parentNode. Error: ${err}`,
n,
);
}
textContent = absoluteToStylesheet(textContent, getHref());
}
Expand Down
4 changes: 4 additions & 0 deletions packages/rrweb-snapshot/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export interface INode extends Node {
__sn: serializedNodeWithId;
}

export interface ICanvas extends HTMLCanvasElement {
__context: string;
}

export type idNodeMap = {
[key: number]: INode;
};
Expand Down
37 changes: 37 additions & 0 deletions packages/rrweb-snapshot/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,40 @@ export function maskInputValue({
}
return text;
}

const ORIGINAL_ATTRIBUTE_NAME = '__rrweb_original__';
type PatchedGetImageData = {
[ORIGINAL_ATTRIBUTE_NAME]: CanvasImageData['getImageData'];
} & CanvasImageData['getImageData'];

export function is2DCanvasBlank(canvas: HTMLCanvasElement): boolean {
const ctx = canvas.getContext('2d');
if (!ctx) return true;

const chunkSize = 50;

// get chunks of the canvas and check if it is blank
for (let x = 0; x < canvas.width; x += chunkSize) {
for (let y = 0; y < canvas.height; y += chunkSize) {
const getImageData = ctx.getImageData as PatchedGetImageData;
const originalGetImageData =
ORIGINAL_ATTRIBUTE_NAME in getImageData
? getImageData[ORIGINAL_ATTRIBUTE_NAME]
: getImageData;
// by getting the canvas in chunks we avoid an expensive
// `getImageData` call that retrieves everything
// even if we can already tell from the first chunk(s) that
// the canvas isn't blank
const pixelBuffer = new Uint32Array(
originalGetImageData(
x,
y,
Math.min(chunkSize, canvas.width - x),
Math.min(chunkSize, canvas.height - y),
).data.buffer,
);
if (pixelBuffer.some((pixel) => pixel !== 0)) return false;
}
}
return true;
}
3 changes: 3 additions & 0 deletions packages/rrweb-snapshot/typings/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export declare type tagMap = {
export interface INode extends Node {
__sn: serializedNodeWithId;
}
export interface ICanvas extends HTMLCanvasElement {
__context: string;
}
export declare type idNodeMap = {
[key: number]: INode;
};
Expand Down
1 change: 1 addition & 0 deletions packages/rrweb-snapshot/typings/utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export declare function maskInputValue({ maskInputOptions, tagName, type, value,
value: string | null;
maskInputFn?: MaskInputFn;
}): string;
export declare function is2DCanvasBlank(canvas: HTMLCanvasElement): boolean;
1 change: 1 addition & 0 deletions packages/rrweb/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ temp
*.log

.env
__diff_output__
3 changes: 3 additions & 0 deletions packages/rrweb/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/**.test.ts'],
moduleNameMapper: {
'\\.css$': 'identity-obj-proxy',
},
};
6 changes: 5 additions & 1 deletion packages/rrweb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,18 @@
"@types/chai": "^4.1.6",
"@types/inquirer": "0.0.43",
"@types/jest": "^27.0.2",
"@types/jest-image-snapshot": "^4.3.1",
"@types/jsdom": "^16.2.12",
"@types/node": "^12.20.16",
"@types/prettier": "^2.3.2",
"@types/puppeteer": "^5.4.3",
"@types/puppeteer": "^5.4.4",
"cross-env": "^5.2.0",
"fast-mhtml": "^1.1.9",
"identity-obj-proxy": "^3.0.0",
"ignore-styles": "^5.0.1",
"inquirer": "^6.2.1",
"jest": "^27.2.4",
"jest-image-snapshot": "^4.5.1",
"jest-snapshot": "^23.6.0",
"jsdom": "^17.0.0",
"jsdom-global": "^3.0.2",
Expand All @@ -73,6 +76,7 @@
"dependencies": {
"@types/css-font-loading-module": "0.0.7",
"@xstate/fsm": "^1.4.0",
"base64-arraybuffer": "^1.0.1",
"fflate": "^0.4.4",
"mitt": "^1.1.3",
"rrweb-snapshot": "^1.1.12"
Expand Down
32 changes: 22 additions & 10 deletions packages/rrweb/src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import {
listenerHandler,
mutationCallbackParam,
scrollCallback,
canvasMutationParam,
} from '../types';
import { IframeManager } from './iframe-manager';
import { ShadowDomManager } from './shadow-dom-manager';
import { CanvasManager } from './observers/canvas/canvas-manager';

function wrapEvent(e: event): eventWithTime {
return {
Expand Down Expand Up @@ -180,11 +182,28 @@ function record<T = eventWithTime>(
},
}),
);
const wrappedCanvasMutationEmit = (p: canvasMutationParam) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.CanvasMutation,
...p,
},
}),
);

const iframeManager = new IframeManager({
mutationCb: wrappedMutationEmit,
});

const canvasManager = new CanvasManager({
mutationCb: wrappedCanvasMutationEmit,
win: window,
blockClass,
mirror,
});

const shadowDomManager = new ShadowDomManager({
mutationCb: wrappedMutationEmit,
scrollCb: wrappedScrollEmit,
Expand All @@ -202,6 +221,7 @@ function record<T = eventWithTime>(
sampling,
slimDOMOptions,
iframeManager,
canvasManager,
},
mirror,
});
Expand Down Expand Up @@ -365,16 +385,7 @@ function record<T = eventWithTime>(
},
}),
),
canvasMutationCb: (p) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.CanvasMutation,
...p,
},
}),
),
canvasMutationCb: wrappedCanvasMutationEmit,
fontCb: (p) =>
wrappedEmit(
wrapEvent({
Expand Down Expand Up @@ -404,6 +415,7 @@ function record<T = eventWithTime>(
mirror,
iframeManager,
shadowDomManager,
canvasManager,
plugins:
plugins?.map((p) => ({
observer: p.observer,
Expand Down
16 changes: 14 additions & 2 deletions packages/rrweb/src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
hasShadowRoot,
} from '../utils';
import { IframeManager } from './iframe-manager';
import { CanvasManager } from './observers/canvas/canvas-manager';
import { ShadowDomManager } from './shadow-dom-manager';

type DoubleLinkedListNode = {
Expand Down Expand Up @@ -179,6 +180,7 @@ export default class MutationBuffer {
private mirror: Mirror;
private iframeManager: IframeManager;
private shadowDomManager: ShadowDomManager;
private canvasManager: CanvasManager;

public init(
cb: mutationCallBack,
Expand All @@ -197,6 +199,7 @@ export default class MutationBuffer {
mirror: Mirror,
iframeManager: IframeManager,
shadowDomManager: ShadowDomManager,
canvasManager: CanvasManager,
) {
this.blockClass = blockClass;
this.blockSelector = blockSelector;
Expand All @@ -214,14 +217,17 @@ export default class MutationBuffer {
this.mirror = mirror;
this.iframeManager = iframeManager;
this.shadowDomManager = shadowDomManager;
this.canvasManager = canvasManager;
}

public freeze() {
this.frozen = true;
this.canvasManager.freeze();
}

public unfreeze() {
this.frozen = false;
this.canvasManager.unfreeze();
this.emit();
}

Expand All @@ -231,16 +237,22 @@ export default class MutationBuffer {

public lock() {
this.locked = true;
this.canvasManager.lock();
}

public unlock() {
this.locked = false;
this.canvasManager.unlock();
this.emit();
}

public reset() {
this.canvasManager.reset();
}

public processMutations = (mutations: mutationRecord[]) => {
mutations.forEach(this.processMutation);
this.emit();
mutations.forEach(this.processMutation); // adds mutations to the buffer
this.emit(); // clears buffer if not locked/frozen
};

public emit = () => {
Expand Down
Loading