Posts in Category "API tips"

API Tip: Don’t Abuse File.applicationStorageDirectory

It was recently pointed out to me that some applications, which shall remain nameless here, are using File.applicationStorageDirectory to cache hundreds of megabytes of files. Please, don’t do this.

The File.applicationStorageDirectory property is intended to provide applications with a guaranteed writable, unique location in which they can save important information, regardless of which platform they’re running on. The information is saved to locations that are, on typical machines, backed up. On Windows, it’s saved under “<user>/Application Data”, which also roams with the user, if roaming profiles are enabled. There’s an implicit assumption here that the amount of data being stored is relatively small–megabytes at most.

Large sets of cached files should be stored elsewhere, in locations that aren’t backed up and don’t roam. AIR doesn’t include a handy property for finding such a location, but don’t let that stop you; the filesystem API lets you access any location on disk, after all. On Windows, you can use the “<user>\Local Settings\Application Data” directory (note the “Local Settings”); on Mac OS, use “<user>/Library/Caches”.

Object Graphs, the GC, and System.disposeXML()

AIR 1.5.2 contains a new, performance-related API that heavy users of XML may find handy, System.disposeXML(), that is an instructive example regarding use (and abuse) of the AIR garbage collector.

When an XML document is loaded, it’s stored in memory as a graph of individual objects representing elements, text, attributes, and so on. Each pair of related objects–for example, a parent/child element parent–are linked, in the underlying representation, with pointers going in both directions.

This is a convenient representation for traversal, but can be problematic for the garbage collector. As I mentioned in my earlier post about referencing counting and ActionScript objects, the garbage collector will eventually find and discard the entire set of objects after the last external reference is removed. However, it takes more work for the GC than less-connected data structures.

In practice, the amount of time it takes the garbage collector to do its job is related both to the number of allocated objects and the number of pointers between them. So while it is nice that the GC is capable of finding even these highly-connected graphs of objects, using them is also asking it do a lot of work. You can reduce this workload by explicitly disconnecting the pointers between objects.

For graphs of ActionScript objects, you can ease the GC workload by simply nulling out reference between the objects. Note that you don’t have to find every last reference for this to be helpful. The GC will still do it’s job in the end, and every bit of clearing references helps. For ActionScript objects this can be particularly helpful because, if their reference count then goes to zero, they can be cleaned up even before the GC sweep completes.

XML objects are a trickier case because the API doesn’t allow developers to traverse the actual object graph. That’s where System.disposeXML() comes in: It traverses the internal object graph backing the XML object, setting all of the internal points to null. Afterwords, the XML object is empty and, presumably, useless. But then, since you were done with it anyway, that shouldn’t be a problem.

Preventing ESC in Full Screen Interactive

Penultimate post on the AIR 1.5.2 update: Prior to AIR 1.5.2, if a user hit the escape key when an application was running in fullScreen or fullScreenInteractive, the application would be forced out of full screen mode. This remains the intended behavior for fullScreen, but was a defect in the implementation of fullScreenInteractive.

Starting with AIR 1.5.2, hitting escape causes fullScreenInteractive to exit by default but the behavior can be canceled by calling preventDefault() on the keydown event. Applications that use full screen to keep users (perhaps kids) from too easily leaving the application may find this change useful. As always, remember to update your namespace to take advantage of this new behavior.

SWFs in HTML in Transparent Windows in AIR 1.5.2

One of the admittedly more annoying limitations of the Adobe AIR HTMLLoader implementation is that it does not render SWFs embedded in the HTML when displayed in a transparent window. In AIR 1.5.2, this restriction has been partially lifted.

The problem arises because, by default, Flash Player renders content into a separate window that is positioned on top of the correct location relative to the underlying HTML. This is the original browser rendering for plug-ins. It’s straightforward, but makes it impossible for the rendered plug-in content to participate in the blending of the transparent window’s contents against whatever lies behind the window.

Flash Player has for some time offered alternate rendering models in which the contents are drawn as part of the HTML content instead of as a separate window. This allows the content to be covered by HTML elements–drop down menus, for example. These modes are accessed by setting the “wmode” parameter to “transparent” or “opaque”. See this Tech Note for details.

Prior to AIR 1.5.2, the HTMLLoader refused to display any SWF when it was inside a transparent window in order to avoid the incorrect rendering that would otherwise result. Starting with 1.5.2, the wmode of the content is inspected. If the wmode is “window”–the default value–the content can still not be rendered correctly, and the old behavior still applies. However, if the wmode is “transparent” or “opaque”, the SWF will be rendered and, because it is rendered into the HTML content, will correctly participate in the window transparency.

Using LocalConnection Reliably

Following up on my previous post about changes to LocalConnection in AIR 1.5.2, I thought I’d post some further information about how LocalConnection operates. Although definitely in the realm of implementation detail, it’s critical to know if you want to use LocalConnection in a reliable fashion. To the best of my knowledge, the following applies to all versions of LocalConnection to date.

LocalConnection operates via a shared memory segment that contains a list of endpoints listening for messages, plus a buffer in which messages can be queued up. All clients using LocalConnection periodically poll the segment, removing any messages for which they are the recipient and enqueueing any new messages they wish to send.

Clients that exit gracefully will remove themselves from the endpoint list during shutdown. However, to avoid a crashed client clogging up the segment with undeliverable messages, endpoints (and associated messages) are timed out by the other clients. Basically, any client that finds old messages queued up, thus indicating an unresponsive endpoint, removes that endpoint and associated messages from the segment. “Old” is defined as a few seconds.

This heuristic for detecting crashed clients can be tripped up by clients that merely become unresponsive–i.e. are in a tight loop in ActionScript–for too long. When this happens, the client’s endpoint is closed even though the client itself is still running. After this, the client can no longer receive any messages destined for that endpoint. Note that there are two things that have to happen to get into this situation: the listener has (1) to be unresponsive for a “long” time while (2) simultaneously having LocalConnection messages sent to it.

Clearly, the implementation could be modified to avoid this issue, and you certainly shouldn’t depend on this behavior. Still, I hope that knowing about this issue might help you avoid it by keeping your applications responsive. Keeping applications responsive is, of course, a good thing in any case.

LocalConnection.isPerUser in AIR 1.5.2

New to the AIR 1.5.2 release (and the corresponding Flash Player, 10.0.32) is the LocalConnection.isPerUser property. Note that you’ll need to update your application’s namespace to …/1.5.2 to access this property. Here’s why you should do that.

LocalConnection provides local (i.e., on the same machine) communication between SWFs and AIR applications. It operates via a shared memory segment that’s visible to all processes that use the mechanism.When LocalConnection was first implemented on Mac OS, it used a memory segment that is visible to all processes running on the machine. This was reasonable at the time, but problematic now that Mac OS is a multi-user operating system. The unfortunate result is that LocalConnection can be used to communicate across user accounts on Mac OS.

To address this a new, per-user implementation has been implemented on Mac OS. You should always use this mode; it’s safer. To do that, set LocalConnection.isPerUser = true on every LocalConnection object you create.

Unfortunately, AIR can’t do this for you transparently. The problem is that, if it did, you could get into a situation where version skew breaks use of LocalConnection. For example, this can occur if an application is running on AIR 1.5.2 and attempts to communicate with a SWF in the browser running on Flash Player 9. Until both sides are updated, there’s no way to use the isPerUser = true option. By adding an API and making this an option, we’ve given you a chance to migrate to this option without breaking anything along the way.

This issue is specific to Mac OS. Windows and Linux use a user-scoped LocalConnection in all cases, regardless of the isPerUser setting. You can safely set LocalConnection.isPerUser = true everywhere and be confident that the Windows and Linux behavior won’t change.

Final note: The default setting of this property is likely to change to true in a future release, in order to be consistent with our general philosophy of defaulting to safe behavior.

Pauses When Using ELS and DRM APIs

Lately I’ve fielded a couple of different queries about long pauses in applications using the EncryptedLocalStore (ELS) and DRM capabilities in Adobe AIR. Two questions on the same topic in one week is usually a good indication that some additional explanation is required, so here it is.

As you are probably aware, AIR applications are protected during deployment by digital signatures. These signatures are checked at installation time in order to verify that the application has not been tampered with and, when possible, to reliably display the application’s publisher.

The signatures are preserved by the installation process but are not normally checked when an application is running. However, there are two exceptions to this: the signature is validated when the application uses the ELS or DRM APIs. This is done to prevent attacks on the application’s protected data that operate by modifying the application itself–any such modification would invalidate the signature.

When we designed this mechanism, we targeted applications in the 1 MB to 10 MB size range. This is important because checking the signature requires computing hashes over the entire application. For these sizes we determined that we could compute hashes over the entire application without significant delay, and so we went with the straightforward implementation that does just that. Larger applications, however–and I’ve seen examples of applications over 1 GB in size–will suffer painful delays during these signature validation pauses.

Our current recommendation is that you avoid making applications this big. That may sound trite, but every large application I’ve seen so far was that big because it included assets, such as videos, that consumed the majority of the space. Moving these kinds of non-code-containing assets out of the application itself–for example, downloading them separately into the application storage directory–is a straightforward way to reduce the application to a tractable size.

For completeness, I’ll note that it is possible to design a validation mechanism that works incrementally. For example, Mac OS uses a clever scheme that hashes each page of the executable separately; the kernel can then amortize validation cost across each page as it is first referenced. (If the page is never referenced, it doesn’t matter if it has been modified.) At this time, however, we have no plans to adopt such a scheme for AIR.

One final note: This signature validation usually occurs just once, the first time either the ELS or DRM capabilities are used. However, if you set “stronglyBound” to true when using the ELS API, signature validation will occur on every access. I don’t recommend using this stronglyBound feature, for this and other reasons.

Using InvokeEvent.reason in AIR 1.5.1

AIR 1.5.1, released today, is not quite a typical dot release containing just bug fixes. It also contains one minor—albeit useful—new feature.

Applications can already detect when they’ve been invoked by listening for the InvokeEvent that is dispatched by NativeApplication. This is dispatched when they’re started from the command line, via the GUI, or by opening an associated file.

Applications can also register to be launched automatically during user login. However, prior to 1.5.1, there was no reliable way to distinguish this login case from the the other reasons an application might be started. That turns out to be awkward: In the login case, applications often want to avoid opening new windows. In all other cases, they generally want to make sure they do open a new window.

Starting with AIR 1.5.1, the InvokeEvent class contains a new property, “reason”, that distinguishes between these two cases. It has two possible values: “standard” and “login”. These are, I hope, self-explanatory.

Note that in order to use this new API, you must update your application to use the AIR 1.5.1 namespace by changing its descriptor. (There are no other changes to the descriptor schema in this release.) The new descriptor will trigger an auto-update to AIR 1.5.1 should someone attempt to install your application against an older version, thus guaranteeing that the new API will actually be available.

Changes to HTMLLoader.loadString() in AIR 1.5

For AIR applications bound to version 1.5 and later, the default behavior of the method HTMLLoader.loadString() has changed. This may impact your application; here’s the how and why of the change. (For Flex users, the following discussion also applies to setting the HTML.htmlText property.)

AIR applications are desktop applications and, consistent with that fact, AIR does not prevent you from doing dangerous things like fetching remote content and running it locally. However, it’s rare that such things are desirable and, when they are, they should be done explicitly and carefully. AIR APIs are therefore generally designed to make doing safe things easy and dangerous things hard.

Prior to the AIR 1.5 behavior, HTML content loaded via HTMLLoader.loadString() was placed in the application sandbox. This content has full access to the local machine. Whether or not this is reasonable depends on where you get the string that you load from and what that string contains. Since it’s easy for that string to come from untrusted sources, this provides an easy path for injecting untrusted code into your application.

To address this, starting in AIR 1.5, loadString() defaults to loading content into the browser sandbox. This is the safe thing to do since untrusted code, when running in the browser sandbox, is unable to operate with the same permissions as are otherwise granted to your application.

If you want the old behavior, you can set HTMLLoader.placeLoadStringContentInApplicationSandbox = true. If you do this, remember that you are taking on responsibility for dynamically loading code into the application sandbox and all of the risks that entails. Ethan Malasky’s blog has some great posts in this area. Still, I don’t generally recommend it—it’s hard to get right.

One final note: This change will not break your existing applications. The new behavior is bound to the namespace used in your application descriptor file. Applications using the 1.0 or 1.1 namespaces will operate as before. You will have to take this change into account when you update to the 1.5 (or later) namespace.

Cleaning Up Sensitive User Data

Applications sometimes store potentially sensitive data. Sometimes the items are obviously so, like store passwords. Sometimes it’s less obvious but still sensitive, like your browser history.

I was recently asked how an AIR application could make sure that sensitive data it stores was cleaned up as part of the application uninstall process. That, unfortunately, isn’t possible.

Not all is lost, however. Instead of depending on the uninstall process, applications that store sensitive data should include an explicit method (e.g., a menu item) for clearing that data. Your browser, for example, probably already has such an option. Users can use this before they uninstall to make sure that data is cleared out. As a bonus, they can use it without uninstall, too.

How to clean up the data? In general, deleting the files is sufficient. If you’re using the EncryptedLocalStore API, use the reset() method to erase it.