Using FileReader.readAsArrayBuffer() on changed files in Firefox - javascript

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:
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)
Observe that the result is an array of 10 items representing the character codes of each item
While this is still running, delete 5 characters from the file and save
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)
Now added 10 characters (so the file is now 15 characters long)
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/

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

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.

Related

Error accessing "autoAllocateChunkSize" in PDFJS

i have a new problem after firefox 99 update.
I have an extension that reads pdfs using PDFJS (version pdfjs-2.13.216). It always worked and after the last update it started to give: Error: Permission denied to access property "autoAllocateChunkSize"
The same extension, with tiny changes, works in chrome normally.
Below is the code:
function getAllTextPDF(urlPDF) {
return new Promise(function (resolve , reject ) {
var pdfjsLib = this['pdfjs-dist/build/pdf']; //this because of firefox
pdfjsLib.GlobalWorkerOptions.workerSrc = '//mozilla.github.io/pdf.js/build/pdf.worker.js';
var loadingTask = pdfjsLib.getDocument(urlPDF);
//** from that line it no longer enters and generates the mentioned error **
loadingTask.promise.then(function(pdf) {
var numPages = pdf._pdfInfo.numPages;
var textoFinal = '';
var pagesPromises = [];
for (var i = 0; i < numPages; i++) {
(function (pageNumber) {
pagesPromises.push(getPageText(pageNumber, pdf));
})(i + 1);
}
Promise.all(pagesPromises).then(function (pagesText) {
for (var i = 0; i < pagesText.length; i++) {
textoFinal += pagesText[i];
}
if(textoFinal.length > 0){
resolve({text: textoFinal});
loadingTask.destroy();
delete pdfjsLib;
delete loadingTask;
}
});
}, function (reason) {
// PDF loading error
reject({reason});
return 0;
});
});
}
example URL of file to read (worked normally):
blob:https://dominio.com/4db038e1-ae8d-4a15-a78f-0d9d0a182c7c
The idea of ​​the method is simple, go through the pdf file and return all the text. However now as mentioned the error is generated. Detail that did not generate the error in version 32bit firefox, I believe it started in the last update of firefox 99.
I appreciate your help.

Console.log not working when testing regex

Can someone explain why my console log is not working?
Every time I select the file for verification to see if anything shows in the console nothing happens
document.addEventListener("DOMContentLoaded", function() {
document.getElementById('file').onchange = function() {
var extPermitidas = ['txt'];
var extArquivo = this.value.split('.').pop();
if (typeof extPermitidas.find(function(ext) {
return extArquivo == ext;
}) == 'undefined') {
alert('The file cannot be used because its extension is not allowed!');
return;
} else {
var file = this.files[0];
var reader = new FileReader();
reader.onload = function(progressEvent) {
// By lines
var lines = this.result.split('\n');
let N = /^(N1\d{14}.{78}|N9\d{14}.{14}\d{6})$/;
for (var line = 0; line < lines.length; line++) {
if (N.test(lines[line]) == N) {
console.log("valid file");
} else {
console.log("invalid file");
}
}
};
reader.readAsText(file);
}
alert('file successfully validated!');
}
});
<input type="file" id="file" />
EDIT
Could it be a problem in the conditional if (N.test(lines[line]) == N)?
This appears to be a function context issue. Try changing var file = this.files[0]; to var file = document.getElementById("file").files[0];.
this can sometimes be tricky since its value is determined by how a function is called (runtime binding). See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
Edit:
The conditional if (N.test(lines[line]) == N) is strange. The test() method executes a search for a match between a regular expression and a specified string and returns true or false. So, you don't need to compare the return of test to == N. Plus, you almost always want to use triple equals (===).
Have you already check your conditions or try to place the console.log in various parts of your code? Maybe it's an issue with event firing. I've tried to run you regex with my console I guess it works smoothly.
regex result

code is working while debug, but without not - Javascript / Jquery

I have an issue that I don't understand and I have no idea how to fix it, where to look for the cause. When I'm debugin (chrome) every thing is working, but during normal use it dosen't. For me is some kind of Science-Fiction, it would be better for me if it's more Science than Fiction :)
for (var i = 0; i < filteredAddedFiles.length; i++) {
if ((/\.(png|jpeg|jpg|gif)$/i).test(filteredAddedFiles[i].name)) {
(function (file) {
var reader = new FileReader();
var blob = b64toBlob(file.base64.replace('data:image/jpeg;base64,', ''), file.type);
reader.addEventListener("load", function () {
console.log(this);
var image = new Image();
image.src = window.URL.createObjectURL(blob);
image.onload = function () {
preview.innerHTML += drawHtml(image, file)
};
//I tried:
//(function (b) {
// var image = new Image();
// image.addEventListener("load", function () {
// preview.innerHTML += drawHtml(this, file);
// //window.URL.revokeObjectURL(image.src);
// });
// image.src = window.URL.createObjectURL(b);
//})(blob);
});
reader.readAsDataURL(blob);
})(filteredAddedFiles[i]);
} else {
errors += filteredAddedFiles[i].name + " Unsupported Image extension\n";
}
}
here I attached a short movie that shows how its working
link to movie
not working - I mean - it looks like the all thing inside for dosen't executed
EDIT: 1
#Teemu - I turn on logs in chrome console and all console.log's appear
#Katie.Sun - now the above for - console.log(filteredAddedFiles.length); is 0 - but when I'm debuging code the same console.log(filteredAddedFiles.length); have values !
EDIT: 2
#Matti Price
filteredAddedFiles - is the result of custom logic of page, filtering,
validation etc.
Everything starts here:
addedFiles = added(files); // files - FileList from input this is a read only array of obj
function added(from) {
var out = [];
for (var i = 0; i < from.length; i++) {
(function (obj) {
var readerBase64 = new FileReader();
readerBase64.addEventListener("load", function () {
var fileBase64 = readerBase64.result;
var row = { name: obj.name, size: obj.size, type: obj.type, base64: fileBase64 }
out.push(row);
});
readerBase64.readAsDataURL(obj);
})(from[i]);
}
return out;
}
then addedFiles - do something farther and transform into filteredAddedFiles later. what I found in added function? during debug there is an length value witch is correct, but when I opened the __proto__: Array(0) I found length property = 0.
Should this length value be equal to the value from above length?
The second thing:
I have to admit that I don't have enough knowledge aboute addEventListener. Are there any queues here or some thread etc?
EDIT: 3
After last #Teemu comment I made some changes (I had to read a lot aboute promisses etc:)), but output is the same console.log("out resolve", out); shows a array of object, but console.log("out.length then", out.length); shows 0 and the small blue i-icon show msg - Value below evaluated just now
var out = [];
for (var i = 0; i < files.length; i++) {
fillArray(files[i], out);
}
console.log("out resolve", out);
console.log("out.length then", out.length);
function fillArray(obj, out) {
return new Promise(function (resolve, reject) {
var readerBase64 = new FileReader();
readerBase64.addEventListener("load", function () {
var fileBase64 = readerBase64.result;
var row = { name: obj.name, size: obj.size, type: obj.type, out.push(row);
resolve(out);
});
readerBase64.readAsDataURL(obj);
});
}
After I posted the edit above I relized that I just create promise, I forgot to call `promise
, but this is not important, because 90% of my code has been changed because of this topic and the answer of the Golden Person #Kaiido
URL.createObjectURL() - is synchronous. You don't need your Promise wrapping event handlers hell, all can be done in a single loop.
In my case, this is a better solution than a filereader, I have to upload only images, and with some restrictions thanks to which I don't have to worry about freeze the Internet browser, because of the synchronous nature of createObjectURL()
Thank you for your help and commitment

execCommand('copy') not working programatically, only when executed via DevTools console

Source:
const package = document.querySelector('td[data-bind="text: packageName"');
if (package.textContent.indexOf('Adaptive') !== -1) {
package.click();
const stacks_tab = document.querySelector('ul[class="tabsExpanded"]').children[5];
stacks_tab.click();
function get_sources() {
const sources = [];
const stacks = document.querySelectorAll('span[data-bind="text:duration"]');
for (let i = 0; i < stacks.length; i++) {
stacks[i].click();
let renditions = document.querySelectorAll('span[class="blockUnSelected"]');
renditions[(i+1) * 8 - 1].click();
sources.push(document.querySelectorAll('p[data-bind="text: $data.name"]')[0].textContent);
}
let copy = '';
for (let i = 0; i < sources.length; i++) {
const change_brackets = sources[i].replace(/\/tveorigin\/vod\/ae\//, '');
const no_pd1 = change_brackets.replace(/-pd1/g, '');
copy += no_pd1 + ',';
}
if (copy === '') {
setTimeout(get_sources, 500);
} else {
const hidden = document.createElement('input');
hidden.value = copy;
document.querySelector('body').appendChild(hidden);
hidden.select();
function copy_sources() {
console.log('running');
hidden.select();
if (!document.execCommand('copy')) {
setTimeout(copy_sources, 500);
} else {
console.log('Sources copied!');
}
}
copy_sources();
}
}
get_sources();
} else {
console.log('There is no Adaptive package in this content.');
}
Line 45 is what isn't working.
That code won't make a lot of sense, but here's the use case:
I'm trying to automate part of my job by injecting some JavaScript into the Chrome DevTools console on our CMS that we use for video content where I work. What the script does is click a few elements, then grabs some file locations and copies them to the clipboard as comma separated values. I had this working just fine before, but I decided to try and make the script better...and now the document.execCommand('copy') is just not working.
As you can see, I use some recursion to continuously select the hidden input value and then I try to copy it, and if it fails, I try again in 500 ms. I also log 'running' to ensure the function is actually running (it is). The execCommand() function keeps returning false every 500ms. BUT, if I type it into the console manually and run it, it returns true and works fine even as the recursive function continues to return false. So for some reason, it won't work in the context of my script, but works totally fine when run manually.
Like I said before, it WAS working programatically before, but I changed some stuff to make the script better and more automated, and it won't work anymore. Here's the code with execCommand() working fine:
const sources = [];
const stacks = document.querySelectorAll('span[data-bind="text:duration"]');
for (let i = 0; i < stacks.length; i++) {
stacks[i].click();
let renditions = document.querySelectorAll('span[class="blockUnSelected"]');
renditions[(i+1) * 8 - 1].click();
sources.push(document.querySelectorAll('p[data-bind="text: $data.name"]')[0].textContent);
}
let copy = '';
for (let i = 0; i < sources.length; i++) {
const change_brackets = sources[i].replace(/\/tveorigin\/vod\/ae\//, '');
const no_pd1 = change_brackets.replace(/-pd1/g, '');
copy += no_pd1 + ',';
}
const hidden = document.createElement('input');
hidden.value = copy;
document.querySelector('body').appendChild(hidden);
hidden.select();
document.execCommand('copy');
I just tested that code and it still works, and copies the text to the clipboard as intended. The only notable different I see is that in the older code, I run execCommand() in the global context, whereas in the new script, it's in a function context. Could this have something to do with it?
So the solution to this was odd. execCommand() can only be triggered by a user event handler, so what I had to do was attach a click listener to the window, then invoke a click event on the hidden node. Because that triggered a click handler, that made it work!

Print function log /stack trace for entire program using firebug

Firebug has the ability to log calls to a particular function name. I'm looking for a bug that sometimes stops a page from rendering, but doesn't cause any errors or warnings. The bug only appears about half the time. So how do I get a list of all the function calls for the entire program, or some kind of stack trace for the execution of the entire program?
Firefox provides console.trace() which is very handy to print the call stack. It is also available in Chrome and IE 11.
Alternatively try something like this:
function print_call_stack() {
var stack = new Error().stack;
console.log("PRINTING CALL STACK");
console.log( stack );
}
When i need a stack trace i do the following, maybe you can draw some inspiration from it:
function logStackTrace(levels) {
var callstack = [];
var isCallstackPopulated = false;
try {
i.dont.exist += 0; //doesn't exist- that's the point
} catch (e) {
if (e.stack) { //Firefox / chrome
var lines = e.stack.split('\n');
for (var i = 0, len = lines.length; i < len; i++) {
callstack.push(lines[i]);
}
//Remove call to logStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
else if (window.opera && e.message) { //Opera
var lines = e.message.split('\n');
for (var i = 0, len = lines.length; i < len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
var entry = lines[i];
//Append next line also since it has the file info
if (lines[i + 1]) {
entry += " at " + lines[i + 1];
i++;
}
callstack.push(entry);
}
}
//Remove call to logStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
}
if (!isCallstackPopulated) { //IE and Safari
var currentFunction = arguments.callee.caller;
while (currentFunction) {
var fn = currentFunction.toString();
var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf("(")) || "anonymous";
callstack.push(fname);
currentFunction = currentFunction.caller;
}
}
if (levels) {
console.log(callstack.slice(0, levels).join('\n'));
}
else {
console.log(callstack.join('\n'));
}
};
Moderator's note: The code in this answer seems to also appear in this post from Eric Wenderlin's blog. The author of this answer claims it as his own code, though, written prior to the blog post linked here. Just for purposes of good-faith, I've added the link to the post and this note.
I accomplished this without firebug. Tested in both chrome and firefox:
console.error("I'm debugging this code.");
Once your program prints that to the console, you can click the little arrow to it to expand the call stack.
Try stepping through your code one line or one function at a time to determine where it stops working correctly. Or make some reasonable guesses and scatter logging statements through your code.
Try this:
console.trace()
I don't know if it's supported on all browsers, so I would check if it exists first.

Categories

Resources