Cross-domain javascript widget, no JSON, no AJAX
February 28th, 2010 | by Sean |I may be doing something terribly wrong. I hope the Internet will tell me. I wanted to make a widget so that I could showcase some AJAX-accessible shipping quotes I’d put on spider.my, but demonstrate accessibility from another site. I’d never written much javascript before this week, so I was very pleased that my first attempt worked so quickly – and then I discovered the Same Origin Policy (SOP). My first attempt was just some javascript that dynamically created the HTML (just thought – shouldn’t the widget markup match the document’s doctype?) elements of the widget, and then responded to user input by sending a request for a shipping quotation to the XML responder I wrote a few days ago.
That first attempt worked great – while it was included in a page from the same server that provided the shipping quote. The first time I tried to include it as a widget on this blog, it failed. According to the Inspect Element facility in Chromium, the problem is:
Uncaught Error: NETWORK_ERR: XMLHttpRequest Exception 101
when the send() function was invoked on the XMLHttpRequest object. At the same point Firefox says
Error: uncaught exception: [Exception… “Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIXMLHttpRequest.send]” nsresult: “0x80004005 (NS_ERROR_FAILURE)” location: “JS frame :: http://localhost:22791/static/js/posmalaysia5.js :: pos_spider_box_quote :: line 26” data: no]
And in IE8 the debugger tells me (on the open() method of XmlHttpRequest)
JScript debugger
Breaking on JScript runtime error – Access is denied
The request is received by my server from Firefox and Chromium, but not from IE8 (obviously, I suppose, if it’s failing on the open() method).
The Internet told me I was attempting Cross-domain scripting and it’s impossible unless you use version 4.8 of Firefox, pretend you’ve fixed it by using a proxy server, or defeat impossibility by using jQuery, flxHR or some other library whose developers presumably don’t understand the word ‘impossible’.
I decided that either this bloody thing is impossible or it isn’t, and that if it isn’t, why should I need a library to do what I wanted to do? For my specific application (or any I can currently imagine I might want to implement) I just want to exchange a very few items of data. If there is a way of doing it, how complex does it have to be?
Anyway, I didn’t have clue a couple of days ago, so I started with what seemed to me to be an unusually straightforward article on cross-domain scripting at IBM:
A quick read of the document led me to think that JSON wasn’t all that bad. If it solved my problem, I could probably bring myself to use it. The article ‘goes off on one’ about jQuery in the later stages, but I decided I would cross that domain when I came to it. I was of the opinion that JSON alone would give me what I want.
At the crucial part where the article says you have to dynamically create a script element in the document’s HEAD element, I thought I’d try a little experiment and just send some bare javascript assignments instead. It just worked! Before I work through an example, here is a rough run-down of what I had at the time it first worked.
The code I invite people to use to try out the widget is a DIV element with a SCRIPT element inside it whose SRC attribute is the URL of a text/javascript resource at spider.my. Here’s the widget-including fragment one more time:
<div id="pos.spider.box"><script type="text/javascript" src="http://spider.my/static/js/posmalaysia4.js"></script></div>
The script creates the widget content. It also sets a var – which I’m guessing has some sort of global scope as it’s outside a function block – to the DIV which will hold the ‘result’ of a shipping quotation.
A text INPUT box and a SELECT element in the widget both have onchange() methods which call a function that’s part of the widget download. In the function called by onchange(), I assemble an URL from the values in the inputbox and the select element, to create a SCRIPT element in the document’s HEAD with the constructed URL as its SRC attribute.
The constructed URL is identical to the one I use to request an XML response for the one-shot shipping quotation AJAX script, but ends in “.js” instead of “.xml”. I modified my XML-producing server-side code to output javascript as a text/javascript response if the URL ends with “.js”, and it sends back a single javascript assignment (not a function) to set the contents of the ‘result area’ of the widget, using the reference prepared earlier by the widget code.
Here’s an example of the URL I use as the SRC attribute of the SCRIPT element the widget code creates in the document’s HEAD:
http://spider.my/pos-malaysia-shipping-quote-2/0.5-to-Australia.js
That’s for a 0.5kg shipment to Australia. Here’s the text/javascript response from spider.my:
pos_spider_box_quotes.innerHTML = ‘RM60.00 <i>Pos Laju Document</i><br>RM75.00 <i>Pos Laju Parcel</i>’;
See? Just a bare assignment to the var I prepared earlier. And lo and behold, the results are set in the widget! What is ‘impossible’ about that?
Now, this code started working bare hours ago, so I’m prepared to believe that there’s something dangerous, criminal or unholy about it. But presumably a bit of judicious editing will fix that, won’t it? The first thing that strikes me about this technique is that I’m dynamically adding things to the document and never removing them, so there’s a chance of morbid page obesity. Then again, it’s inline javascript that has no means of being executed more than once, so perhaps the clever javascript engine developers automatically dispose of it once it executes. Who knows? Not me, certainly! All I know is that my cross-domain javascript widget works. Despite it being impossible.
Update: after a night’s sleep, I’m wondering if this is a cross-domain example or not. It seems to me that cross-domain should only refer to a script attempting to access a domain other than the one it was loaded from. In that case the XmlHttpRequest method should work. If ‘cross-domain’ means ‘not the page the document was loaded from’, then I can see why XmlHttprequest shouldn’t work and why the dynamic inline javascript would work (both original and dynamically created being from the same domain). Just in case this is not the fantasy of a tired mind, I’m going to christen this Asynchronous Javascript InLining (AJIL – like ‘agile’). Two levels of asynchronism are an obvious problem, but maybe someone will find it useful.
1 Trackback(s)