Login Register

Alternative Host Environments: Adobe AIR

Introduction to Adobe AIR and Dojo

Adobe AIR is a new runtime by Adobe. Adobe AIR allows web applications permission to the local file system that traditional browser-based web applications do not normally receive due to security policies.

As of Dojo Toolkit 1.1.0 Beta 1, Dojo-based applications will now run successfully in Adobe AIR's secure application sandbox.

Getting Started

  1. Download and install the Adobe AIR runtime and Adobe AIR SDK
  2. Download the latest version of Dojo
  3. Read the documentation below:

Modifications were required throughout the Dojo Toolkit to accommodate Adobe AIR's security policies for local web applications. Changes to Dojo were made in the following areas:

  • dojo.require() pre-onload
  • dojo.require() post-onload
  • dojo.addOnLoad()
  • dojo.query()
  • dojo.parser
  • dojo.fx
  • Dijit widget system
  • AIR dojox.storage provider

Creating the Development Environment

After installing the Adobe AIR runtime and the Adobe AIR SDK, you will find a bin directory inside the SDK containing the adl command used to execute an AIR application. By adding this bin folder to your path, you'll be able to execute the adl command from anywhere.

Start by creating an empty directory, and add Dojo to that directory.

Dojo's Package System

At the heart of the Dojo Toolkit is a package system that allows code to be loaded on demand. There are two loader mechanisms in Dojo: a default loader and a cross-domain (XDomain) loader. The default loader uses XmlHttpRequest (XHR) to retrieve the code. Once the code is downloaded, it is evaluated using eval(). This method is synchronous, so when a package is downloaded, its dependencies are also retrieved and executed. The XDomain loader uses script tags to retrieve dependencies, eliminating the need for evaluating the code after downloading. This method is asynchronous, so the XDomain loader has additional functionality for checking when the script has completely downloaded and to defer the declaration of the package until its dependencies have been loaded.

No debugAtAllCosts

Dojo has a configuration parameter called “debugAtAllCosts” which forces the use of the XDomain loader. This feature does not work properly with apps running on Adobe AIR. It is not required for developing Dojo applications, but needs to be disabled for applications to run on Adobe AIR.

dojo.require(): Using the default loader in the Adobe AIR Application Sandbox

Pre-onload

The default loader uses XHR to read in the contents of a package, then evaluates it. This functionality works as expected on Adobe AIR.

Post-onload

When the default loader gets the contents of a package, it evaluates the contents. Because eval() is prohibited post-onload in Aodbe AIR, dojo.require() cannot function post-onload. If dojo.require() is absolutely necessary post-onload, the XDomain loader is your only option. Otherwise simply define all your dojo.require()'s before or during onload which is analogous to other programming languages which require you to define all external code dependencies via an import/include at the beginning of the code file.

dojo.require() Using the default loader in the Adobe AIR Non-Application Sandbox

The default loader functions properly both pre-onload and post-onload.

dojo.require() Using the XDomain Loader

In order to use XDomain loader with apps running inside Adobe AIR, you must perform a custom build. Note the change that was required to make the XDomain loader work with Adobe AIR:

if(dojo.isAIR){
        this._xdTimer = setInterval(function(){dojo._xdWatchInFlight();}, 100);
}else{
        this._xdTimer = setInterval(dojo._scopeName + "._xdWatchInFlight();", 100);
}

Additionally, the _loadUri() function has to be modified to strip the app:/ protocol from the beginning of the URI since the app:/ protocol is unavailable in the non-application sandbox.

if (dojo.isAIR) {
        xdUri = xdUri.replace("app:/", "/");
}

Next you need to define a build profile which tells the build system which files to bundle in the build. We are not concerned with combining packages into a single file as much as we are in the XDomain loader and generation of .xd versions of each package. The difference in the .xd files is the package's contents are wrapped in a function call that allows the package's execution to be deferred until it's dependencies have been loaded.

A build profile may look like the following:

// my-build-profile.js
dependencies = {
        layers: [
                {
                        name: "dojo.js",
                        dependencies: [
                                "dojo.parser",
                                "dijit.dijit"
                        ]
                },
        ],
        prefixes: [
                [ "dijit", "../dijit" ],
                [ "dojox", "../dojox" ]
        ]
}

To run the build, execute the following from the command line:

cd dojo/util/buildscripts
./build.sh profileFile=../../../my-build-profile.js action=clean,release
releaseDir=../../../ releaseName=MyBuild loader=xdomain xdDojoPath=app:/MyBuild

Now you can reference the build in your code by including the following:

<script type="text/javascript" src="/MyBuild/dojo/dojo.xd.js.uncompressed.js" />

dojo.require() will now work pre-onload and post-onload.

dojo.addOnLoad()

dojo.addOnLoad() registers callback functions to be executed when the page loads. The default loader properly executes the callback functions before the page's onload event is fired. The XDomain loader on the other hand fires the onload callbacks after the body's onload has fired. This causes problems for the XDomain loader in the application sandbox if parseOnLoad is enabled since Dojo will be unable to parse and eval() the data post-onload.

dojo.query()

Dojo has a query function that is used to search the DOM for nodes. The default query function works for simple queries such as “div” or “li > a”. However, the default query function is unable to find nodes based on a specific attribute. For example, the default query function is unable to locate the element <button dojoType=”dijit.form.Button”>Click Me</button> using the
query “[dojoType]”. This was fixed by the following change to Dojo:

// uncomment to disable XPath for testing and tuning the DOM path
_getQueryFunc = getStepQueryFunc;

dojo.fx

Dojo's FX library works as expected in both Adobe AIR's application and non-application sandboxes as well as with both the default and XDomain loaders.

dojo.parser

Dojo's parser scans the DOM using dojo.query() for declarative markup. If markup is found, it is evaluated. The parser can either be invoked automatically when the page loads or manually. The parser can be executed onload by specifying the following in the djConfig:

<script type="text/javascript" src="path/to/dojo.js" djConfig="parseOnLoad:true" />

The parser will then scan the DOM for declarations such as:

<button id="myButton" dojoType="dijit.form.Button">Click me!>/button> 
<script type="text/javascript"< 
dojo.declare("tests.parser.SaySomething", null, { 
	sayWhat: "", 
	constructor: function(args, node){ dojo.mixin(this, args); }, 
	sayIt: function() { alert(this.sayWhat); } 
}); 
</script> 
<div dojoType="tests.parser.SaySomething" jsId="obj" sayWhat="Hello World!"> 
<script type="dojo/method" event="sayItReversed">
	alert(this.sayWhat.split("").reverse().join("")); 
</script> 
<script type="dojo/connect" event="sayIt"> 
	alert("sayIt() was called!"); 
</script> 
</div>

The parser works in both application and non-application sandboxes as well as with the default loader. When using the XDmain loader, the parser works as expected in the non-application sandbox. However, the XDomain loader fails in the application sandbox because the parser cannot
eval() post-onload inside Adobe AIR.

Dijit: The Dojo Widget System

Dijit widgets work both declaratively and programatically using the both the default loader in both application and non-application sandboxes. Dijit does not work properly with the XDomain loader since parser doesn't fire correctly.

AIR dojox.storage Providers

There are 3 new storage providers that leverage the AIR's API:

  • dojox.storage.AirFileStorageProvider
  • dojox.storage.AirDBStorageProvider
  • dojox.storage.AirEncryptedLocalStorageProvider

When using the XDomain loader inside Adobe AIR, dojox.storage is unavailable until onload unless dojox.storage and the providers you wish to use are included in the XDomain build.

These 3 AIR specific storage providers are only available in the application sandbox. It would take a large amount of abstraction to each provider with respect to the AIR API calls plus a significant sized snippet of code in the parent window to perform the storage operation.

AirFileStorageProvider

The AirFileStorageProvider store the data in flat files in the app-storage directory. For example, a key of “MyKey”, value of “Hello World!”, and namespace of “MyNamespace” would create the file app-storage:/__DOJO_STORAGE/MyNamespace/MyKey which contains the text “Hello World!”. This storage provider allows for large amounts of data to be stored. Not only can strings be stored, but also objects thanks to AIR's serialize/de-serialize functionality. On the downside, data is not encrypted on disk. It would be trivial however to encrypt the information using dojox.crypto before storing the data.

AirDBStorageProvider

The AirDBStorageProvider leverages AIR's embedded database. When the provider is initialized, a database file is created in the app-storage directory and the Dojo storage table is created. The table holds the namespace, key, and value. This provider is similar to the AirFileStorageProvider in which it can store large amounts, but it cannot store serialized objects because there is no way to de-serialize them due to eval() being unavailable post-onload. The database file is not encrypted, but you could encrypted the data using dojo.crypto prior to storing the data. One advantage of the AirDBStorageProvider is there is only one file written to disk whereas the AirFileStorageProvider writes a file for each key/value.

AirEncryptedLocalStorageProvider

The AirEncryptedLocalStorageProvider uses AIR's encrypted local data store functions. Data, such as passwords, will be encrypted when being stored and decrypted when being retrieved. Similar to the AirEncryptedLocalStorageProvider, objects cannot be stored because they cannot be
de-serialized with an eval(). One limitation of the AIR's encrypted local data store is it does not provide a way to enumerate keys. To solve this, the AirEncryptedLocalStorageProvider creates a registry using AIR's encrypted local data store to track the namespaces and keys.

Default vs. XDomain Loader

The default loader cannot dojo.require() post-onload, however the XDomain loader can.
The XDomain loader requires a custom build, which in turn requires Java runtime environment installed. The default loader does not require a build, but would work if it was a custom build. If you are using the XDomain loader, unless the package you are requiring is already in the build, you cannot reliably access it until onload is fired.

There may be caching issues with the Xdomain loader pulling in code that not already in the build on subsequent requests, which causes out of order execution of code before dependencies have loaded. It is recommended to bundle all needed packages into the XDomain build. The XDomain loader fires dojo.addOnLoad() callbacks after onload which causes Dojo's parser in the application sandbox to fail because it cannot eval() post onload inside Adobe AIR.

Application vs. Non-Application Sandbox

The non-application sandbox does not recognize the app-storage:/ and app:/ protocols. Due to the lack of eval() post-onload, the XDomain loader doesn't work in the application sandbox when the parser is used.

application.xml

Here is an example of an application.xml file for a Dojo-based web application:

<?xml version="1.0" encoding="utf-8" ?>
<application xmlns="http://ns.adobe.com/air/application/1.0">
        <id>org.dojotoolkit.DojoAir</id>
        <filename>DojoAir</filename>
        <name>Dojo on AIR!</name>
        <version>v1.1</version>
        <description></description>
        <copyright></copyright>
        <initialWindow>
                <title></title>
                <content>index.html</content>
                <systemChrome>standard</systemChrome>
                <transparent>false</transparent>
                <visible>true</visible>
                <width>680</width>
                <height>550</height>
                <resizable>true</resizable>
                <x>500</x>
                <y>100</y>
        </initialWindow>
        <installFolder>Adobe/AIR/DojoAir</installFolder>
        <icon>
                <image16x16>icons/ApolloApp_16.png</image16x16>
                <image32x32>icons/ApolloApp_32.png</image32x32>
                <image48x48>icons/ApolloApp_48.png</image48x48>
                <image128x128>icons/ApolloApp_128.png</image128x128>
        </icon>
</application>

Known Issues

The following are limitations of running Dojo in the Adobe AIR environment:

  • Only use declarative syntax when you can guarantee its evaluation will be performed pre-
    onload or during onload. Avoid using to define functionality
    that hooks into widget. Rather define the function using the traditional method, then
    connect to the hook you are interested after the widget has been created.

Summary

The changes required to integrate Dojo into the AIR runtime are very focused, in most cases requiring minimal changes from your development approach. There were no noticeable performance issues when not using a build, as Adobe AIR is very fast at loading scripts and other resources. For most projects, it will be easiest to use the default loader within the application sandbox provided you could declare all dojo.require()'s before or during onload.

Adobe engaged SitePen to make updates to Dojo to address compatibility issues. If you're curious, Dojo Changeset 12311 contains the specific changes.