web-dev-qa-db-ja.com

Firefoxで変更されたファイルでFileReader.readAsArrayBuffer()を使用する

FileReader.readAsArrayBuffer を使用して奇妙な問題に直面しています。これはFirefoxにのみ影響するようです(現在のバージョン-v40でテストしました)。何か間違ったことをしているだけなのか、これがFirefoxのバグなのかわかりません。

readAsArrayBufferを使用して<input>フィールドで指定されたファイルを読み取るJavaScriptがあります。通常の状況では、すべてが正常に機能します。ただし、ユーザーが<input>フィールドで選択した後にファイルを変更すると、readAsArrayBufferが非常に混乱する可能性があります。

ArrayBufferから返されるreadAsArrayBufferの長さは、常にファイルの元の長さです。ユーザーがファイルを変更して大きくする場合、元のサイズより後のバイトは取得されません。ユーザーがファイルを小さくして変更すると、バッファーは同じサイズのままで、バッファーの「超過」には文字コード90(文字列として表示される場合は大文字の「Z」)が入力されます。

このコードは非常に単純で、テストした他のすべてのブラウザーで完全に機能するため、Firefoxの問題だと考えています。私は バグとして報告 をFirefoxに報告しましたが、これが私が間違っていることの明らかなものではないことを確認したいと思います。

この動作は、次のコードスニペットで再現できます。あなたがしなければならないのは:

  1. 10文字のテキストファイルを参照します(10はマジックナンバーではありません-例として使用しています)
  2. 結果が各アイテムの文字コードを表す10個のアイテムの配列であることを確認します
  3. これがまだ実行されている間に、ファイルから5文字を削除して保存します
  4. 結果はまだ10項目の配列であることに注意してください。最初の5つは正しいが、最後の5つはすべて90(大文字のZ)です。
  5. 10文字を追加しました(したがって、ファイルの長さは15文字になりました)
  6. 結果がまだ10個のアイテムの配列であることに注意してください-最後の5個は返されません
function ReadFile() {
  var input = document.getElementsByTagName("input")[0];
  var output = document.getElementsByTagName("textarea")[0];

  if (input.files.length === 0) {
    output.value = 'No file selected';
    window.setTimeout(ReadFile, 1000);
    return;
  }

  var fr = new FileReader();
  fr.onload = function() {
    var data = fr.result;
    var array = new Int8Array(data);
    output.value = JSON.stringify(array, null, '  ');
    window.setTimeout(ReadFile, 1000);
  };
  fr.readAsArrayBuffer(input.files[0]);

  //These two methods work correctly
  //fr.readAsText(input.files[0]);
  //fr.readAsBinaryString(input.files[0]);
}

ReadFile();
<input type="file" />
<br/>
<textarea cols="80" rows="10"></textarea>

スニペットが機能しない場合、サンプルコードはここでJSFiddleとしても利用可能です: https://jsfiddle.net/Lv5y9m2u/

18

興味深いことに、Firefoxはファイルが変更されてもバッファサイズをキャッシュしているようです。

これを参照することができます linkreadAsArrayBufferreadAsBinaryStringを使用するカスタム機能に置き換えます。 FirefoxおよびChromeで正常に動作します

function ReadFile() {
var input = document.getElementsByTagName("input")[0];
var output = document.getElementsByTagName("textarea")[0];

if (input.files.length === 0) {
    output.value = 'No file selected';
    window.setTimeout(ReadFile, 1000);
    return;
}

var fr = new FileReader();
fr.onload = function () {
    var data = fr.result;
    var array = new Int8Array(data);
    output.value = JSON.stringify(array, null, '  ');
    window.setTimeout(ReadFile, 1000);
};
fr.readAsArrayBuffer(input.files[0]);



//These two methods work correctly
//fr.readAsText(input.files[0]);
//fr.readAsBinaryString(input.files[0]);
}
if (FileReader.prototype.readAsArrayBuffer && FileReader.prototype.readAsBinaryString) {
    FileReader.prototype.readAsArrayBuffer = function readAsArrayBuffer () {
        this.readAsBinaryString.apply(this, arguments);
        this.__defineGetter__('resultString', this.__lookupGetter__('result'));
        this.__defineGetter__('result', function () {
            var string = this.resultString;
            var result = new Uint8Array(string.length);
            for (var i = 0; i < string.length; i++) {
                result[i] = string.charCodeAt(i);
            }
            return result.buffer;
        });
    };
}
ReadFile();
11

Firefoxのバグに当たっていると思います。ただし、指摘したとおり、readAsArrayBufferはFirefoxを除くすべてのサポートされているブラウザーで正しく動作しますが、readAsBinaryStringはIEを除くすべてのブラウザーでサポートされています。

したがって、存在する場合はreadAsBinaryStringを優先し、そうでない場合はreadAsArrayBufferにフェールバックすることができます。

function readFileAsArrayBuffer(file, success, error) {
    var fr = new FileReader();
    fr.addEventListener('error', error, false);
    if (fr.readAsBinaryString) {
        fr.addEventListener('load', function () {
            var string = this.resultString != null ? this.resultString : this.result;
            var result = new Uint8Array(string.length);
            for (var i = 0; i < string.length; i++) {
                result[i] = string.charCodeAt(i);
            }
            success(result.buffer);
        }, false);
        return fr.readAsBinaryString(file);
    } else {
        fr.addEventListener('load', function () {
            success(this.result);
        }, false);
        return fr.readAsArrayBuffer(file);
    }
}

使用法:

readFileAsArrayBuffer(input.files[0], function(data) {
    var array = new Int8Array(data);
    output.value = JSON.stringify(array, null, '  ');
    window.setTimeout(ReadFile, 1000);
}, function (e) {
    console.error(e);
});

作業フィドル: https://jsfiddle.net/Lv5y9m2u/6/

ブラウザサポート:

  • Firefox:readAsBinaryStringを使用しますが、これは問題ありません。
  • IE> = 10:サポートされているreadAsArrayBufferを使用します。
  • IE <= 9:FileReader AP​​I全体はサポートされていません。
  • 他のほとんどすべてのブラウザー:readAsBinaryStringを使用します。
6
FelisCatus