Webkit (Chrome or Safari) way doing AJAX safely on onunload / onbeforeunload - ajax

In my tests Chrome (and I guess as any other webkit browser probably) is UNABLE to perform an AJAX request BEFORE leaving a page.
Imagine for instance, that you need to clean up something on the server because the user clicked on some link or left the page.
First thing I noticed is that window.onunload DOES NOT work anyhow on Chrome (Webkit?)
Once you are using window.onbeforeunload MAKE SURE you DON'T put in the the body like this: Cause it is ignored. YOU HAVE TO do window.onbeforeunload=function() {...} to make sure the binding is done (or use jquery or protoype libs for this)
WITHIN your onbeforeunload code a ASYNCHRONOUS Ajax like this WON'T work either:
var req = new XMLHttpRequest();
req.open("GET", "dosomething.page");
req.send(null);
(although this will WORK in Firefox)
It will work if ONLY if the request is made SYNCHRONOUS like this:
var req = new XMLHttpRequest();
req.open("GET", "dosomething.page",false);
req.send(null);
Although keep in mind that synchronous can cause the browser to hang for 2minutes if the server does NOT reply.
Also Firefox DOES NOT seem to work with onunload.
So in the end YOU have to provide with a different code path for each browser or browser family.
I haven't been able to test IE properly on this.
Does anyone know?
Is IE more like Chrome or FF in this?
or is it different to both as well?

IE seems to work just like Firefox (Gecko) in this particular case:
With this code you can make it work for WebKit, Firefox and IE:
// Browser detection
var Browser={
IE: !!(window.attachEvent && !window.opera),
Opera: !!window.opera,
WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
};
// Ensures the Ajax Get is performed... Asynchronously if possible
// or Synchronously in WebKit Browsers (otherwise it'll most probably fail)
function ensureAJAXGet(url, args) {
var async=!Browser.WebKit;
var finalUrl=url;
var sep="";
for (var key in args) {
sep=(sep=="?")?"&":"?";
finalUrl=finalUrl+sep+encodeURIComponent(key)+"="+encodeURIComponent(args[key]);
}
var req = new XMLHttpRequest();
req.open("GET", finalUrl,async);
req.send();
return req;
}
// Sets up an unload function for all browsers to work (onunload or onbeforeunload)
function onUnload(func) {
if(Browser.WebKit) {
window.onbeforeunload=func;
} else {
window.onunload=func;
}
}
A test html could be this:
var browser="?"
if (Browser.IE) {
browser="IE";
} else if (Browser.Opera) {
browser="Opera";
} else if (Browser.WebKit) {
browser="WebKit";
} else if (Browser.Gecko) {
browser="Gecko";
} else if (Browser.MobileSafari) {
browser="MobileSafari";
}
function unload() {
ensureAJAXGet("testajax.jsp", {"browser": browser});
}
onUnload(function() { unload(); });
That is:
To do something onunload you call onUnload() instead of directly using either window.onload or window.onunload. This ensures that the proper event is used (onbeforeunload in WebKit and onunload on the rest)
To sent some GET Ajax on an unload function use ensureAjaxGet() that will be asynchronous AJAX when possible and synchronous when needed (WebKit)

Related

pushState change - equivalent to Chrome Extension onHistoryStateUpdated

I'm porting a Chrome extension to a Firefox extension and due to the nature of the website that it runs on, I need to monitor the pushState.
Chrome Extensions has a handy way to handle this: chrome.webNavigation.onHistoryStateUpdated. The way that I use it in the Chrome extension is as follows:
chrome.webNavigation.onHistoryStateUpdated.addListener(function(details) {
var tabUrl = details.url;
if (isTabUrlValid(tabUrl)) {
$.get(tabUrl, function(data) {
var videoUrl = $(data).find('meta[itemprop=contentURL]').prop('content');
videoUrl = validateUrl(videoUrl);
videoUrl5k = make5kUrl(videoUrl);
});
}
});
I need to do the same thing for the Firefox Extension, but I haven't found any good answers. I've tried doing the answer mentioned here: How to get notified about changes of the history via history.pushState?
(function(history) {
var pushState = history.pushState;
history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
var tabUrl = tabs.activeTab.url;
console.log("UPDATED TAB URL: " + tabUrl);
if (isTabUrlValid(tabUrl)) {
$.get(tabUrl, function(data) {
var videoUrl = $(data).find('meta[itemprop=contentURL]').prop('content');
videoUrl = validateUrl(videoUrl);
videoUrl5k = make5kUrl(videoUrl);
});
}
return pushState.apply(history, arguments);
};
})(window.history);
The problem is that when I do cfx run it complains that history/window is undefined and therefore never gets detected. I think this is due to it being within the SDK, but I don't know of a good workaround.
Any thoughts?
Edit: I looked at #willma's answer below and I don't think that would work for me. The issue is that the URL is updated via pushState and the DOM is not... Is there any good way replicate what I do in the chrome extension?
Edit: Here's the pageMod portion
pageMod.PageMod({
attachTo: 'top', // Don't attach to iFrames --> http://goo.gl/b6b1Iv
include: [URLs],
contentScriptFile: [data.url("jquery-2.1.1.min.js"),
data.url("csScript.js")],
onAttach: function(worker) {
worker.port.on('url', function(url) {
var videoUrl = validateUrl(url);
videoUrl5k = make5kUrl(videoUrl);
console.log("--5K URL--: " + videoUrl5k);
});
}
});
That history code needs to get injected into a tab using a content script. Right now your logic says when the history event occurs, check to see if the tab URL is valid.
In Firefox, the logic will be the other way around: when a tab is opened, check if its URL is valid, and if so, then attach a script to it that will monitor for the history event. To do so you'll need to use a Page Mod.
Edit: All the code
One key concept you're missing is the difference between a content script and a main/library script. The library scripts are stored in lib and have access to all the SDK modules, but don't have access to the DOM, window object… The content scripts are stored in data, are injected into a page using the PageMod or tabs modules, can access the dom and window objects, but have no access to any SDK modules. Content scripts are essentially like the page scripts you'd attach your standard HTML page (with <script></script>) with the caveats that they can't share variables other page scripts but they can communicate with the main scripts.
The only reason I bring this up is because your initial problem was trying to access the window object from a main script and the problem in your fiddle is that you're trying to access the tabs module inside a content script. It's worth reading the topmost link in this answer if this is still confusing.
main.js
const { PageMod } = require('sdk/page-mod');
var sendXHR = function(url) {
// Do something with the new URL
// See Request Module docs (below) for sending XHRs from main script.
}
const pageMod = PageMod({
attachTo: 'top',
include: '*',
onAttach: function(worker) {
worker.port.on('newURL', sendXHR);
}
});
content.js
var sendNewUrlToMain = function() {
self.port.emit('newURL', location.href);
}
var pushState = window.history.pushState;
window.history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
sendNewUrlToMain();
return pushState.apply(history, arguments);
}
window.addEventListener('hashchange', sendNewUrlToMain);
Here are the request module docs, for making XHRs.
NB: if you don't want to use the request module (the only reason being that you already have standard XHR code for your chrome extension and don't want to take the time to learn/rewrite that code), you can send a standard XHR from the content script, but in doing so, you risk allowing the user to close the tab and thus destroy the script before your XHR callbacks are executed.

Ajax Broken in Browsers works in Android

I can run this code in Android app (using PhoneGap adn jQuery Mobile) but not on desktop browsers.
It gives me a syntax error in firebug for this line =
var TicketList = eval("(" + ajax.responseText + ")");
Here is the code
// JScript source code
// ran on body load
function doJsStuff()
{
var ajax = AJAX();
ajax.onreadystatechange = function () {
if (ajax.readyState == 4) {
var TicketList = eval("(" + ajax.responseText + ")");
if (TicketList.ListCount > 0) {
document.getElementById("opencount").innerHTML = TicketList.ListCount +" Open Tickets";
for (Ticket in TicketList.Tickets) {
// add stuff to DOM
//AddTicketToList(TicketList.Tickets[Ticket]);
}
}
else {
document.getElementById("opencount").innerHTML = "All Tickets Reviewed";
DisplayNoresults();
}
}
}
ajax.open("GET", "http://website.com/ListTicketsRequest.ashx?PageNumber=1&PageSize=1&Status=Open", true);
ajax.send(null);
//document.addEventListener("deviceready", onDeviceReady, false);
//event to check for PhoneGap
//$('ul').listview('refresh');
$('#mtickets').page();
//showVars();
}
function AJAX()
{
var xmlHttp;
try
{
xmlHttp = new XMLHttpRequest();
}
catch (e)
{
}
return xmlHttp;
}
**TicketList is a variable in the JSon that comes across like this=
{"Tickets" : [{"TicketID": "1054","Category": "N/A","SubmittedUserID": "bob.thebuilder","ShortDescription": "test question QID:16668","CreationDate": "2/16/2011 12:24:19 PM","TicketStatus": "Open","LongDescription": "Something is wrong with this question I know I hve the right answer but it keeps telling me I'm wrong"},{"TicketID": "1053","Category": "Mission Support","SubmittedUserID": "dave","ShortDescription": "Make courseware revisions","CreationDate": "2/16/2011 9:34:48 AM","TicketStatus": "Open","LongDescription": "Find help tickets generated by users for possible courseware update."}], "PageCount": "6", "ListCount": "11"}
Note about PhoneGap If you are trying to include phoengap functions in a place where the code may also be executed on in a browser make sure you only add the phone gap function with on "deviceready" or your browser will not render. Example:
function onload(){
//event to check for PhoneGap
document.addEventListener("deviceready", onDeviceReady, true);
}
...
function onDeviceReady()
{
// Now PhoneGap API ready
vibrate(90); // vib to ack pg ready
$("a").click(function(event){
vibrate(30); // add 30 sec vib to all links
});
}
My immediate response would be to use jQuery's getJSON method, since you're aready using jQuery. jQuery's AJAX provides a much broader base of browser compatibility. Also, every time you use eval(), a small baby somewhere cries.
var url = "http://website.com/ListTicketsRequest.ashx?PageNumber=1&PageSize=1&Status=Open";
$.getJSON(url ,function(TicketList){
if (TicketList.ListCount > 0) {
$("#opencount").html(TicketList.ListCount +" Open Tickets");
for (Ticket in TicketList.Tickets) {
...
}
} else {
$("#opencount").html("All Tickets Reviewed");
DisplayNoresults();
}
});
If this still doesn't work for you, ensure that the JSON being returned is valid. But please stick to this method, and don't use eval!!
SIMPLIFIED UPDATE
var url = "http://website.com/ListTicketsRequest.ashx?PageNumber=1&PageSize=1&Status=Open";
$.getJSON(url ,function(AnyNameYouWant){
alert(AnyNameYouWant.ListCount + " Open Tickets");
});
UPDATE USING 'DATA'
If your url becomes too long, you might begin to encounter problems. It is suggested to pass the url data via the data argument.
var url = "http://website.com/ListTicketsRequest.ashx";
var data = "PageNumber=1&PageSize=1&Status=Open";
$.getJSON(url, data, function(AnyNameYouWant){
alert(AnyNameYouWant.ListCount + " Open Tickets");
});
Looking at your code, it seems likely to me that the syntax error isn't in the code you posted, but instead is contained in the JSON object you're evaluating in ajax.responseText. Take a look at the data being returned by the AJAX request. Is it valid Javascript? Does the page you're calling return something different to desktop browsers vs mobile? Is there an error message where the JSON code should be?
Another possibility: Is your app running on website.com? If not, Firefox is probably blocking the XMLHttpRequest from functioning properly. Firefox 3 and below block cross-site AJAX requests. Firefox 3.5 seems to allow some exceptions.

Receiving AJAX HTTP Response Code as 0

I have a pretty simple AJAX and PHP code. While calling the PHP through the AJAX it receives the response code as 0. The PHP code is successfully run, but I can't get the response. What does this status '0' denote and how can I solve this?
function confirmUser(id)
{
xmlhttp=GetXmlHttpObject();
regid = id;
if (xmlhttp==null) {
alert ("Browser does not support HTTP Request");
return;
}
var url="confirm.php";
url=url+"?id="+id;
url=url+"&a=confirm";
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
$("#txtHint" + regid).text("Awaiting confirmation");
} else {
alert (xmlhttp.status); //this shows '0'
}
};
xmlhttp.open("GET",url,true);
xmlhttp.send(null);
}
Well, this is the javascript I used. Pardon me if I should've added anything more than this. Also tell me what I missed.
I appreciate your help
GetXmlHttpObject function:
function GetXmlHttpObject()
{
if (window.XMLHttpRequest) {
// code for IE7+, Firefox, Chrome, Opera, Safari
return new XMLHttpRequest();
}
if (window.ActiveXObject) {
// code for IE6, IE5
return new ActiveXObject("Microsoft.XMLHTTP");
}
return null;
}
When working with XMLHttpRequests in the past, I've found that status 0 is usually returned for locally processed files. When I saw this question, I had a bit of a hunt around and found a confirmation of this at the following pages:
XMLHttpRequest - Why Status 0, and StatusText Unknown occur
https://developer.mozilla.org/En/Using_XMLHttpRequest#section_3
Here are the readyState codes for you.
0. Uninitialized
1. Set up, but not sent
2. Sent
3. In flight
4. Complete
(Source: http://www.stevefenton.co.uk/Content/Blog/Date/201004/Blog/AJAX-Ready-State-Codes/)
Do you get stuck constantly on a readyState of 0? If so, it means your request hasn't been sent, although I can see a line of code in your example "xmlhttp.send(null)"...
I would predict that you'll get a 0 before you call send, but after that a different status code. What happens if you wait a bit?
I know people may not want to hear it, but this is exactly what JS frameworks are for. Why mess with all of the various browser inclinations and disasters that are custom AJAX calls when you can just do a simple AJAX call through jQuery.
Basically, you are reinventing the wheel, and for no reason. Have your php return JSON data, and embed a variable in with the success code if you need to test for that.
<script src="jquery.js"></script>
<script>
$.get("myphp.php", { id : "yes", blah : "stuff" }, function(data) {
if (data.success == 1) {
alert("got data");
} else {
alert("didn't get data");
}
},"json");
</script>
Boom, you now have cross-browser AJAX.
This can happen if you're requesting an HTTPS resource and the handshake fails (for example an invalid certificate). In particular, if you're using the XML request object from outside a browser, the error may not be obvious.

How to execute a page ,that contains JS ,in AJAX ,using innerHTML?

I send GET data with AJAX to another file.And on the another file I have echo "<script>alert('Something');</script>";.This is displayed dynamicly with AJAX ,i.e
var ajaxDisplay = document.getElementById('edit');
ajaxDisplay.innerHTML = ajaxRequest.responseText;
puts the <script>alert('Something');</script> to div with name edit.
But it doesn't alert anything.
How to get it work?
I have mixed html/javascript.
Here is the code.
function ajaxFunctions(){
var ajaxRequest; // The variable that makes Ajax possible!
try{
// Opera 8.0+, Firefox, Safari
ajaxRequest = new XMLHttpRequest();
} catch (e){
// Internet Explorer Browsers
try{
ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try{
ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e){
// Something went wrong
alert("Your browser broke!");
return false;
}
}
}
// Create a function that will receive data sent from the server
ajaxRequest.onreadystatechange = function(){
if(ajaxRequest.readyState == 4){
var ajaxDisplay = document.getElementById('edit');
ajaxDisplay.innerHTML = ajaxRequest.responseText;
}
}
var namef = document.getElementById('nameed').value;
var queryString = "?namef=" + namef;
ajaxRequest.open("GET", "try.php" + queryString, true);
ajaxRequest.send(null);
}
Maybe to find the script tags and to eval them?
But how to find the script tags?
Instead of trying to inject a script element in the DOM, just have your script return:
alert('Something');
And then use eval(response); to run it. Or you could add a script element with the src attribute pointing to the page that returns your JavaScript into the <head> (which is the preferred method).
function loadScript(url) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
head.appendChild(script);
}
Keep in mind that this wont work for cross-domain requests--the script has to have the same origin as the page running your code. To get around this, you'll have to use a callback.
It looks like the only purpose of setting innerHTML is an attempt to get the JS to execute. But once the page is loaded, JS won't 'know' that it needs to parse and execute the new text you've changed, so your method won't work. In this case, what you want is a callback function:
http://api.jquery.com/jQuery.ajax/
I haven't used jQuery, but it looks like you'd simply add a 'complete' property to the settings object you pass to the .ajax() call, like so:
$.ajax({
// ......
complete: function(){
alert('Something');
}
// ......
});
In this case, the callback function would execute once the ajax call has completed. You can pick other events, such as on success, on failure, and so on, if you need to attach your code to a different event.
But how to find the script tags?
Well, parent.getElementsByTagName('script') and then evaling the data of the text node inside will do it.
However, inserting content that includes script tags is unreliable and works slightly differently across browsers. eg. IE will execute the script the first time the script node is inserted into any parent, inside the document or not, whilst Firefox will execute script the first time a subtree including the script is added to a node inside the document. So if you're not extremely careful, you can end up executing scripts twice on some browsers, or executing the script at the wrong time, following a further page manipulation.
So don't. Return script that you want to execute separately to any HTML content, eg. using a JSON object containing both the HTML and the JavaScript seperately.

ajax issue on IE8

I have an old site that uses xml documents, and when I created it i had firefox and IE7 to test on, and it worked just great. Since then, IE8 appeared, and it seams that the site does no longer work properly.
This is the current code:
if (window.XMLHttpRequest)
{
XMLHttpRequestObject = new XMLHttpRequest();
XMLHttpRequestObject.overrideMimeType("text/xml");
XMLHttpRequestObject.open("GET", "produse.xml", true);
XMLHttpRequestObject.onreadystatechange = function()
{
if (XMLHttpRequestObject.readyState == 4)
{
xmlDocument = XMLHttpRequestObject.responseXML;
removeWhitespace(xmlDocument);
}
}
XMLHttpRequestObject.send(null);
}
else if (window.ActiveXObject)
{
xmlDocument= new ActiveXObject("Microsoft.XMLDOM");
xmlDocument.async=false;
if (xmlDocument .readyState == 4)
{
xmlDocument.load("produse.xml");
}
}
But I get this error:
Message: Object doesn't support this property or method
Line: 19
Char: 3
Code: 0
which relates to:
XMLHttpRequestObject.overrideMimeType("text/xml");
What should I use instead?
This page shows what the differences are between firefox/ie/ie8 are and has some examples of how to do it:
http://www.javascriptkit.com/jsref/ajax.shtml
Mostly it says that IE8 does not support this method and you have to make sure your server is adding the proper header (text/xml) to the outgoing response.
It also has a link to http://www.javascriptkit.com/dhtmltutors/ajaxgetpost3.shtml that explains the common pitfalls and IE problems.
It says do:
if (mygetrequest.overrideMimeType)
mygetrequest.overrideMimeType('text/xml')
jQuery would be my choice. It is a lot faster and is cross browser supported and on a CDN.
You could strip that code down to just a few lines.
$.get('ajax/test.html', function(data) {
$('.result').html(data);
alert('Load was performed.');
});

Resources