Sirko
Sirko

Reputation: 74046

How to check, if a canvas element has been tainted?

Basic scenario

I'm loading several images on the client side. Some of them are from another domain, some are not. Some I may be able to access using the crossOrigin attribute, some not.

The basic requirement is to retrieve a dataURL for the images where possible.

Question

After drawing the image to the canvas element (which I need to to to get a dataURL, right?), how can I check, without a try ... catch block, whether the canvas has been tainted? If the canvas is tainted, I can't use toDataURL() anymore (see MDN).

var image = new Image(),
    canvas = document.createElement( 'canvas' ),
    context = canvas.getContext( '2d' );

image.onload = function(){

  // adjust dimensions of canvas
  canvas.width = image.width;
  canvas.height = image.height;

  // insert image
  context.drawImage( image, 0, 0 );

  // how to check here?

  // get dataurl
  dataurl = tmpCanvas.toDataURL();

  // do stuff with the dataurl
};
image.src = src;  // some cross origin image

Upvotes: 22

Views: 7819

Answers (2)

user1693593
user1693593

Reputation:

Here is a solution that doesn't add properties to native objects:

function isTainted(ctx) {
    try {
        var pixel = ctx.getImageData(0, 0, 1, 1);
        return false;
    } catch(err) {
        return (err.code === 18);
    }
}

Now simply check by doing this:

if (isTainted(ctx)) alert('Sorry, canvas is tainted!');

Edit: NOW i see you wanted a solution without try-catch. Though, this is the proper way to check as there is no origin clean flag exposed to user (it's for internal use only). Adding properties to native object is not recommended.

Upvotes: 7

markE
markE

Reputation: 105015

Here's an indirect test for CORS tainting that doesn't use try-catch:

A Demo: http://jsfiddle.net/m1erickson/uDt2K/

It works by setting a image.tainted=true flag before loading the image

Then in image.onload, context.getImageData triggers/doesn't trigger a CORS violation.

If no violation occurs, then the tainted flag is set to false (image.tainted=false).

var img=new Image();

// set a "tainted flag to the image to true (initialize as tainted)
img.tainted=true;

img.onload=function(){

    // try an action that triggers CORS security

    var i=ctx.getImageData(1,1,1,1);

    // if we don't get here, we violated CORS and "tainted" remains true

    // if we get here, CORS is happy so set the "tainted" flag to false

    img.tainted=false;

};

// test with tainted image

img.src="http://pp-group.co.uk/wp/wp-content/uploads/2013/10/house-illustration-web.gif";

Since image.onload is asynchronous, your code outside image.onload will still execute even after a CORS violation.

Here's example code that:

  • creates an image object
  • tests if the image is CORS compliant
  • executes one callback if the image is compliant
  • executes another callback if the image is not compliant

Example Code:

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>
<script>
$(function(){

    // known CORS violator
    var src1="http://pp-group.co.uk/wp/wp-content/uploads/2013/10/house-illustration-web.gif";
    // known CORS compliant
    var src2="https://dl.dropboxusercontent.com/u/139992952/houseIcon.png";

    // callbacks depending on if the image causes tainted canvas
    function tainted(img){console.log("tainted:",img.src);}
    function notTainted(img){console.log("not tainted:",img.src);}

    // testing
    var image1=newImage(src1,tainted,notTainted);
    var image2=newImage(src2,tainted,notTainted);


    function newImage(src,callWhenTainted,callWhenNotTainted){

        // tmpCanvas to test CORS
        var tmpCanvas=document.createElement("canvas");
        var tmpCtx=tmpCanvas.getContext("2d");
        // tmpCanvas just tests CORS, so make it 1x1px
        tmpCanvas.width=tmpCanvas.height=1;

        var img=new Image();
        // set the cross origin flag (and cross our fingers!)
        img.crossOrigin="anonymous";
        img.onload=function(){
            // add a tainted property to the image 
            // (initialize it to true--is tainted)
            img.tainted=true;
            // draw the img on the temp canvas
            tmpCtx.drawImage(img,0,0);
            // just in case this onload stops on a CORS error...
            // set a timer to call afterOnLoad shortly
            setTimeout(function(){
                afterOnLoad(img,callWhenTainted,callWhenNotTainted);
            },1000);  // you can probably use less than 1000ms
            // try to violate CORS
            var i=tmpCtx.getImageData(1,1,1,1);
            // if we get here, CORS is OK so set tainted=false
            img.tainted=false;
        };
        img.src=src;
        return(img);
    }

    // called from image.onload
    // at this point the img.tainted flag is correct
    function afterOnLoad(img,callWhenTainted,callWhenOK){
        if(img.tainted){
            // it's tainted
            callWhenTainted(img);
        }else{
            // it's OK, do dataURL stuff
            callWhenOK(img);
        }
    }


}); // end $(function(){});
</script>
</head>
<body>
    <canvas id="canvas" width=360 height=281></canvas>
</body>
</html>

Upvotes: 1

Related Questions