canvas-interceptor
                                
                                 canvas-interceptor copied to clipboard
                                
                                    canvas-interceptor copied to clipboard
                            
                            
                            
                        A tool to automatically create test cases to aid <canvas> debugging
Canvas Interceptor
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.
Usage
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. 
Reducing test cases
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:
- Look for .save()and.restore()calls. These functions push and pop the canvas state in a stack-like way. Keep removing all lines between.save()up to the first.restore()call (without other.save()calls in between!) until the bug that you're trying to locate (dis)appears.
- If you end up with extra .save()calls after removing all instructions between and including.save()and.restore(), just remove the remaining.save()calls. They are not going to be useful.
- Remove independent and uninteresting drawings. E.g. a complete path (moveTofollowed by a sequence oflineTofollowed byclosePathandstroke). Or e.g. a set of transformations that slightly alters the appearance.
- Change some values (widths / transformations) to see the impact of a command on the whole drawing, and if the result is acceptable, cut a whole chunk of commands.
Browser compatibility
Run test.html to see whether the tool works as expected.
- Chrome 43+ (not 42- because accessor descriptors were not prototypical, and data descriptors were not configurable)
- Opera 31+ (same reason as Chrome).
- Firefox 17+ (not 16- because accessor descriptors were inaccessible)
- IE 10+ and Edge 0.11+ (IE9 and earlier doesn't support
- Safari (not supported because accessor descriptors are not prototypical)
API documentation
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__proxyNameproperty of the given parameter. To get the name for a given canvas, usegetCanvasName(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 andgetCanvasReplaywill continue to work.
- 
serializeArg(obj)- Get the string representation for the given object. Callingevalon 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 newImageDataobject with a given width and height, initialized withdata.
- 
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).