Detect whether postMessage can send objects? - javascript

I'm looking for a neat way to detect whether postMessage in the browser supports the sending and receiving of objects or just strings. I figure that someone out there must have wrote something that does this but I have not managed to find a solution.
I'm using postMessage to send data to/from a WebWorker. Whilst detecting whether the browser supports workers is straight-forward, detecting whether objects can be send via postMessage has proved more difficult.
I'd like to write a simple detection function. So, if the browser supports the sending of objects to use that. If only strings are allowed I can fallback to using JSON.stringify(). I'll probably assign the function to a dojo/has test (although this is not relevant to the question/answer).
What have other people done to solve this problem? Any advice would be great, I'm new to both WebWorkers and postMessage. Thanks in advance.

I found an even easier way to detect if postMessage only supports strings or if it supports other types. Simply add a custom toString-method on the object. When trying to send an object with postMessage in IE8 and IE9 they will be converted to a string with the toString-method on the object. Since browsers that support sending objects doesn't call toString we can use this to our advantage. This test is not async, so you'll get the result instantly. Haven't tested this with web-workers, but I suppose you can use the same technique.
var onlyStrings = false;
try{window.postMessage({toString:function(){onlyStrings=true;}},"*");}catch(e){}
console.log("Browser only supports postMessage with strings? " + onlyStrings);
Tested in IE8, IE9, IE10 and latest version of Chrome, Firefox, Safari and Opera:
http://jsbin.com/igowoFaj/1/edit?js,console
Update: Did a BrowserScope test with many more tests and browsers. Conclusion is that it's safe to send clonable objects, arrays, numbers, pixel data and array buffers if onlyStrings is false. In theory all browsers that allow sending objects should use the structured clone algorithm, but the Android browser and Opera Mobile has quirks. The BrowserScope test result is a bit hard to read, because a 0 for send_xxx is only problematic if the browser actually has support for that type, so check supports_xxx too. If they are equal it's ok, but it's a bug if the browser has support but can't send (when onlyStrings is false).

You could try to perform an action BEFORE resuming your script. You could try this:
dummy_task.js
self.onmessage = function(event) {
self.postMessage(event.data);
};
javascript
workerSupportObject(callback);
function workerSupportObject(callback) {
var callbackIsCalled = false; // to make sure callback isn't run twice
var worker = new Worker('dummy_task.js'); // create a worker
// create event
worker.onmessage = function(event) {
// if the value is the same as we sent, it probably works
if(!callbackIsCalled) callback.call(null, event.data.value === 'dummy');
callbackIsCalled = true;
};
try {
// send dummy JSON data
worker.postMessage({'value': 'dummy'});
} catch(e) {
// oh... an error... clearly that's a no.
if(!callbackIsCalled) callback(null, false);
callbackIsCalled = true;
}
}
function callback(objectSupported) {
console.log('Worker supports objects: ', objectSupported);
}

I wanted to know the same thing. I created this script to detect if an object could be passed in postMessage by a simple callback to the current window. You will see IE 9 return false, IE 10 returns true.
http://jsfiddle.net/milesplit/DvqqH/
var supportsPostObject = false;
(function(){
var callback = function(e) {
supportsPostObject = (typeof(e.data)!='string');
};
(window.addEventListener) ?
window.addEventListener('message', callback) :
window.attachEvent('onmessage', callback);
('postMessage' in window) && window.postMessage({}, '*');
})();
setTimeout(function(){
alert(supportsPostObject);
}, 0);

postMessage also works between iframes; assuming that the behavior is the same between workers and frames, you should try the following or something like it:
<html>
<body>
<iframe id='if'>
</iframe>
<script>
var iframe = document.getElementById('if');
var iframeScript = iframe.contentDocument.createElement("script");
iframeScript.appendChild(
iframe.contentDocument.createTextNode(
'window.addEventListener("message", function(e) {console.log(e.data);}); console.log("listener attached");'));
iframe.contentDocument.body.appendChild(iframeScript);
iframe.contentWindow.postMessage("asdf", "*");
iframe.contentWindow.postMessage({'whatAmI': 'an object, maybe?'}, "*");
</script>
</body>
</html>
You may need to replace console or console.log to be able to see results, but on Chrome, this gets me
listener attached about:blank (1):1
asdf about:blank (1):1
Object {whatAmI: "an object, maybe?"} about:blank (1):1
when I save it to a local file and open it up.
The jsfiddle version (and the version which uses an actual worker) are left as an exercise for the reader. :)

Related

Adding Error Function to XPages Partial Refresh (dojo.xhrPost) Events?

I have a page which depends a number of partial refreshes for interaction. I'm not looking to use a keep session alive mechanism, but I would like to catch error responses to my partial refreshes, alert the user that their session expired / they need to refresh, perform a window.location.reload once they've acknowledged the alert. Specifically, to be event-driven by the user's attempt at a partial refresh (and, by nature, purely through CSJS).
I know from using Chrome DevTools that the partial refresh shows as a Post request, which returns the partial (refresh area) of the html plus any requisite CSJS. I also know that, as documented under Dojo 1.6 in regards to dojo.xhr (Post and Get) supports an error handling parameter, by which I can pass the function I'll want to create. I'm currently uncertain of how to add this function to my dojo.xhrPost requests.
Having seen this XSnippet, "Cache Prevention for Dojo xhr requests", which allows for adding the preventCache option to dojo.xhr requests, I feel this is the right approach. Again, it's the implementation I'm uncertain of. I was disappointed to find that the link it referenced as the source, http://www.juliusbuss.de/web/youatnotes/blog-jb.nsf/dx/a-big-issue-for-mobile-web-apps-with-xpages-for-iphone-and-ipad-and-how-to-solve-it..htm, was not working for me.
I'm uncertain of whether I can use the same argument, arguments[1], as in the referenced XSnippet (or which is the best arguments position).
Also, I'm not 100% sure of whether I need to further specify this for dojo xhrPost events, or if just using the dojo.xhr is good enough.
Would love a more experienced perspective.
Here is an example how to override the error function for every partialRefresh:
// hijack the dojo function if required
if( !dojo._xhr )
dojo._xhr = dojo.xhr;
var _error;
dojo.xhr = function(){
try{
var args = arguments[1];
_error = args["error"]; // save the original error function
// add your own method
args["error"] = function(arg1, arg2){
alert( "Hello World!" );
_error(arg1, arg2);
}
arguments[1] = args;
}catch(e){}
dojo._xhr( arguments[0], arguments[1], arguments[2] );
}

GWT - "Access is Denied" JavaScript error when setting document.domain in Internet Explorer only

Background Information
I am working on a web application that utilizes GWT (v2.4). For the application, I am creating an iframe that will display some information from another website. I need to access some information from that iframe that is normally restricted via the Same Origin Policy (SOP). However, both sites (the parent and iframe) are hosted on the same super-domain, just under different sub-domains. So, something like this:
Parent: dev.app.mySite.com
frame: someOtherContent.mySite.com
I know the usual solution for this problem is to set the property: document.domain = 'mySite.com' on both parent and iframe site to allow passage of SOP. This works for all browsers (that I'm concerned with) except Internet Explorer 8 (and probably other versions).
The Problem
In IE, when I attempt to load my web application, I get a completely blank page with the following JS exception, "Access is denied." The source of this problem is in GWT's myGwtAppName.nochache.js where GWT generates some code during the compilation process that it needs (see below).
From the research I've done on this problem in general, the root cause of this issue seems to be that in IE, unlike all other browsers, iframes don't inherit their parent's document.domain settings. From what I understand, the code generated by GWT runs in an iframe (based on this comment: https://stackoverflow.com/a/5161888). So, what I think is happening based on my limited knowledge of JS:
I set document.domain = 'mySite.com' in the parent index page via JS and it is processed.
myGwtAppName.nochache.js is processed.
In nochache.js, code is ran to setup the GWT iframe sand-box environment
In that code, a call is being made to a SOP restricted property of the sand-box iframe
An exception is thrown because the site's parent document domain has been set to 'mySite.com' and the iframe's document.domain doesn't inherit that setting, so it's still 'dev.app.mySite.com'. This won't pass SOP because the domain has to be exactly the same.
The generated code that causes the exception
The below code, looks like it's setting up the GWT sandbox iframe environment.
var $intern_4 = 'myGwtAppName',
$intern_7 = 'body',
$intern_8 = 'iframe',
$intern_9 = 'javascript:""',
$intern_10 = 'position:absolute; width:0; height:0; border:none; left: -1000px; top: -1000px; !important',
$intern_11 = '<html><head><\/head><body><\/body><\/html>',
$intern_12 = 'script',
$intern_13 = 'javascript',
$intern_14 = 'var $wnd = window.parent;''
....
....
function setupInstallLocation(){
if (frameDoc) {
return;
}
var scriptFrame = $doc.createElement($intern_8);
scriptFrame.src = $intern_9;
scriptFrame.id = $intern_4;
scriptFrame.style.cssText = $intern_10;
scriptFrame.tabIndex = -1;
$doc.body.appendChild(scriptFrame);
frameDoc = scriptFrame.contentDocument;
if (!frameDoc) {
frameDoc = scriptFrame.contentWindow.document; //THIS CAUSES THE EXCEPTION
}
frameDoc.open();
frameDoc.write($intern_11);
frameDoc.close();
var frameDocbody = frameDoc.getElementsByTagName($intern_7)[0];
var script = frameDoc.createElement($intern_12);
script.language = $intern_13;
var temp = $intern_14;
script.text = temp;
frameDocbody.appendChild(script);
}
....
....
My Questions
Is my analysis of the situation completely off-base?
Has anyone seen a solution for this problem that will work in a GWT environment in IE?
Information Sources
IE doesn't inherit document.domain settings: https://stackoverflow.com/a/1888711 (and many other threads).
GWT runs in an iframe sand-box environment: https://stackoverflow.com/a/5161888
You may use html5 web messaging to communicate between iframe and parent.
Be aware that Internet Explorer has following bugs. You can send only string as messages. You can't send object like other browser support.
Some people advice to encode object into JSON if you wish to send more then just a string but sometimes it is cheaper to send URL encoded string just like query string in URL.
Here are examples 2 top results from my google
http://tutorials.jenkov.com/html5/messaging.html
https://thenewcircle.com/bookshelf/html5_tutorial/messaging.html
Take a look that they use different code to listen for messages
window.attachEvent("onmessage", handleMessage);
window.addEventListener("message", handleMessage, true);
First works with IE and old Opera and last works with a rest of world.
I've run into the same issue and have found no elegant solution, but...
Right now I have an ant task that manually alters 3 specific points in the GWT nocache.js file after compilation to workaround the issue. You have to use regular expressions to make sure the injected code can reference a couple specific variables in the obfuscated code. It's all terribly ugly...
If you've found a more elegant solution please do post, since my solution is a hack. Details below...
Note - This assumes you have compiled GWT in "PRETTY" mode, since that's the only way I could reference variable/method names.
The problem we really need to solve here is that IE does not inherit altered document.domain values. So IFrames will have invalid document.domains. You can, however, force an IFrame to set its own document.domain, so that it is in sync with the outer page. However - this method requires letting the IFrame load and execute first. Which means that further operations must be executed in a callback after the iframe has loaded.
1) You need to add the follow two methods to the gwt .js file:
function waitForSetupInstallLocation(callback){
if (frameDoc) {
return;
}
var scriptFrame = $doc_0.createElement('iframe');
scriptFrame.src="javascript:document.open();document.domain='<domainvalue>';document.close();document.body.contentEditable=true;";
scriptFrame.id = '<MyWidgetSetName>';
scriptFrame.style.cssText = 'position:absolute; width:0; height:0; border:none; left: -1000px;' + ' top: -1000px;';
scriptFrame.tabIndex = -1;
$doc_0.body.appendChild(scriptFrame);
var addedContent = false;
try {
setFrameDoc(scriptFrame);
callback();
}
catch(e){
scriptFrame.onload = function(){
if(!addedContent){
addedContent = true;
setFrameDoc(scriptFrame);
callback();
}
};
}
}
function setFrameDoc(scriptFrame){
frameDoc = scriptFrame.contentDocument;
if (!frameDoc) {
frameDoc = scriptFrame.contentWindow.document;
}
frameDoc.open();
var doctype = document.compatMode == 'CSS1Compat'?'<!doctype html>':'';
frameDoc.write(doctype + '<html><head><\/head><body><\/body><\/html>');
frameDoc.close();
}
These two methods allow GWT to inject code into the page while also waiting for IE to load an IFRAME which then changes its own document.domain. You can see that the first accepts a callback. The callback is executed only after the IFrame is loaded.
The next issue is that these are asynchronous methods, and only accept callbacks. GWT currently does all setup synchronously. So the next modification is that the two methods that need to use it must be altered. All of the inner content of the following methods:
function installCode(code_0){...}
<WidgetSetName>.__installRunAsyncCode = function(code_0){...}
Needs to be wrapped in a function, and passed to the waitForSetupInstallLocation method as a callback. So that essentially you have turned those methods into asynchronous methods.
An example of what this looks like is:
function installCode(code_0){
waitForSetupInstallLocation(function(){
<real method content>
});
}
Once you have done all this - it should work in IE, and should remain functional in other browsers, since youve only added the use of a callback.

Why is $.browser deprecated - and what is a better alternative?

So I know $.browser has been deprecated and "frowned upon", since jQuery 1.3, but it continues to exist & work in the code.
It's still using the plain javascript: navigator.userAgent to determine the browser being used, as well as the version.
Now is there something about these I don't know about navigator itself, that I shouldn't be using either $.browser or plain vanilla JS to get the browser/version? I just want to make sure when they have IE8 (for example), they really do have it, and I'm not processing the wrong code.
What other alternatives do we have for browser sniffing? I know about $.support, I use modernizr, but sometimes I need just need the down and dirty browser version, instead of seeing what the browser is capable of handling (I think that is a completely different problem solver).
You kind of answer the question yourself. The ideal is to check for feature support. As more browsers and devices come onto the market this approach should scale.
However if you want to do something 'down and dirty' then browser detection of course works, but only so far as you will know your code works in the existing set of browsers (or even just those you've tested your code with).
Generally it's recommended not to try to guess what the browser is but to check if a function is available. There are too many browsers and variants.
To check if a function is available, you simply do this :
if (!Array.prototype.map) {
// not available, shut down computer !
If a "must" to know which browser on the page for me, I use this personally;
(function() {
var re_browsers = {
firefox: /firefox\/([\d\.]+)/,
chrome: /chrome\/([\d\.]+)/,
safari: /webkit.*?version\/([\d\.]+)/,
opera: /opera.*?version\/([\d\.]+)/,
ie: /msie\s+([\d\.]+)/
// ...
};
var ua = window.navigator.userAgent.toLowerCase(), k, re, browser = {};
for (k in re_browsers) {
if (re = re_browsers[k].exec(ua)) {
break;
}
}
browser[k] = true;
browser["version"] = parseFloat(re && re[1]);
browser["versionOrig"] = re[1];
jQuery.extend({browser: browser});
})();

Why is javascript saying that addcallback function is not defined?

my first time on here.
My problem is with AS3, Javascript and possibly the browsers Firefox and IE.
I have done so much searching for an answer so i will print my code:
i am using this line to call the flash application and in all browsers its combatible and actually traces in firebug to hold an OBJECT->FLASH_ID so thats not the problem.
var obj = document.getElementById('test');
then i use addcallback:
obj.sendStatus(loggedIn);
now whats weird is that i trace all individual elments in chrome and
-obj = flash object
-sendStatus = flash->function
-loggedIn = either false or true;
everything works great but when i am on firefox or ie
it traces differently
-obj = flash object
-sendStatus = undefined
-loggedIn = either true or false;
now what am i missing??????????
i tried embedding rather than object insertion
i made sure that the id's were all unique
i checked to make sure i had the right flash object selected with getElementById
im so confused.. and it feels like something simple.
I know about some browser - dependent timing problems, making the interface of the flash object available...
A timer could help, try this:
var obj = document.getElementById('test');
setTimeout(function(){obj.sendStatus(loggedIn);}, 500);
500 is a bit to long, but just to be sure. If it works you can try to lower it to 200 - 300.
make sure you declared allowScriptAccess = sameDomain both in embed tag and object tag
in case you don't use swfObject
Maybe the way you get a reference to the swf is wrong, try this
function thisMovie(movieName) {
if (navigator.appName.indexOf("Microsoft") != -1) {
return window[movieName];
} else {
return document[movieName];
}
}
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/external/ExternalInterface.html
The problem is that using ExternalInterface requires both parties (browser and flash) to be ready.
You can have the flash poll a method in the page which just returns true so that you know its ready to receive calls from flash.
On the flip side if the page is cached, it can sometimes happen that the page wants to send to flash before flash is ready, so I use a callback to the page telling it flash is ready, so its like a handshake, once both parties are ready, then we can start sending data back and forth.
This has been my approach since Firefox 3.

Javascript FileReader detection in Safari

I'm aware of the fact that the FileReader Object is not available in Safari 5.0.5. I have a script that uses it and thought that i'd just be able to detect whether the object exists to run some alternate code, as is suggested here,
http://www.quirksmode.org/js/support.html
So my code is,
if( FileReader )
{
//do this
}else{
//the browser doesn't support the FileReader Object, so do this
}
The problem is, i've tested it in Safari and once it hits the if statement i get this error and the script stops running.
ReferenceError: Can't find variable: FileReader
So obviously that's not the best way to deal with it then? Any idea why this doesn't work?
I believe in your case you can get away with a simpler check:
if(window.FileReader) {
//do this
} else {
//the browser doesn't support the FileReader Object, so do this
}
check for the type if you really wanna be granular and picky.
You can write if (typeof FileReader !== "undefined")
You can also use the Modernizr library to check for you.
Or you can do something like this.
if('FileReader' in window) {
// FileReader support is available
} else {
// No support available
}

Categories