Использование FileReader.readAsArrayBuffer() для измененных файлов в Firefox

Я столкнулся с нечетной проблемой, используя FileReader.readAsArrayBuffer, которая только влияет на Firefox (я тестировал в текущей версии - v40). Я не могу сказать, что я просто делаю что-то неправильно или это ошибка Firefox.

У меня есть JavaScript, который использует readAsArrayBuffer для чтения файла, указанного в поле <input>. В нормальных условиях все работает правильно. Однако, если пользователь изменяет файл после его выбора в поле <input>, readAsArrayBuffer может сильно запутаться.

ArrayBuffer Я возвращаюсь из readAsArrayBuffer, всегда имеет длину, из которой был первоначально создан файл. Если пользователь меняет файл, чтобы сделать его более крупным, я не получаю ни одного из байтов после исходного размера. Если пользователь меняет файл, чтобы сделать его меньше, буфер по-прежнему имеет тот же размер, а "лишний" в буфере заполняется кодами символов 90 (заглавная буква "Z", если смотреть как строка).

Поскольку этот код настолько прост и отлично работает в каждом другом браузере, который я тестировал, я думаю, что это проблема Firefox. Я сообщил об этом как об ошибке в Firefox, но я хочу убедиться, что это не просто что-то очевидное. Я делаю неправильно.

Поведение может быть воспроизведено в следующем фрагменте кода. Все, что вам нужно сделать, это:

  • Найдите текстовый файл с 10 символами (10 не является магическим числом - я просто использую его в качестве примера)
  • Обратите внимание, что результатом является массив из 10 элементов, представляющих коды символов каждого элемента.
  • Пока это все еще выполняется, удалите 5 символов из файла и сохраните
  • Обратите внимание, что результат по-прежнему представляет собой массив из 10 элементов: первые 5 верны, но последние 5 - все 90 (прописная буква Z)
  • Теперь добавлено 10 символов (так что файл длиной 15 символов)
  • Обратите внимание, что результат по-прежнему представляет собой массив из 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>

Ответы

Ответ 1

Интересно, что Firefox кэширует размер буфера, даже если файл изменен.

Вы можете обратиться к этой ссылке, заменив readAsArrayBuffer на пользовательский функционал, который использует readAsBinaryString. Работает нормально в 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();

Ответ 2

Я думаю, что вы попали в ошибку Firefox. Однако, как вы указали, readAsArrayBuffer правильно во всех поддерживаемых браузерах, кроме Firefox, в то время как readAsBinaryString поддерживается всеми браузерами, кроме IE.

Следовательно, можно предпочесть readAsBinaryString когда он существует, и в readAsArrayBuffer случае не выполнить 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: весь API FileReader не поддерживается.
  • Почти во всех других браузерах: используется readAsBinaryString.