Должна ли быть доступна установка изображения src в URL-адрес данных?

Рассмотрим следующий (хрупкий) код JavaScript:

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!'); };

Вопрос (ы)

  • Если ширина и высота изображения должны быть немедленно устранены?
  • Должен ли рисовать холст немедленно?
  • Должен ли вызываться обратный вызов onload (установленный после изменения src)?

Экспериментальные тесты
Я создал простую тестовую страницу с похожим кодом. В Safari мои первые тесты, открывая локальную страницу HTML (file:/// url) и загружая ее с моего сервера, показали все, что работает: высота и ширина верны, рисование холста работает, а также срабатывает onload.

В Firefox v3.6 (OS X) загрузка страницы после запуска браузера показывает, что высота/ширина неверны сразу после настройки, drawImage() завершается с ошибкой. (Однако обработчик onload срабатывает.) Однако загрузка страницы снова показывает правильность ширины/высоты сразу после настройки и drawImage(). Похоже, что Firefox кэширует содержимое URL-адреса данных в качестве изображения и имеет его доступное сразу же при использовании в одном сеансе.

В Chrome v8 (OS X) я вижу те же результаты, что и в Firefox: изображение недоступно сразу, но занимает некоторое время, чтобы асинхронно "загружать" с URL-адреса данных.

В дополнение к экспериментальному доказательству того, какие браузеры выше или не работают, мне действительно нравятся ссылки на спецификации того, как это должно себя вести. Пока что мой Google-фу не справился с задачей.

Воспроизведение в безопасности
Для тех, кто не понимает, почему вышеуказанное может быть опасным, знайте, что вы должны использовать изображения, подобные этому, в безопасности:

// 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 = '...';

Ответ 1

Поскольку никто до сих пор не нашел каких-либо спецификаций о том, как это должно себя вести, мы должны быть удовлетворены тем, как он себя ведет. Ниже приведены результаты моих тестов.

            | 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**              
------------+-----------------------------+-------------------------------------

Вкратце:

  • Вы не можете предположить, что данные изображения будут доступны сразу после установки data-uri; вы должны ждать события onload.
  • Из-за IE9 вы не можете установить обработчик onload после установки src и ожидать, что он будет вызван.
  • Рекомендации в "Воспроизведение в безопасности" (из вопроса выше) являются единственным способом обеспечения правильного поведения.

Если кто-нибудь может найти спецификации, обсуждающие это, я с радостью приму их ответ.

Ответ 2

После передачи управления механизму рендеринга браузера отчеты об испытаниях true в FF 4b9 и Chromium 8.0.552.237. Я изменил эти части:

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);

Обновление: Да, я понимаю, что этот "ответ" скорее похож на комментарий, а просто хотел указать на него. И после тестирования я получаю воспроизводимые результаты:

  • без setTimeout, в обоих браузерах я получаю false при первом открытии страницы и true только после нажатия F5. После явной очистки кеша снова скажем false после перезагрузки.
  • с setTimeout тест ширины/высоты всегда выравнивается до true.

В обоих случаях изображение не появляется в первый раз, только после перезагрузки страницы.