How to capture WebGL/Canvas by writing out PNGs to a sandboxed filesystem --- Summary: Write .PNGs to the sandboxed file system in Chrome. Then sneak in and move them out. __1) Set up a sandboxed file system__ ```javascript // Request 40 GB (should be enough for ~5 minutes of 1080p) var bytes = 1024*1024*1024*40; window.webkitStorageInfo.requestQuota(PERSISTENT, bytes, function(grantedBytes) { console.log('Got storage', grantedBytes); window.webkitRequestFileSystem(PERSISTENT, grantedBytes, function (fs) { window.fs = fs; console.log("Got filesystem"); }); }, function(e) { console.log('Storage error', e); }); ``` __2) Override requestAnimationFrame to capture N frames and write them to PNGs__ ```javascript var frames = 1000; var raf = window.requestAnimationFrame; var next = null; var hold = false; window.requestAnimationFrame = function rafOverride(callback) { // Find canvas var canvas = document.querySelector('canvas'); if (canvas && window.fs) { // Done capturing? if (frames < 0) { window.requestAnimationFrame = raf; return raf(callback); } // Hold rendering until screenshot is done if (!hold) { hold = true; frames--; setTimeout(function () { callback(); capture(canvas, function () { // Resume rendering hold = false; rafOverride(next); }); }, 66); } else { next = callback; } } else { // Canvas not created yet? return raf(callback); } } function capture(canvas, callback) { var name = Math.random(); // File name doesn't matter var image = canvas.toDataURL('image/png').slice(22); fs.root.getFile(name, {create: true}, function (entry) { entry.createWriter(function (writer) { // Convert base64 to binary without UTF-8 mangling. var data = atob(image); var buf = new Uint8Array(data.length); for (var i = 0; i < data.length; ++i) { buf[i] = data.charCodeAt(i); } // Write data var blob = new Blob([buf], {}); writer.seek(0); writer.write(blob); console.log('Writing file', frames, blob.size); setTimeout(function () { // Resume rendering callback(); }, 66); }); }, function () { console.log('File error', arguments); }); } ``` __3) Find where the files are stored and move them out__ The reason the filenames are random in capture() is because they get written out as sequential 00000000, 00000001, etc. files, somewhere buried deep inside Chrome's profile. On a Mac, I used the following node.js script to get them out. The specific location will differ, but you're looking for a directory full of 00, 01, 02, 03 subdirectories, with 100 numbered files inside each. ``` var fs = require('fs'); var path = "/Users/steven/Library/Application Support/Google/Chrome/Default/File System/009/p/"; var first = 0; var last = 9899; var j = 0; for (var i = first; i <= last; ++i) { var subdir = ("00" + Math.floor(i / 100)).slice(-2); var file = path + subdir + '/' + ("000000000" + i).slice(-8); var target = './file' + ("00000000" + (++j)).slice(-8); console.log(file, target + '.png'); fs.renameSync(file, target + '.png'); } ``` __4) Assemble the PNGs into a movie with your favorite tool.__ I used the old school Quicktime Player 7, as it still has the capability to open numbered image sequences. Then I used the x264 QuickTime plug-in to encode it (it's both faster and better than Apple's H.264 encoder). __5) Clear your Google Chrome cache to clean up the file system.__