76

Consider the following (fragile) JavaScript code:

var img = new Image;
img.src = "data:image/png;base64,..."; // Assume valid data

// Danger(?) Attempting to use image immediately after setting src
console.log( img.width, img.height );
someCanvasContext.drawImage( img, 0, 0 );

// Danger(?) Setting onload after setting src
img.onload = function(){ console.log('I ran!'); };

The Question(s)

  • Should the image width and height be correct immediately?
  • Should the canvas draw work immediately?
  • Should the onload callback (set after the src was changed) be invoked?

Experimental Tests
I created a simple test page with similar code. In Safari my first tests, both by opening the HTML page locally (file:/// url) and loading it from my server, showed everything working: the height and width is correct, the canvas draw works, and the onload also fires.

In Firefox v3.6 (OS X), loading the page after launching the browser shows that the height/width is not correct immediately after setting, drawImage() fails. (The onload handler does fire, however.) Loading the page again, however, shows the width/height being correct immediately after setting, and drawImage() working. It would appear that Firefox caches the contents of the data URL as an image and has it available immediately when used in the same session.

In Chrome v8 (OS X) I see the same results as in Firefox: the image is not available immediately, but takes some time to asynchronously 'load' from the data URL.

In addition to experimental proof of what browsers the above does or does not work, I'd really love links to specs on how this is supposed to behave. So far my Google-fu has not been up to the task.

Playing It Safe
For those who don't understand why the above might be dangerous, know that you should use images like this to be safe:

// First create/find the image
var img = new Image;

// Then, set the onload handler
img.onload = function(){
  // Use the for-sure-loaded img here
};

// THEN, set the src
img.src = '...';
Phrogz
  • 271,922
  • 98
  • 616
  • 693
  • 1
    You definitely need to set `onload` before setting the `src`. But if the load event sometimes fires in the current constellation, then setting the "data" URI is indeed asynchronous - which would surprise me. Interesting! – Pekka Jan 23 '11 at 21:37
  • Is it not safer to assume that no, to pump a timeout and continue after the browser gets a chance to redraw itself ? – mP. Jan 23 '11 at 22:43
  • @mP Yes, it certainly is; I edited my question to make this clear a scant 5 minutes before your comment :) However, this does not invalidate the question. Assuming that the behavior in Chrome and FF is permissible—not a bug—then it is vital, in fact, to play it safe. – Phrogz Jan 24 '11 at 02:11
  • @Pekka See my 'answer' below; IE9 requires onload to be set before setting src, but no other browser does. (!) Surprising indeed. – Phrogz Jan 26 '11 at 16:46
  • @phrogz Hi, Phrogz, this functionality is very important to me; however, on Android Browser (the latest version now), assigning dataURL to an image source just can't work properly even by your "Playing It Safe" method. Could you give me more suggestion? – Alston Dec 09 '12 at 09:37
  • @Stallman According to [this Android bug](http://code.google.com/p/android/issues/detail?id=7901) it appears that `toDataURL()` was not properly implemented until recently. Have you first confirmed that you _have_ a valid data URL? Secondly, you should post this as a question, not as a comment to a largely unrelated question. – Phrogz Dec 09 '12 at 15:35
  • @Phrogz Well... I got it... I will post a question in these days... Thank u! – Alston Dec 09 '12 at 16:22

2 Answers2

53

As no one has yet found any specifications about how this is supposed to behave, we will have to be satisfied with how it does behave. Following are the results of my tests.

            | is image data usable right  | does onload callback set after src
            |     after setting src?      |   is changed still get invoked?
------------+-----------------------------+-------------------------------------
Safari  5   |             yes             |                  yes                
------------+-----------------------------+-------------------------------------
Chrome  8   | **no** (1st page load), but |                  yes                
FireFox 3.6 |  yes thereafter (cached)    |                                     
------------+-----------------------------+-------------------------------------
IE8         |    yes (32kB data limit)    |                  yes         
------------+-----------------------------+-------------------------------------
IE9b        |             yes             |                 **no**              
------------+-----------------------------+-------------------------------------

In summary:

  • You cannot assume that the image data will be available right after setting a data-uri; you must wait for the onload event.
  • Due to IE9, you cannot set the onload handler after setting the src and expect it to be invoked.
  • The recommendations in "Playing it Safe" (from the question above) are the only way to ensure correct behavior.

If anyone can find specs discussing this, I will happily accept their answer instead.

Phrogz
  • 271,922
  • 98
  • 616
  • 693
  • 4
    Thanks for the research you did here. I've been struggling with the possibility of forcing this behavior for an offline webapp where I need to report using canvas, so I dug in a bit more. The living WHATWG spec actually does call out the cached case as synchronous, and the non-cached case as asynchronous. Check out step 7 in the src section of the spec found here: http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#attr-img-src. It appears if the image is in the Document's "list of available images" it completes synchronously, or that's how I read it. – J Trana Dec 09 '13 at 03:23
  • @Phrogz At what stage or document state did you conduct that test? I am observing Safari 5.1 to have the Chrome behavior, but that's before the `window.load` event. Were your tests made after load or before? Do you remember? – hexalys Aug 05 '15 at 06:45
7

After giving control back to the browser's rendering engine, the test reports true in FF 4b9 and Chromium 8.0.552.237. I modified these parts:

img.onload = function(){   // put this above img.src = …
    document.getElementById('onload').innerHTML = 'yes';
};
img.src = img_src;
…
window.setTimeout(function () {   // put this inside setTimeout
    document.getElementById('wh').innerHTML = (img.height==img.width) && (img.height==128);
}, 0);

Update: Yes, I understand this 'answer' is more like a comment, but just wanted to point it out. And after testing I get reproducible results:

  • without setTimeout, in both browsers I get false the first time I open the page and true only after hitting F5. After explicitly clearing the cache both say false again after reloading.
  • with setTimeout the width/height test always evualuates to true.

In both cases the image doesn't show up the first time, only after reloading the page.

Marcel Korpel
  • 20,899
  • 5
  • 58
  • 79
  • Are you telling me that without these changes you are seeing 'failures' in FF and Chromium? My question isn't how to make this work—clearly setting `onload` before `src` and only referencing the image in the callback will work. My question is whether there exist specs describing how this ought to work. – Phrogz Jan 23 '11 at 21:57