Invisible Flash

Enhance Web applications by secretly using the Flash Player

Have you ever wished that Cookies were a lot bigger so you could store more data on the client, or that you could make cross-domain Asynchronous JavaScript and XML (Ajax) calls? If so, you are in luck. Both of these techniques can be accomplished using invisible Flash. So, just what is invisible Flash? It is not really invisible, however, it is 1 pixel by 1 pixel, which makes it pretty hard to see. And, it can be used as a way to tap into the capabilities of the Flash Player. In this article, you will learn how to build invisible Flash files that let you to store up to 100 KB of client-side data and make cross-domain Ajax calls—all without your users ever knowing that Flash is being used.

Share:

Michael Galpin, Software architect, eBay

Michael Galpin's photoMichael Galpin is an architect at eBay. He is a frequent contributor to developerWorks. He has spoken at various technical conferences, including JavaOne, EclipseCon, and AjaxWorld. To get a preview of what he is working on next, follow @michaelg on Twitter.



23 February 2010

Also available in Chinese Russian Japanese Portuguese Spanish

Let's get started

As mentioned, in this article you will learn how to use Flash to add extra capabilities to your Web applications. Familiarity with JavaScript is the most necessary skill, but experience with ActionScript will be handy. There are a variety of ways to compile ActionScript, some of which depend upon Adobe's commercial tools. You can, however, just use the open source Flex SDK from Adobe. For this article, Flex SDK 4.0.0.10485 (Beta 2) was used. To run the examples, you will need the Flash Player, version 10 or higher. I use version 10.0.42.34 throughout this article. See Resources for links to acquire these downloads.


Flash local storage

Many Web applications need to save state on the client. Sometimes this may just be a session ID of some sort that can be used to retrieve server-side state from memory or a database. However, many Web applications deliberately avoid server-side state for the sake of scalability. So any state must be stored on the client. In addition, it is often desirable for this state to persist beyond the user's current session.

HTTP cookies have been the default way to handle this for years. However, HTTP cookies have their drawbacks. They can be difficult to use from a developer perspective because they are physically just an HTTP header. More importantly, they are a potential security liability. HTTP cookies are sent back and forth between the client and server on every request, so any data in them could be intercepted. Further, cross-site scripting/forgery often takes advantage of this fact to "steal cookies." If you get your cookies stolen, your corresponding account can be easily compromised and even taken over.

But, the most common drawback of HTTP cookies is the size limitation. Different browsers place different limits on the maximum size of HTTP cookies allocated per domain. The HTTP specification places the limit at 4 KB, and this is all you can count on. So what to do if you need to store more than 4 KB on the client? If you have ever thought it would be fun to write a gzip-style compression algorithm in Java™Script, then this is your opportunity. Or you can use an alternative, like Flash.


Local shared objects

The Flash Player gives Flash applications local storage space. By default, this has a limit of 100 KB per domain. That is right: you get twenty-five times as much space as you would with HTTP cookies. There are also some other key differences. One, client-side data in Flash is never sent to the server, as it has nothing to do with HTTP. Of course, you can take this data and send it to your server if you want to. There is obviously nothing stopping you from doing this. However, you have to choose what data to send and how it is sent. If you really need this data on both the client and server, this can actually become a bit tedious. Although, this is generally much more secure, because you must explicitly "expose" this data over the wire.

The Flash API for storing and retrieving local data is known as a SharedObject. Flash actually has a notion of SharedObjects that can be remote as well, hence the client-only variety is often known as a local SharedObject. The API is very simple, and it allows for the storage and retrieval of arbitrarily complex objects using a key-value paradigm. Listing 1 shows simple code for storing and retrieving SharedObjects.

Listing 1. Store and retrieve SharedObjects
package{
    
    import flash.display.Sprite;
    import flash.net.SharedObject;
    
    public class JsHelper extends Sprite{
        private const SO_NAME:String = "helperSo";
        
        private function saveLocal(name:String, value:Object):void{
            var so:SharedObject = SharedObject.getLocal(SO_NAME);
            so.data[name] = value;
            so.flush();
        }
        
        private function readLocal(name:String):Object{
            var so:SharedObject = SharedObject.getLocal(SO_NAME);
            return so.data[name];
        }
    }
}

If you are unfamiliar with ActionScript, the code in Listing 1 might look a little odd to you. ActionScript is very similar to JavaScript; in fact, it is derived from the ECMAScript standard. However, it has many features usually seen in languages like C++ and Java. Variables are strongly typed, and there is class-based inheritance. The code in Listing 1 is a class that extends the Sprite class. Such a class will compile into a Shockwave Flash (SWF) file that can be embedded in a Web page. This class has two methods. One is called saveLocal. This takes a name (which is just a string) and a value (which can be any type of object). It then gets a particular SharedObject using the getLocal factory method.

Each SharedObject instance has a data property that can be thought of as a hashtable for storing data. That is what the second line of the saveLocal function does. The last line "flushes" the SharedObject, or persists it to disk. That is all you are required to do to save locally. If you make heavy use of SharedObjects and start to get close to the 100 KB limit, then you may want to attach event listeners to it. This will let you respond to events like "flush completed" or "flush failed."

Reading back from local storage is just as straightforward and is done in the readLocal function in Listing 1. In this case, the SharedObject is looked up, and the name parameter is used to look up the saved object from the data hashtable. If name does not correspond to a key in the hashtable, then null will simply be returned. Now that you have seen how to access SharedObjects, you just need to get this Flash (invisibly) on a Web page -- and access it from JavaScript.


Making it invisible

By default, none of the functions shown in Listing 1 will be accessible outside of the JsHelper class. However, Flash makes it very easy to expose functions to JavaScript. All you have to do is use Flash's ExternalInterface API, as shown in Listing 2.

Listing 2. Exposing JsHelper functions to JavaScript
package{
    
    import flash.external.ExternalInterface;
    import flash.net.SharedObject;
    
    public class JsHelper extends Sprite{
        private const SO_NAME:String = "helperSo";
        private const SAVE_LOCAL:String = "saveLocal";
        private const READ_LOCAL:String = "readLocal";
        
        public function JsHelper(){
            ExternalInterface.addCallback(SAVE_LOCAL, saveLocal);
            ExternalInterface.addCallback(READ_LOCAL, readLocal);
        }
        // functions omitted for brevity
    }
}

Listing 2 simply shows what was added to the JsHelper class from Listing 1. The main thing to notice is that you have added an explicit constructor. This will be called whenever an instance of this class is created, just as you would expect. In it, you use the ExternalInterface API to expose the two functions, saveLocal and readLocal, to any JavaScript on any Web page where this SWF will be embedded. The addCallback function takes a string as its first parameter. This will be the name used by JavaScript clients to identify the function. It could be different than the function name in the class, but in this case they are the same. The second parameter is a function closure. Like JavaScript, ActionScript is a functional language, so functions are first-class and can be passed around. This is all that is needed to expose the two functions. Now take a look at the JavaScript for embedding and accessing the SWF. This is shown in Listing 3.

Listing 3. Embedding invisible Flash
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Flash Helper for JavaScript</title>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
    function writeFlash(){
        var attrs = {id : "JsHelper"};
        swfobject.embedSWF("JsHelper.swf", "flashContainer", "1", "1", "10.0.0", 
            "playerProductInstall.swf", null, null, attrs);
    }
    function save(name, value){
        var helper = document.getElementById("JsHelper");
        helper.saveLocal(name, value);
    }
    function load(name){
        var helper = document.getElementById("JsHelper");
        return helper.readLocal(name);        
    }
</script>
</head>
<body onload="writeFlash()">
    <div id="flashContainer"></div>
</body>
</html>

The first thing to notice about this code is that it uses a third-party JavaScript library called swfobject. This is the de facto standard library for embedding SWFs. It is open source, and while it was not originally developed by Adobe, it is distributed by them now. Its embedSWF function is used to embed the SWF compiled from Listing 2. The first parameter is the URL to the SWF. In this case, the SWF is being served from the same server and path as the HTML file, so a relative URL works fine. The second parameter is the ID of the HTML element on the page where the SWF will be embedded within. In this case, it is "flashContainer"—and you will notice that in the body of the HTML document there is an HTML div with this ID.

The next two parameters to embedSWF are the height and width of the SWF. In this case, they are both 1. That means that your SWF will be one pixel tall and one pixel wide. This is the invisibility of SWF! The next parameter is the minimum Flash version that your JavaScript will check for. If Flash is installed on the browser, but it is an earlier version than the minimum version you passed to embedSWF, then embedSWF will use the next parameter "playerProductInstall.swf". This is the URL to another SWF used to prompt the user to install the latest version of Flash. For invisible Flash, this does not really matter—the "you need to install the latest version of Flash" SWF is also going to be invisible (well, a 1x1 pixel anyway). The last parameter passed to embedSWF is important. This is an attributes object that can contain various optional parameters. One of those optional parameters is an ID for your SWF. For a use case like the one in this article, this parameter is not optional—it is critical! This will give your SWF an HTML ID, which you will need to access it programmatically using JavaScript.

Now take a look at the two JavaScript functions in Listing 3. They are very similar. Both use the familiar getElementById function to get a reference to the SWF. Notice they use the ID specified in the attrs object in the writeFlash function. After you get a reference to the SWF, you can directly call the ActionScript functions exposed in Listing 2. The key here is that the function name used in JavaScript must match up to the name exposed through the ExternalInterface.addCallback function or the first parameter passed to addCallback.

For the save method, you are passing a JavaScript object in as the value parameter. This can be any object, even one with a deep nested structure. However, only its data will be passed in. If the object has any functions, those will not be passed. Notice that the load method returns whatever comes back from the SWF. What will this be? The answer is simple: whatever you sent. If you stored a scalar value like a number or string, then that is what will come back. If you stored a complex object, then that JavaScript object is what will come back—no need to parse, and so on. One exception is that if your object has functions, those can obviously not be serialized and saved. So it is just the data from your object, no more, no less. That concludes everything you need to know about using Flash for local storage. Before moving on and looking at cross-domain Ajax with Flash, examine some of the alternatives for local storage that may be available to you.


Other local storage options

I mentioned that Flash can be an attractive option to HTTP cookies for client-side storage. However, there are other Web technologies that have adopted the same paradigm as Flash's SharedObjects. In fact, for years the various browsers have provided very similar APIs. However, these APIs were usually different from one browser to the next. So you could do some browser sniffing and then use the browser-specific API. Flash provided a consistent alternative. The code developed in this article will work on virtually any browser: Internet Explorer 6 and Firefox 2, all the way up to the latest versions of those browsers. The newer versions do provide a consistent alternative. The HTML 5 specification includes a localStorage API. This is implemented in the latest version of the major browsers, including IE 8 and Firefox 3.5. So if you only have to worry about those browsers, then localStorage is a viable option. If you have to worry about older browsers (IE 6, IE 7, and so on), then it may be simpler to stick with Flash. Now, take a look at another place where Flash can open up new capabilities, cross-domain Ajax.


Cross-domain Ajax

Ajax has become ubiquitous for Web applications; it is an assumed part of any Web application. One of the major constraints of Ajax is the notorious same origin policy. If your Web page was served from a.com, then you can only make Ajax (or more precisely, XMLHttpRequest) calls to a.com. You cannot call b.com, for example. It does not matter if your company owns both a.com and b.com; the browser does not care. However, this is not the case for Flash applications.

Flash applications can be granted permission to make calls to domains other than the one that they were served from. This is done using a cross-domain policy file. By default, the Flash run time will look for this policy file at the document root of your server: http://<your domain>/crossdomain.xml. For example, Listing 4 is the policy file for Twitter's search domain, http://search.twitter.com/crossdomain.xml.

Listing 4. Policy file for Twitter search domain
<!DOCTYPE cross-domain-policy 
SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
    <allow-access-from domain="*" />
</cross-domain-policy>

This is an especially nice policy file. It is granting access to SWFs from any (as indicated by the "*" wildcard) domain. So any Flash application can call Twitter's search APIs. Policy files like this are common from major Web sites with public APIs. However, some sites also use more restrictive policies that only allow access from other domains that they own or have partnerships with. With policy files, you get fine-grained control over exactly who can call your servers and who cannot. Take a look at how you can extend these same abilities to Ajax applications.


Going cross-domain

If your application only needed to call a specific domain, you could write ActionScript code that called that domain, and then expose it using ExternalInterface to JavaScript in your application. However, I will take a little more ambitious route and try to expose this generically. Thus, in Listing 5 you will see a utility class for calling any domain from ActionScript.

Listing 5. Cross-domain utility SWF
package{
    
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.external.ExternalInterface;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    
    public class JsHelper extends Sprite{
        private const SEND_REQUEST:String = "sendRequest";
        
        public function JsHelper(){
            ExternalInterface.addCallback(SEND_REQUEST, sendRequest);
        }

        public function sendRequest(url:String, handlerName:String, 
                method:String="GET", content:Object=null, 
                headers:Object=null):void{
            var loader:URLLoader = new URLLoader();
            var request:URLRequest = new URLRequest(url);
            if (method){
                request.method = method;
            }
            if (headers){
                for each (var name:String in headers){
                    request.requestHeaders[name] = headers[name];
                }
            }
            if (content){
                request.data = content;
            }
            loader.addEventListener(Event.COMPLETE, 
                function(e:Event):void{
                    ExternalInterface.call(handlerName, loader.data);
            });
            loader.load(request);
        }
    }
}

This class exposes its sendRequest function to JavaScript using ExternalInterface. This is done in the object's constructor, just as you've done in the previous example in Listing 2. The sendRequest function is a little more complicated: It has two required parameters. The first paramater is the URL that you want to call. This is the full URL string, including any request parameters. Next is the name of the JavaScript function that you want Flash to call when it gets a response from the server. As with typical Ajax, Flash makes asynchronous calls to remote servers so that the main UI thread is not frozen while awaiting a response from the remote server. Therefore, just like with Ajax, you need to write a callback function that will get invoked after a response is received from the server. You pass this into Flash as a string, but it must be the exact name of the function.

The sendRequest function has three optional parameters as well. ActionScript lets you have optional parameters, but they must have default values to use. The first of these is the HTTP method to be used, which is typically either GET or POST. In this article, I default to GET, but you can easily override it to POST if that is required by the remote server. The next optional parameter is called content. This is a generic data object that would contain any name-value pairs that you want to send to the remote server. You would need to use this if you are posting data to the remote server. Finally, the last optional parameter is another generic data object for headers. This is for setting custom HTTP headers to send as part of the call to the remote server.

The code then takes all of these parameters and constructs an HTTP request using Flash's URLRequest object and sends the request using Flash's URLLoader class. An event listener is wired up to the URLLoader so that you can process the response after it is returned. Here you create a closure, just like you would do in JavaScript, to create an anonymous inline function that will be invoked when the COMPLETE event is fired by the loader. This closure will simply use ExternalInterface to call the function whose name was passed in to sendRequest. It will pass all of the data from the remote server to that function. This is definitely a little more of a complex use case than the local storage one. Take a look at Listing 6 for an example that calls Twitter search.

Listing 6. Calling Twitter search
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Calling Twitter from the client</title>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
    function writeFlash(){
        var attrs = {id : "JsHelper"};
        swfobject.embedSWF("JsHelper.swf", "flashContainer", "1", "1", "10.0.0",
            "playerProductInstall.swf", null, null, attrs);
    }
    function search(){
        var keyword = document.getElementById("keyword").value;
        var helper = document.getElementById("JsHelper");
        helper.sendRequest("http://search.twitter.com/search.json?q=" + keyword, 
"showResults");
    }
    function showResults(responseStr){
        var response = eval("(" + responseStr +")");
        var results = response.results;
        alert("#Results = " + results.length);
    }
</script>
</head>
<body onload="writeFlash()">
    <div id="flashContainer"></div>
    <div id="inputDiv">
        <label for="id">Search Twitter</label>
        <input type="text" id="keyword" name="keyword"/>
        <input type="button" value="Search Twitter" onclick="search()"/>
    </div>
</body>
</html>

The code here is similar to the HTML/JS you saw in Listing 3. Again, you use the swfobject library to embed the SWF on the page. This code has a simple form that prompts the user to enter a keyword to search on Twitter. The search function is invoked after the Search button is clicked. This pulls the keyword from the input field and uses it to create the URL string that you want to call. This is passed to the JsHelper SWF, just like you did in Listing 3, by getting a reference to the SWF using its ID and then directly calling the function exposed using ExternalInterface. You pass in the URL and a string naming the callback function. After the data is returned from Twitter, it is passed to the showResults function. This will be a JSON string because that is what comes back from Twitter, so you can simply use JavaScript's eval function to turn it into an object. In this case, you simply show the number of results returned from Twitter. However, you could easily create some HTML on the page that listed each of these results; it's just a matter of boilerplate DOM code. Just like that, you have made a cross-domain Ajax call courtesy of Flash. As with using Flash for local storage, there are some other options that you should be aware of.


Cross-domain alternatives

For years, Flash has made it possible to do cross-domain calls, just like it has made it possible to do local storage for years. Similar to the local storage use case, some Web browsers have come up with their own way to do this, and there has been subsequent attempts at standardization. It has not been as nice of a story as it was with local storage. Even though there is currently a standard for cross-domain Ajax, it is not adopted by IE 8. Instead, IE 8 has a proprietary way of doing cross-domain Ajax. This proprietary implementation is new in IE 8 and not present in older versions of IE. So once again, Flash presents a model that works on all browsers.

Join the Web development group on My developerWorks

Discuss topics and share resources with other developers about Web development in the My developerWorks Web development group.

Not a member of My developerWorks? Join now!

Finally, if you do any research into cross-domain Ajax, you will surely find other ways to do it that do not require Flash or any new features in browsers. This technique, usually referred to as JSONP (JSON with Padding) or "dynamic script tag," takes advantage of the fact that the browser does not enforce the same origin policy on JavaScript source files. So a script tag is created whose source points to the URL that you want to call, and is inserted into the DOM. The URL usually includes a "callback" parameter, which is the name of the callback function that you want invoked. The server then wraps the data with the function name so that the function gets invoked with the data from the server passed into it. This technique is widely used on the Internet (for example, the Twitter search API showed earlier supports this technique), but it has serious security risks associated with it—therefore, the desire for a standard, safe way to do this, similar to how Flash does it.


Summary

This article explained how to use Flash to expand the capabilities of your Web applications. You can dramatically increase how much local storage is available to your application. This allows you to cache large amounts of data on the client. You can also make cross-domain Ajax calls from your Web applications using Flash. This is only a small taste of some of the capabilities of Flash. If you find something else that will help you, you can use the same techniques: make the Flash invisible and expose its functions using ExternalInterface.


Download

DescriptionNameSize
Article source codeJsHelper.zip12KB

Resources

Learn

Get products and technologies

  • Download the Flex SDK.
  • Innovate your next open source development project with IBM trial software, available for download or on DVD.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development, Open source
ArticleID=469076
ArticleTitle=Invisible Flash
publish-date=02232010