👻

XMLHttpRequestはdataURLを読み込むことができない

2021/08/04に公開

https://github.com/blueimp/JavaScript-Load-Image
というライブラリを使っていた時に発見した知見。

発見に至るまで

アップロードされた画像をFile形式なりBlob形式なりdataURL形式なりで取り扱うために、相互変換したいなんてことは多分にあると思います。
そこで、blueimp-load-imageというライブラリを使い、以下のような関数を仕立てました。

export function convertImageToDataUrl(
    file: string | File | Blob,
    options?: loadImage.LoadImageOptions
): Observable<string> {
    return new Observable((subscriber) => {
        loadImage(
            file,
            (image) => {
                subscriber.next((image as HTMLCanvasElement).toDataURL());
                subscriber.complete();
            },
            {
                canvas: true,
                meta: true,
                orientation: true,
                ...options,
            }
        );
    });
}

export function convertImageToBlob(
    file: string | File | Blob,
    options?: loadImage.LoadImageOptions
): Observable<Blob> {
    return new Observable((subscriber) => {
        loadImage(
            file,
            (image) => {
                (image as HTMLCanvasElement).toBlob((blob) => {
                    subscriber.next(blob);
                    subscriber.complete();
                });
            },
            {
                canvas: true,
                meta: true,
                orientation: true,
                ...options,
            }
        );
    });
}

このようにして、Chromeでは正常に相互変換できることを確認したのですが、なぜかIE11[1]ではアクセスが拒否されましたというエラーが出て変換できません。

スタックトレースからライブラリのソースコードを見ると、どうやら以下の部分でエラーが発生しているようです。

https://github.com/blueimp/JavaScript-Load-Image/blob/a238dfb0ff544535a2d3e715845cfa59bb0d7522/js/load-image-fetch.js#L72

load-image-fetch.js
function executor(resolve, reject) {
  options = options || {} // eslint-disable-line no-param-reassign
  var req = new XMLHttpRequest()
  req.open(options.method || 'GET', url)
  if (options.headers) {
    Object.keys(options.headers).forEach(function (key) {
      req.setRequestHeader(key, options.headers[key])
    })
  }
  req.withCredentials = options.credentials === 'include'
  req.responseType = 'blob'
  req.onload = function () {
    resolve(req.response)
  }
  req.onerror = req.onabort = req.ontimeout = function (err) {
    if (resolve === reject) {
      // Not using Promises
      reject(null, err)
    } else {
      reject(err)
    }
  }
  req.send(options.body)
}

この関数のちょっと上にIF文があり、「Fetchに対応していない環境」のみ上記の関数が実行されるようになっていました。
IE11はFetchには対応しておらず、XMLHttpRequestには対応しているので、この関数が実行されます。

さて、じゃあなぜfetchだのXHRだのをしようとしているのかというと、元々このライブラリはリモートURLを渡して変換することを想定して作られたライブラリだからです。
なので、string型で渡された場合は必ずload-image-fetch.jsを実行するようです。

「じゃあdataURL渡しても動くっしょ」と安易な気持ちでdataURLを渡したところ、Fetchでは正常に動くのですが、XMLHttpRequestを使わざるを得ないIE11では正常に動きませんでした。

明確に同様の症状が現れている文献が見当たらなかったので確実とは言えませんが、「XMLHttpRequestはdataURLには対応していない」というのが最終的な結論です。

対策

  • IE11を使わせない
  • 同じ作者によるblueimp-canvas-to-blobというポリフィルライブラリを使って、一度Blobに変換する

今回はIE11対応がマストだったので、is-base64というライブラリも用いて、このようにしました。

export function convertImageToDataUrl(
    file: string | File | Blob,
    options?: loadImage.LoadImageOptions
): Observable<string> {
    return new Observable((subscriber) => {
        let data = file;

        if (isString(file) && isBase64(file, { mimeRequired: true })) {
            data = dataURLtoBlob(file) as Blob;
        }

        loadImage(
            data,
            (image) => {
                subscriber.next((image as HTMLCanvasElement).toDataURL());
                subscriber.complete();
            },
            {
                canvas: true,
                meta: true,
                orientation: true,
                ...options,
            }
        );
    });
}
脚注
  1. 今時IE11対応をしなければならない気持ちをどうかお察しください ↩︎

Discussion