prototype here does not refer to the correct WebGLRenderingContext … (#1013)

* `prototype` here does not refer to the correct WebGLRenderingContext when there are multiple being patched - all were referring to the same one meaning that variables weren't being indexed correctly as `canvasVarMap` in serialize-args.ts was only seeing a single context

If you revert the change from `prototype` to `this` from this commit, you'll get the following failure in the new test added in this commit:

   @@ -199,11 +199,11 @@
                "property": "bindBuffer",
                "args": [
                  34962,
                  {
                    "rr_type": "WebGLBuffer",
    -               "index": 0
    +               "index": 1
                  }
                ]
              },

This is because the 'confound' canvas was populating a single entry in the canvasVarMap so when 'myCanvas' starts populating, there is already an entry (which doesn't exist on the replay side as the 'confound' canvas was never emitted)

* Update packages/rrweb/test/integration.test.ts

Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>

* Update packages/rrweb/test/html/canvas-webgl-shader.html

Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>

Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>
This commit is contained in:
Eoghan Murray
2026-04-01 12:00:00 +08:00
committed by GitHub
parent 06dad675e5
commit f43168ba06
4 changed files with 490 additions and 2 deletions

View File

@@ -36,9 +36,9 @@ function patchGLPrototype(
) {
return function (this: typeof prototype, ...args: Array<unknown>) {
const result = original.apply(this, args);
saveWebGLVar(result, win, prototype);
saveWebGLVar(result, win, this);
if (!isBlocked(this.canvas, blockClass, blockSelector, true)) {
const recordArgs = serializeArgs([...args], win, prototype);
const recordArgs = serializeArgs([...args], win, this);
const mutation: canvasMutationWithType = {
type,
property: prop,

View File

@@ -1,5 +1,419 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`record integration tests can correctly serialize a shader and multiple webgl contexts 1`] = `
"[
{
\\"type\\": 0,
\\"data\\": {}
},
{
\\"type\\": 1,
\\"data\\": {}
},
{
\\"type\\": 4,
\\"data\\": {
\\"href\\": \\"about:blank\\",
\\"width\\": 1920,
\\"height\\": 1080
}
},
{
\\"type\\": 2,
\\"data\\": {
\\"node\\": {
\\"type\\": 0,
\\"childNodes\\": [
{
\\"type\\": 1,
\\"name\\": \\"html\\",
\\"publicId\\": \\"\\",
\\"systemId\\": \\"\\",
\\"id\\": 2
},
{
\\"type\\": 2,
\\"tagName\\": \\"html\\",
\\"attributes\\": {
\\"lang\\": \\"en\\"
},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"head\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 5
},
{
\\"type\\": 2,
\\"tagName\\": \\"meta\\",
\\"attributes\\": {
\\"charset\\": \\"UTF-8\\"
},
\\"childNodes\\": [],
\\"id\\": 6
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 7
},
{
\\"type\\": 2,
\\"tagName\\": \\"meta\\",
\\"attributes\\": {
\\"name\\": \\"viewport\\",
\\"content\\": \\"width=device-width, initial-scale=1.0\\"
},
\\"childNodes\\": [],
\\"id\\": 8
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 9
},
{
\\"type\\": 2,
\\"tagName\\": \\"title\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"canvas shader\\",
\\"id\\": 11
}
],
\\"id\\": 10
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 12
}
],
\\"id\\": 4
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 13
},
{
\\"type\\": 2,
\\"tagName\\": \\"body\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 15
},
{
\\"type\\": 2,
\\"tagName\\": \\"canvas\\",
\\"attributes\\": {
\\"id\\": \\"myCanvas\\",
\\"width\\": \\"300\\",
\\"height\\": \\"300\\",
\\"style\\": \\"border: 1px solid #000000\\"
},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 17
}
],
\\"id\\": 16
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 18
},
{
\\"type\\": 2,
\\"tagName\\": \\"script\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
\\"id\\": 20
}
],
\\"id\\": 19
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\\\n \\",
\\"id\\": 21
},
{
\\"type\\": 2,
\\"tagName\\": \\"script\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
\\"id\\": 23
}
],
\\"id\\": 22
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\",
\\"id\\": 24
}
],
\\"id\\": 14
}
],
\\"id\\": 3
}
],
\\"id\\": 1
},
\\"initialOffset\\": {
\\"left\\": 0,
\\"top\\": 0
}
}
},
{
\\"type\\": 3,
\\"data\\": {
\\"source\\": 9,
\\"id\\": 16,
\\"type\\": 1,
\\"commands\\": [
{
\\"property\\": \\"createBuffer\\",
\\"args\\": []
},
{
\\"property\\": \\"bindBuffer\\",
\\"args\\": [
34962,
{
\\"rr_type\\": \\"WebGLBuffer\\",
\\"index\\": 0
}
]
},
{
\\"property\\": \\"bufferData\\",
\\"args\\": [
34962,
{
\\"rr_type\\": \\"Float32Array\\",
\\"args\\": [
[
-0.5,
0.5,
-0.5,
-0.5,
0,
-0.5
]
]
},
35044
]
},
{
\\"property\\": \\"bindBuffer\\",
\\"args\\": [
34962,
null
]
},
{
\\"property\\": \\"createShader\\",
\\"args\\": [
35633
]
},
{
\\"property\\": \\"shaderSource\\",
\\"args\\": [
{
\\"rr_type\\": \\"WebGLShader\\",
\\"index\\": 0
},
\\"attribute vec2 coordinates;void main(void) { gl_Position = vec4(coordinates,0.0, 1.0);}\\"
]
},
{
\\"property\\": \\"compileShader\\",
\\"args\\": [
{
\\"rr_type\\": \\"WebGLShader\\",
\\"index\\": 0
}
]
},
{
\\"property\\": \\"createShader\\",
\\"args\\": [
35632
]
},
{
\\"property\\": \\"shaderSource\\",
\\"args\\": [
{
\\"rr_type\\": \\"WebGLShader\\",
\\"index\\": 1
},
\\"void main(void) {gl_FragColor = vec4(0.0, 0.0, 0.0, 0.1);}\\"
]
},
{
\\"property\\": \\"compileShader\\",
\\"args\\": [
{
\\"rr_type\\": \\"WebGLShader\\",
\\"index\\": 1
}
]
},
{
\\"property\\": \\"createProgram\\",
\\"args\\": []
},
{
\\"property\\": \\"attachShader\\",
\\"args\\": [
{
\\"rr_type\\": \\"WebGLProgram\\",
\\"index\\": 0
},
{
\\"rr_type\\": \\"WebGLShader\\",
\\"index\\": 0
}
]
},
{
\\"property\\": \\"attachShader\\",
\\"args\\": [
{
\\"rr_type\\": \\"WebGLProgram\\",
\\"index\\": 0
},
{
\\"rr_type\\": \\"WebGLShader\\",
\\"index\\": 1
}
]
},
{
\\"property\\": \\"linkProgram\\",
\\"args\\": [
{
\\"rr_type\\": \\"WebGLProgram\\",
\\"index\\": 0
}
]
},
{
\\"property\\": \\"useProgram\\",
\\"args\\": [
{
\\"rr_type\\": \\"WebGLProgram\\",
\\"index\\": 0
}
]
},
{
\\"property\\": \\"bindBuffer\\",
\\"args\\": [
34962,
{
\\"rr_type\\": \\"WebGLBuffer\\",
\\"index\\": 0
}
]
},
{
\\"property\\": \\"getAttribLocation\\",
\\"args\\": [
{
\\"rr_type\\": \\"WebGLProgram\\",
\\"index\\": 0
},
\\"coordinates\\"
]
},
{
\\"property\\": \\"vertexAttribPointer\\",
\\"args\\": [
0,
2,
5126,
false,
0,
0
]
},
{
\\"property\\": \\"enableVertexAttribArray\\",
\\"args\\": [
0
]
},
{
\\"property\\": \\"clearColor\\",
\\"args\\": [
0.5,
0.5,
0.5,
0.9
]
},
{
\\"property\\": \\"enable\\",
\\"args\\": [
2929
]
},
{
\\"property\\": \\"clear\\",
\\"args\\": [
16384
]
},
{
\\"property\\": \\"viewport\\",
\\"args\\": [
0,
0,
300,
300
]
},
{
\\"property\\": \\"drawArrays\\",
\\"args\\": [
4,
0,
3
]
}
]
}
}
]"
`;
exports[`record integration tests can freeze mutations 1`] = `
"[
{

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>canvas shader</title>
</head>
<body>
<canvas
id="myCanvas"
width="300"
height="300"
style="border: 1px solid #000000"
>
</canvas>
<script>
function createTriangle(canvas) {
// copied from https://www.tutorialspoint.com/webgl/webgl_sample_application.htm
var gl = canvas.getContext('webgl');
var vertices = [-0.5, 0.5, -0.5, -0.5, 0.0, -0.5,];
var vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
var vertCode =
'attribute vec2 coordinates;' +
'void main(void) {' + ' gl_Position = vec4(coordinates,0.0, 1.0);' + '}';
var vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);
var fragCode = 'void main(void) {' + 'gl_FragColor = vec4(0.0, 0.0, 0.0, 0.1);' + '}';
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertShader);
gl.attachShader(shaderProgram, fragShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
var coord = gl.getAttribLocation(shaderProgram, "coordinates");
gl.vertexAttribPointer(coord, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coord);
gl.clearColor(0.5, 0.5, 0.5, 0.9);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0,0,canvas.width,canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
setTimeout(() => {
// canvas element that never gets appended to the DOM
const confound = document.createElement('canvas');
// These will not be emitted (but was triggering a bug in the emitted canvas)
// @see https://github.com/rrweb-io/rrweb/pull/1013
createTriangle(confound);
const c = document.getElementById('myCanvas');
createTriangle(c);
}, 10);
</script>
</body>
</html>

View File

@@ -401,6 +401,19 @@ describe('record integration tests', function (this: ISuite) {
assertSnapshot(snapshots);
});
it('can correctly serialize a shader and multiple webgl contexts', async () => {
const page: puppeteer.Page = await browser.newPage();
await page.goto('about:blank');
await page.setContent(
getHtml.call(this, 'canvas-webgl-shader.html', {
recordCanvas: true,
}),
);
await waitForRAF(page);
const snapshots = await page.evaluate('window.snapshots');
assertSnapshot(snapshots);
});
it('will serialize node before record', async () => {
const page: puppeteer.Page = await browser.newPage();
await page.goto('about:blank');