Chrome/WebKit XHR File Upload - javascript

I have searched relentlessly but just can't figure this one out. Why will this XHR connection work perfectly fine in Firefox but breaks in Chrome? I'm using this in conjunction with AngularJS, by the way.
$scope.upload = function(project, file) {
var formData = new FormData(); //Not really sure why we have to use FormData(). Oh yeah, browsers suck.
formData.append('', file.file); //The real file object is stored in this file container
file.xhr = new XMLHttpRequest();
file.xhr.open('PUT', '/api/projects/'+project._id+'/files', true);
//Progress event listener
file.xhr.upload.onprogress = function(event) {
if(event.lengthComputable) {
file.uploadPercent = Math.round(event.loaded / event.total * 100);
}
};
//Upload complete listener
file.xhr.upload.onload = function(event) {
file.uploaded = true;
};
//Every time the status changes
file.xhr.onreadystatechange = function(event) {
if(event.target.readyState == 4) {
//The file has been added, so tag the ID onto the file object
console.log(event.target.responseText);
file._id = JSON.parse(event.target.responseText)._id;
} else {
return;
}
};
file.xhr.send(formData);
};
In Firefox, the file is sent just fine to my server, and the responseText is returned exactly like I'd expect. However, in Chrome, I get this error: Error: INVALID_STATE_ERR: DOM Exception 11
Error: An attempt was made to use an object that is not, or is no longer, usable., which would be more helpful if it told me exactly what object was attempted to be used. I've read here that I should try to set async to false and use onreadystatechange, but I'm not sure how that helps, since I'm already using onreadystatechange.

Bug from 2009: XMLHttpRequest doesn't work while submitting a form - https://bugs.webkit.org/show_bug.cgi?id=23933

Related

jQuery: Getting Ajax object from within either $.ajaxSetup() or $.ajaxPrefilter()

I'm working on a web app which makes multiple API calls using jQuery's ajax. Because all of these requests are to my own API and use very similar sets of data, I'm hoping to use the features of either the $.ajaxSetup() or $.ajaxPrefilter() to make a generalized config, avoiding repetition and and reducing the footprint of my app significantly.
I'm using a function in the xhr config to calculate upload progress as a percentage which would also need to move. The behaviour of this is to be the same in every API call, but what I was hoping to do is provide a smaller "progress" function, unique to each ajax call that can then do whatever it wants with that percentage data once calculated. This would allow for easy operation and minimal code in all subsequent ajax calls.
If everything is written into the same ajax call as below - it works great! The progress function logs upload progress to console as expected.
$.ajax({
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", function(evt) {
if (evt.lengthComputable) {
this.progress_percentage = parseInt(evt.loaded / evt.total * 100);
if(typeof this.progress === 'function'){
this.progress(this.progress_percentage); // <--- Run ajax-call specific function if defined.
}
}else{
console.warn('Content-length is not set. Unable to display upload progress.');
}
}, false);
return xhr;
},
progress: function(e) {
console.log('Called by xhr(). Progress: ' + e) // <-- Output logged successfully.
}
});
The difficulty comes in when moving the xhr function into the generalized config.
Because at that point the ajax object has not been created yet, I'm unsure of how to access it and check for the presence of the progress() function. To illustrate:
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
options.xhr = function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", function(evt) {
if (evt.lengthComputable) {
this.progress_percentage = parseInt(evt.loaded / evt.total * 100);
if(typeof this.progress === 'function'){
this.progress(this.progress_percentage); // <--- progress function will NEVER be defined at this point because as far as prefilter is concerned, the ajax object has not been created yet and no call-specific behaviour can be set. "this" points to the window and NOT the ajax object.
}
}else{
console.warn('Content-length is not set. Unable to display upload progress.');
}
}, false);
return xhr;
}
});
I was expecting there to be some way to access the ajax object from it's setup functions but have been unable to find anything in the documentation. Hoping for either a way to do that, or some advice on how to refactor my code to get this running!
I realise upload progress is a fairly simple function and there will certainly be a lib for it, but as I have other configs and features within the app that would also benefit, I'd much prefer to get this running instead, if at all possible.
What would you suggest? Thank you.

Firefox CORS issue with JSON

I'm trying to use the API from https://www.themoviedb.org/. (The key is free and can be changed easily, so I'll include it because without it, you can’t even test their functions).
Now my JavaScript is working fine in FF when it's hosted local, but not on GitHub pages.
Here is a function that doesn’t work. Error is:
NetworkError: A network error occurred.
…and it appears to happen after bhttp.send();.
function getMovieDetails() {
var reqURL = "https://api.themoviedb.org/3/movie/latest?api_key=afe4e10abbb804e2b4a4f8a3ef067ad5&language=en-US";
var bhttp = new XMLHttpRequest();
bhttp.open("GET", reqURL, false);
bhttp.setRequestHeader("Content-type", "json");
bhttp.send();
var response = JSON.parse(bhttp.responseText);
var str = JSON.stringify(response, null, 2);
return response;
}
console.log(getMovieDetails());
It works fine in Chrome. Googling appears to indicate it’s a CORS problem, but as far as I know GitHub pages supports CORS, so I don't know what I'm doing wrong.
I'm not a firefox user, so you will need to test this. But if the theory of async blocking is true this should work.
I've modified it to use a simple callback, personally I wouldn't use callbacks but would make into promises, but that's another question :)
function getMovieDetails(callback) {
var reqURL = "https://api.themoviedb.org/3/movie/latest?api_key=afe4e10abbb804e2b4a4f8a3ef067ad5&language=en-US";
var bhttp = new XMLHttpRequest();
bhttp.open("GET", reqURL, true);
bhttp.setRequestHeader("Content-type", "json");
bhttp.onload = function() {
if (bhttp.readyState === 4) {
if (bhttp.status === 200) {
callback(JSON.parse(bhttp.responseText));
} else {
console.error(bhttp.statusText);
}
}
};
bhttp.send();
}
getMovieDetails(function (movie) {
console.log(movie);
});
Alright looks like skirtle was correct, the issue was the firefox addon Privacy-Badger blocking the API. I feel pretty stupid now but at least my code is now pretty clean.

XMLHttprequest.send(null) is crashing my code

I'm currently writing a search function using JavaScript.
However, when I attempt to test my creation, I find that it stops about halfway through for no discernible reason.
Below is my code:
document.getElementById("test").innerHTML = "";
var Connect = new XMLHttpRequest();
Connect.open("GET", "xmlTest.xml", false);
document.getElementById("test").innerHTML = "1";
Connect.send(null);
document.getElementById("test").innerHTML = "2";
var docX = Connect.responseXML;
var linjer = docX.getElementsByTagName("linjer");
The first line is there to clear a potential error message from earlier in the code. Then I attempt to open up an XML file, as I need to read from it.
As you can see, I've entered two debug statements there; they will print 1 or 2 depending on how far I get in the code.
Using this, I've found that it stops exactly on the Connect.send(null); statement (as 1 gets printed, but 2 never does), but I can't figure out why. Google says that it might be that chrome can't access local files, but when I found a way to allow Chrome to do this, it still did not work.
What am I doing wrong?
This might be a synchronous issue that requires a response that your code simply is not getting.
Try using an async call instead:
Connect.open("GET", "xmlTest.xml", true);
Also make sure to setup proper callbacks since you'll be using async here now instead of synchronous code, like so:
// Global variable scope
var docX;
var linjer;
// Define your get function
getDoc = function(url, cbFunc) {
var Connect = new XMLHttpRequest();
// Perform actions after request is sent
// You'll insert your callback here
Connect.onreadystatechange = function() {
// 4 means request finished and response is ready
if ( Connect.readyState == 4 ) {
// Here is where you do the callback
cbFunc(Connect.responseXML);
}
};
// 'true' param means async, it is also the default
Connect.open('GET', url, true);
Connect.send();
}
// Define your callback function
callbackFunction = function(responseXML) {
// XML file can now be stored in the global variable
window.docX = responseXML;
window.linjer = window.docX.getElementsByTagName("linjer");
}
// And here is the call you make to do this
getDoc("xmlTest.xml", callbackFunction);
For better understanding of all of this, do some research on scope, closures, callbacks, and async.

Illegal invocation with start/noteOn using Web Audio API

So basically I've tested this in Chrome and maybe the order of codes is off or whatever, trying to cover some of the functionality of HTML5 audio using "web audio" due to the range requests bug, for the making of games with looping sound effects and music...
I get an "illegal exception" error. Here's the code:
var url='example.mp3';
var a_result=new Object();
a_result.loaded=false;
a_result.evalstring='';
a_result.loop=false;
a_result.play=function(){
var asrc=a_result.source;
asrc.loop=a_result.loop;
//try{
var playfunc=asrc.start||asrc.noteOn;
playfunc(0);
//}catch(e){
/* do nothing */
//}
}
a_result.pause=function(){
var asrc=a_result.source;
try{
var stopfunc=asrc.stop||asrc.noteOff;
stopfunc(0);
}catch(e){
/* do nothing */
}
}
var asrc=actx.createBufferSource();
asrc.connect(actx.destination);
var req=new XMLHttpRequest();
req.open('GET',url,true);
req.responseType='arraybuffer';
req.onload=function(){
if(a_result.loaded==false){
asrc.buffer=actx.createBuffer(req.response,false);
a_result.source=asrc;
a_result.loaded=true;
}
var cont=a_result;
eval(cont.evalstring);
}
req.onerror = function() {
if(a_result.loaded==false){
a_result.loaded=true;
}
}
try{
req.send(null);
}catch(e){
req.onerror();
}
return a_result;
And then later on after the sound has loaded I do something like:
a_result.play();
And instead of playing it gives the error.
Here is a sound test that uses the above code with the fix suggested below, and it works in Chrome, successfully working around the range requests issue on a crappy web server. Here is another that has issues (throwing some kind of "invalid string" error at asrc.buffer=actx.createBuffer(req.response,false); in Iron and silently screwing up in Chrome).
Here is the code edited according to the suggestions:
var url='example.mp3'
var a_result=new Object();
a_result.loaded=false;
a_result.evalstring='';
a_result.loop=false;
a_result.play=function(){
var asrc=a_result.source;
asrc.loop=a_result.loop;
try{
if(asrc.start){
asrc.start(0);
}else{
asrc.noteOn(0);
}
}catch(e){
/* do nothing */
}
}
a_result.pause=function(){
var asrc=a_result.source;
try{
if(asrc.stop){
asrc.stop(0);
}else{
asrc.noteOff(0);
}
}catch(e){
/* do nothing */
}
}
var asrc=actx.createBufferSource();
asrc.connect(actx.destination);
var req=new XMLHttpRequest();
req.open('GET',url,true);
req.responseType='arraybuffer';
req.onload=function(){
actx.decodeAudioData(req.response,function(buffer){
if(buffer){
if(a_result.loaded==false){
asrc.buffer=buffer;
a_result.source=asrc;
a_result.loaded=true;
}
var cont=a_result;
eval(cont.evalstring);
}
});
}
req.onerror = function() {
if(a_result.loaded==false){
a_result.loaded=true;
}
}
try{
req.send(null);
}catch(e){
req.onerror();
}
return a_result;
It no longer appears to have a syntax error, however, it does not seem to solve the playability issues on Chrome (second test case above updated). Specifically, after a sound is stopped it does not want to play again. -- Apparently because it's in the spec. The buffer has to be applied to a new sound source every time you play.
In your new page, you're calling createBuffer(data, false) on the results of an XMLHTTPRequest. You almost certainly want to be calling decodeAudioData() on those results, instead - createBuffer doesn't have a 2-parameter version, and doesn't match the parameters you're passing even if you're trying to push arbitrary data into a buffer. It appears from a browse of your code that you're pulling down MP3 or Ogg files - you need to decode them.
The play method (either start() or noteOn()) needs to be invoked ON the buffersourcenode object, not disassociated from it. You have a few options:
1) I'd strongly recommend just including my audio context monkeypatch library (https://github.com/cwilso/AudioContext-MonkeyPatch/) and using the new (start()) syntax. no muss, no fuss.
2) You can just use an if statement instead of using playfunc to switch:
if (asrc.start)
asrc.start(0);
else
asrc.noteOn(0);
3) You can bind() the play function to the object and keep your code essentially the same:
var playfunc=asrc.start ? asrc.start.bind(asrc) || asrc.noteOn.bind(asrc);
playfunc(0);
4) You can push the playfunc() method onto the object itself (I think this will work):
asrc.playfunc=asrc.start||asrc.noteOn;
asrc.playfunc(0);

web workers behave differently in webkit than Firefox

I have a web application that works just fine in modern webkit-based browsers (http://montecarlo-tester.appspot.com/). Basically it uses a webworker to fetch data from a server, and then sends it back after performing some computations.
It works just fine in Chrome/Safari (no errors in console), but when I try to use it in Firefox, it doesn't. I've deduced that somehow the variable 'iterations' is not set properly in Firefox. Unfortunately, Firefox lacks a debugger (for web workers), and javascript has functional scoping, so it's really hard to pinpoint where the problem is. I've posted the javascript code for my web worker, and I was wondering if anybody could point out where I went wrong:
importScripts('/static/js/mylibs/jquery.hive.pollen-mod.js');
$(function (data) {
main();
//while(main());
close();
});
function main() {
//make an ajax call to get a param
var iterations//value will be set by server response
var key//key of the datastore object
var continueloop = true;
p.ajax.post({
url:'/getdataurl',
dataType: "json",
success: function(responseText){
if (responseText === null) {
var workermessage = {
"log":"responseText is null. Either the server has issues or we have run out of stuff to compute."
};
$.send(workermessage);
continueloop = false;
}
iterations = responseText.iterationsjob;
key = responseText.key;
}
});
if (continueloop === false) {
return false;
}
//here is where I think the problems begin. In chrome/safari, iterations = 1000.
//In Firefox however, iterations = null. As a result, everything after that does not work.
var i,x,y,z;
var count = 0;
var pi;
start = new Date();
for (i=0;i<iterations;i++) {
x = Math.random();
y = Math.random();
z = x*x+y*y;
if(z<=1.0){
count++;
}
}//end for loop
pi = count/(iterations)*4.0;
end = new Date();
result = {
"estimated_pi":pi,
"num_iter":iterations,
"duration_ms":end.valueOf()-start.valueOf(),
"key":key
};
//send results to the server
p.ajax.post({
url: "/resultshandler",
dataType:'json',
data: result,
success: function()
{
//do nothing!
}
});
$.send(result);
return true;//persists the loop
}
You're doing an async XHR, then immediately doing a loop trying to use its results. I have no idea why this possibly works in Chrome, but it's definitely racy. Have you tried passing "sync:true" in your post options?
Edit: Oh, nevermind. I see why your code works. The hive.pollen script has this wonderful bit:
sync: navigator.userAgent.toLowerCase().indexOf('safari/') != -1 ? false : true,
So it's doing a sync XHR in Chrome/Safari and an async one in everything else, by default (because it passes options.sync as the value for the async argument to XMLHttpRequest.open, which is backwards, but whatever; it does mean that you actually need to pass sync: false at the callsite to get sync behavior). And since you don't specify whether you want sync or async, you get sync in Chrome and async in Firefox.
Oh, and the script has this wonderful comment before that line:
// TODO: FIX THIS.

Categories

Resources