Using FileReader.readAsArrayBuffer() on changed fi

2020-02-24 20:21发布

I'm running into an odd problem using FileReader.readAsArrayBuffer that only seems to affect Firefox (I tested in the current version - v40). I can't tell if I'm just doing something wrong or if this is a Firefox bug.

I have some JavaScript that uses readAsArrayBuffer to read a file specified in an <input> field. Under normal circumstances, everything works correctly. However, if the user modifies the file after selecting it in the <input> field, readAsArrayBuffer can get very confused.

The ArrayBuffer I get back from readAsArrayBuffer always has the length that the file was originally. If the user changes the file to make it larger, I don't get any of the bytes after the original size. If the user changes the file to make it smaller, the buffer is still the same size and the 'excess' in the buffer is filled with character codes 90 (capital letter 'Z' if viewed as a string).

Since this code is so simple and works perfectly in every other browser I tested, I'm thinking it's a Firefox issue. I've reported it as a bug to Firefox but I want to make sure this isn't just something obvious I'm doing wrong.

The behavior can be reproduced by the following code snippet. All you have to do is:

  1. Browse for a text file that has 10 characters in it (10 is not a magic number - I'm just using it as an example)
  2. Observe that the result is an array of 10 items representing the character codes of each item
  3. While this is still running, delete 5 characters from the file and save
  4. Observe that the result is still an array of 10 items - the first 5 are correct but the last 5 are all 90 (capital letter Z)
  5. Now added 10 characters (so the file is now 15 characters long)
  6. Observe that the result is still an array of 10 items - the last 5 are not returned

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>

In case the snippet does not work, the sample code is also available as a JSFiddle here: https://jsfiddle.net/Lv5y9m2u/

2条回答
在下西门庆
2楼-- · 2020-02-24 20:29

I think you are hitting a bug of Firefox. However, as you pointed out, readAsArrayBuffer behaves correctly in every supported browser except Firefox while readAsBinaryString is supported by every browser except IE.

Therefore, it is possible to prefer readAsBinaryString when it exists and fail back to readAsArrayBuffer otherwise.

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

Usage:

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

Working fiddle: https://jsfiddle.net/Lv5y9m2u/6/

Browser Support:

  • Firefox: Uses readAsBinaryString, which is not problematic.
  • IE >= 10: Uses readAsArrayBuffer which is supported.
  • IE <= 9: The entire FileReader API is not supported.
  • Almost all other browsers: Uses readAsBinaryString.
查看更多
SAY GOODBYE
3楼-- · 2020-02-24 20:40

Interesting, looks like Firefox is caching the buffer size even the file is modified.

You can refer to this link, replaced readAsArrayBuffer with is custom functionality which uses readAsBinaryString. Its working fine in Firefox and 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();
查看更多
登录 后发表回答