A tool to automatically create test cases to aid <canvas> debugging
canvas-interceptor.js
is a snippet to aid with debugging canvas issues, and
designed to create reduced test cases.
Load canvas-interceptor.js
before the web page uses the HTML5 canvas API.
Then the all method calls and property invocations on canvas objects will be
logged and stored in the __proxyLogs
property on the canvas context. The final
code to recreate the canvas can be generated by calling getCanvasReplay(canvas)
.
Example:
<script src="canvas-interceptor.js"></script>
<canvas id="mycanvas"></canvas>
<!-- ... a lot of canvas magic on #mycanvas... -->
<script>
var canvas = document.getElementById('mycanvas');
var code = getCanvasReplay(canvas);
// code can be copied to a new file to recreate the canvas,
// and used to e.g. create reduced test cases for canvas bugs.
// E.g. via the console: copy(code)
</script>
See test.html for more examples.
If you control the source code, loading the snippet via a <script>
tag as
in the above example should work as expected. But if you want to debug a
third-party application, it is more convenient to paste something in the
JavaScript console.
Identify the first script in the page, and set a breakpoint at the start of the file.
Reload the page. The browser will now trigger that breakpoint.
Paste the following code:
;(function(){
var x = new XMLHttpRequest;
x.open('GET', 'https://robwu.nl/s/canvas-interceptor.js', false);
x.send();
window.eval(x.responseText.replace(/(["'])use strict\1/g, ''));
})();
Step out of the debugger.
Now you can freely use the public methods (documented below) to debug canvas issues.
After calling getCanvasReplay()
, you can end up with thousands lines of code.
This code can be pasted to a file and used for debugging. Here are some tips to
reduce the file, in order to pinpoint the bug:
.save()
and .restore()
calls. These functions push and pop the.save()
up.restore()
call (without other .save()
calls in between!).save()
calls after removing all instructions.save()
and .restore()
, just remove the remaining.save()
calls. They are not going to be useful.moveTo
lineTo
followed by closePath
and stroke
).Run test.html to see whether the tool works as expected.
When canvas-interceptor.js
is loaded, it modifies the prototype of
CanvasRenderingContext2D
to intercept calls. The following APIs are exposed:
getCanvasReplay(HTMLCanvasElement|CanvasRenderingContext2D)
- Get the code
to recreate the state of the canvas from the logs.
clearCanvasLogs(HTMLCanvasElement|CanvasRenderingContext2D)
- Reset the logs
for the given canvas / context.
getCanvasName(CanvasRenderingContext2D)
- Get the identifier of the context.
If the context was not known yet, a new name will be generated and stored in
the __proxyName
property of the given parameter. To get the name for a given
canvas, use getCanvasName(canvas.getContext('2d'))
.
CanvasRenderingContext2D.prototype.__proxyUnwrap
- Disable canvas logging.
To re-enable canvas logging, the page has to be refreshed.
Existing logs are still preserved and getCanvasReplay
will continue to work.
serializeArg(obj)
- Get the string representation for the given object.
Calling eval
on the return value ought to reconstruct the original object.
Not all objects can be serialized; if serialization is not supported, object
literals with an inline comment is returned ({ /* ... */ }
).
constructImageData(width, height, data)
- Create a new ImageData
object
with a given width and height, initialized with data
.
wrapObject(...)
implements the logic of intercepting APIs. Do not use this
method unless you know what you're doing (see the code for documentation).
If you need to use a canvas method without logging, retrieve the original
property from .__proxyOriginal
, and call the method (value) or setter/getter.
Example: ctx.__proxyOriginal.scale.value.call(ctx, 1, 1)
or
ctx.__proxyOriginal.lineWidth.set.call(ctx, 1)
.