Rails, TinyMCE and JavaScript in IFrame
Browsers developed some really bizarre means of user data protection over the year of evolution. One of many is the JavaScript cross-domain protection. If you load a web-page into an IFRAME and access its DOM model or the model of the parent document from the loaded page, you get what you deserve – a permissions violation error that looks like ”Permissions Exception” (in IE) and ”uncaught exception: Permission denied to get property …” or similar in (FireFox). All is simple and clear once you know what to expect. It’s all logical. Today however I faced something different, really different and odd.
In the application I work on we need to upload images from the post editing page (right, much like a regular blog). As all sane people, we have an editor (Tiny MCE), an IFRAME to post files being uploaded through and some code to glue it all together. Previously we were using a different WYSIWYG editor, but recently replaced that with Tiny MCE for its Safari 3 support.
When the first “permissions” error showed up upon receiving the on-upload-completed GUI update code, the immediate reaction was “hey, there must be a port number somewhere, since I know we talk to the same domain, or otherwise it wouldn’t breathe at all”. A quick check showed that no, there was no explicit port number either in the main document URL or in the URL of the IFRAME part. Ouch.
Next few hours I spent reading the code of the editor, experimenting, scanning forums, playing with JS console in FireBug and no matter what I did the result remained the same. Finally, I decided to make a copy of the sources and remove HTML / JavaScript of the page piece by piece until a bare minimum of stuff stays, then analyze it under the looking glass. The bare minimum appeared to be two Rails javascript_include_tag lines (for Prototype and Tiny MCE) and one text field with the submit button. Not much, but fairly sufficient.
What I need to mention here is that we use an asset distribution plug-in for Rails (distributed_assets by Ezra Zygmuntovicz) to load static files from 4 different domains (which in fact are the same server with 4 different names). Every time you call image_tag, javascript_include_tag and any other helper returning the static asset tag code, the plug-in intercepts the call and uses the domain name of one of these servers to spread the load between them.
When there was nothing but few <script>
tags and 2 lines of HTML, the answer became excitingly obvious. Here’s the short rule of thumb derived from this lesson:
When you use Tiny MCE and planning to access parent DOM from within an IFRAME somewhere on the same page, make sure that:
- The code you load into IFRAME comes from the domain with the name that matches the domain name of the main document to the last letter
- JavaScript of the Tiny MCE editor is loaded from exactly the same domain
- Port numbers matter
The end of the story.