When coding the little fade-in logo for the front page of SaneMethod.com, I ran into a rather obtuse javascript error (although, let's be fair, alot of the errors are pretty obtuse, and can only be understood from context - ie. What was it I was doing at that exact moment to generate such an error. Not that javascript is alone in this, just that its been the worst offender in my recent memory).
Dragonfly reported the error as Reference Error: Security Violation, and the line of code reference the script which manipulated my canvas. Oh, right - the logo fade-in thing is a canvas, loading images from and then displaying them one by one via javascript. I'll write up how the whole thing works shortly, and amend this article to link to it. For now, though - the error.
The Problem
See, I hadn't encountered this error when developing locally, so I figured immediately it must be a cross-site scripting thing. Actually, I figured immediately that it was the Universe reminding me that, hey, it could really f*ck me over at any minute, and just thought it should remind me of the fact, in a small but significant way. Then, a cup of tea and a quick Google search later, I arrived at the formerly mentioned conclusion instead.
Turns out this security violation - which you might also have seen featuring as NS_ERROR_DOM_SECURITY_ERR - has to do with not letting the script pick out any images loaded into the canvas from a different domain from the one its currently working in. I'm not certain exactly what the rationale is there, except that same-domain policy is simply being enforced across the board - and maybe there's some worries about, say, making screen captures to invisible canvas' and then grabbing the contents, along with passwords and such. Not sure if that's doable, though. If there's more to this, please let me know via the comments, I really am agog.
In any case, I wasn't loading the images from another domain - but apparently images loaded from files are just as suspect since their origins are unknown, or possibly because of where they were hosted. Drawing them is all good - running getImageData() on the canvas afterward though?
No good at all.
The Solution
So, a work around had to be arrived at. This has a lot to do with the way the canvas script works, and will make more sense once you read that, but here's quick step-by-step summary:
- Get the canvas context and load the images we want to fade into view (using a little callback function that calls the rest of the script only when the images are fully loaded);
- By adjusting the globalAlpha value, we can draw the images at varying levels of transparency. We set ahead of time what we want the step to be (that is, by how much the alpha will increase each cycle), and set a timer to call the function on a certain interval;
- The image is drawn multiple times, with the alpha increasing each - we clear the canvas between each call as well, to prevent the overlap from making the image too dark, too quickly;
- Finally, when we've reached full alpha, we check to see if there are any other images left to be drawn. If there are, we repeat the whole process over again with the new image - only this time, we preserve whatever was already drawn when we draw the new thing on top.
It was that last bit that was tripping me up - trying to get the image data from the canvas to preserve the background triggered our friend the security error. The workaround was fairly simple and, considering the costliness of getImageData, might actually be the better solution. We simply store the images ahead of time in array, keep track of how many images have been drawn, and when it comes time to draw them as background to a new in-fading image, we reset the globalAlpha to 1.0, and draw them, then set the globalAlpha back down to whatever it was before and draw the new image, repeating until finished. You can see the result of this working right on the front page.
Too Long, Didn't Read
Don't try to use getImageData to retrieve pixel data when you've drawn image files to the canvas - you'll get a security error. You'll have to work around it, and the way I did it was to keep track of what images have already been drawn, and simply draw them again when needed.
Show Me The Code
I'll be writing that article on how the whole thing works soon, but if you'd like you can take a peek at the code right now: canvasScript.js. I apologize in advance for its untidiness. I've also left the original mechanism I coded around in place, commented out, so you can understand what I changed. Enjoy.