Making JSLint AIR: Part 2 - Porting to AIR Beta 2

2007 October 26
tags: AIR · Ext JS · Javascript
by Paul Marcotte
Now that JSLint AIR Edition is available as an AIR Beta 2 application, I want to share my experiences working with AIR for HTML/Javascript and the process of migrating the app from Beta 1 to Beta 2. In terms of the functionality provided by AIR, my Beta 1 version allowed you to open and save a file, exit the application and open a default browser window to the original JSLint documentation page. I had originally hoped to include drag and drop of .js files and the ability to open Javascript files from a file explorer context menu, but the rush was on to submit JSLint AIR to the developer derby, so I was forced to trim features. The latter feature is available in the Beta 2 version and the former is still on the roadmap. A New Security Model With the release of Adobe AIR Beta 2, there is a new security model for HTML/Javascsript applications. This model was introduced to mitigate potential abuse of the AIR API by malicious persons by disabling eval() among other changes. Rather than rehash some specifics, I suggest reading the HTML Security FAQ. The end result of the of the new security model is that it is now incumbent upon HTML/Javascript developers to expose only the specific AIR functionality required in their application by creating a bridge between the application sandbox (where AIR API resides) and the child sandbox (where the UI resides). Here is a brief description of how the security model works and how methods are exposed between them. In the Beta1 version of JSLint, I had a single HTML file with script tags importing the Ext JS library, my application mediator and components, the JSLint engine, and AIRAliases.js. In the Beta 2 version, I now have two html files, one named root.html and another named ui.html. The names of the files are irrelevant, what matters is that AIRAliases.js is now referenced in root.html and ui.html is embedded as an iframe. <html>
<head>
<title>JSLint AIR Edition</title>
<script type="text/javascript" src="AIRAliases.js"></script>
</head>

<body style="margin: 0px; padding: 0px;">
<iframe id="UI"
src="ui.html"
sandboxRoot="http://www.fancybread.com/"
documentRoot="app-resource:/"
frameborder="0"
width="100%"
height="100%"
style="margin: 0px; padding: 0px;">

</iframe>
</body>
</html>
This setup allows you to use libraries like Ext JS or jQuery that make use of eval() to run within the child sandbox (ui.html), while keeping the AIR API within the parent sandbox (root.html). Exposing functionality between the two is a matter of creating object literals within each sandbox called 'parentSandboxBridge' and 'childSandboxBridge'. Here's a snippet from my root.html script. There are different ways to implement the parent and child sandbox bridges, I prefer to set the parentSandboxBridge for the UI iframe within window.onload() event in root.html (example below).
window.onload = function ()
{
     var UI = document.getElementById('UI').contentWindow;
      
     UI.parentSandboxBridge = {
         // provide method to exit via shell
         exitApplication : function ()
         {
             air.Shell.shell.exit();
         },
         // open the jslint docs window
         openDocsWindow : function ()
         {
             air.navigateToURL(new air.URLRequest("http://www.jslint.com/lint.html"));
         }
     };
          
     // initialize the app within the child sandbox
     UI.childSandboxBridge.initApp();
}
And in ui.html, I have the following window.onload() event.
window.onload = function() {
  // expose the init function for the application sandbox
  childSandboxBridge = {
      setSource : function (str) {   
        JSLINT_AIR.app.setSource(str);
      },
      setFileName : function (str) {   
        JSLINT_AIR.app.setFileName(str);
      },
      broadcast : function (event) {
        JSLINT_AIR.app.broadcastEvent(event);
      },
      initApp : function () {
        JSLINT_AIR.app.init();
      }
   }
}
Prior to sandboxing the application, I initialized it within the Ext.onReady(), but under the new security model, the window.onload event in ui.html runs prior to that in root.html, so in order to ensure that the AIR API is exposed prior to initializing the applciation, I provide an initApp() method in the childSandboxBridge that is called within the window.onload event in root.html. To exit the application in the beta 1 version, I called the following method in my menu component.
function exitApp()
{
    air.Shell.shell.exit();
}
As described above, direct calls to 'air' are not possible in the child sandbox under the new security model, so I moved air.Shell.shell.exit() to the application sandbox and exposed it as 'exitApllication'. Now in my menu component, I call parentSandboxBridge.exitApllication() when a user selects the 'File|Exit' menu. General Impressions HTML/Javascript developers will clearly have to consider the extra time required to develop under new security model and plan their applications carefully. Fortunately, I put a lot of effort up front to organize my code and encapsulate my AIR API calls, so that exposing them as the 'parentSandoxBridge' required a minimum amount of refactoring (especially given that my app had a limited amount of AIR functionality built in). Since I plan to maintain JSLint AIR as an HTML/Javascript app, I will likely redesign some aspects in a future release as I feel the current iteration is less cohesive than the last.