Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript: toDataUrl() throwing "Security Error: Tainted canvases may not be exported."

I have an HTML5 canvas on which I draw an image from an svg.

HTML

<canvas id="canvas" width="600" height="320"></canvas>

JavaScript

var DOMURL = window.URL || window.webkitURL || window;

var data = '<svg xmlns="http://www.w3.org/2000/svg" width="600" height="320">'+
                    '<foreignObject width="100%" height="100%">'+
                        '<style>'+
                             'foreignObject {'+
                                 'background-color: #000;'+
                                 'color: #fff'+
                                 'border-radius: 10px;'+
                             '}'+
                            'h1 {'+
                                'color: #2acabd;'+
                                'font: 25px arial;'+
                                'font-weight: bold;'+
                                'text-align: center;'+
                            '}'+
                            'h2 {'+
                                'margin: 0;'+
                                'color: #2acabd;'+
                                'font: 15px arial;'+
                            '}'+
                            'p {'+
                                'color: #fff;'+
                            '}'+
                        '</style>'+
                        '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size: 40px;">'+
                            '<h1>Heading</h1>'+
                            '<div>'+
                                '<div id="details-wrapper">'+
                                    '<h2>Full Name</h2>'+
                                    '<p>Alan Johnson</p>'+

                                    '<h2>Date of Birth</h2>'+
                                    '<p>7th November 1988</p>'+

                                    '<p><span id="user-id">34329483028493284093284432</span></p>'+
                                '</div>'+
                            '</div>'+
                        '</div>'+
                    '</foreignObject>'+
                '</svg>';


var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
img = new Image();
img.setAttribute("crossOrigin", "anonymous");
var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svg);

img.onload = function() {
  ctx.drawImage(img, 0, 0);
  DOMURL.revokeObjectURL(url);
  console.log(canvas.toDataURL());
}
img.src = url;

(JS Fiddle: https://jsfiddle.net/LondonAppDev/qnpcg8th/1/)

When I call canvas.toDataURL(), I am getting the exception:

(index):98 Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

I have seen the many other questions and answers on Stack Overflow relating to this exception. My problem is different, because (as you can see) I am not at any point including any images from another domain in my svg or canvas.

I am guessing the problem is that I am creating an object URL with DOMURL.createObjectURL.

I am aware that there are some compatibility issues with doing this in different browsers, however this app only needs to run in Chrome. Also, drawing the text directly onto the canvas is not an option, I must do it via an svg.

Any ideas as to how I can get around this issue, and successfully get a PNG of my canvas?

like image 437
LondonAppDev Avatar asked Aug 25 '16 14:08

LondonAppDev


3 Answers

I solved this by converting the svg to a data URL instead of a Blob.

I removed var url = DOMURL.createObjectURL(svg); and replaced img.src = url; with this:

function buildSvgImageUrl(svg) {
    b64 = window.btoa(svg);
    return "data:image/svg+xml;base64," + b64;
}

img.src = buildSvgImageUrl(data);

Now it works flawlessly.

like image 190
LondonAppDev Avatar answered Oct 22 '22 16:10

LondonAppDev


This is was for security and user privacy reasons.
To paint a <foreignObject> on a canvas can leaks several informations about users, and until then, chrome team thought they didn't had enough security measures to prevent this so they did taint the canvas.

Here is the chromium bug report about this issue.

But as OP found out, they had an implementation bug that forgot to set this restriction on dataURI versions.
I did post this bug report about the fact that they do not taint canvas with dataURIs.

All this lead to the leverage of the former restrictions, so in next versions of chrome, we should be able to paint <foreignObject> on canvas without tainting it, even with BlobURIs.

But note that Safari still does have the same restriction (without the implementation bug, i.e the dataURI hack doesn't work there), and that IE < Edge do not support <foreignObject> element and taints the canvas when any SVG has been painted to it, and that FF does remove all UserAgent and OS styles (which leads to different results than the one you could expect).

A real workaround then is to not use this hack to get your elements painted, but draw all your HTML with CanvasAPI drawing methods (just like html2canvas does).

like image 28
Kaiido Avatar answered Oct 22 '22 16:10

Kaiido


I agree with the accepted answer but it didn't work for me.......this similar approach did: Tainted Canvas with canvas.toBlob

This is the code:

let img = new Image();
let svg = document.getElementById('svg-node');
let url = "data:image/svg+xml;charset=utf-8," + (new XMLSerializer()).serializeToString(svg);
img.src = url;

Hope I saved you some time! :)

like image 33
Mark Avatar answered Oct 22 '22 16:10

Mark