Adobe AIR EncryptedLocalStore Folder Paths

June 1st, 2011 by Steven Sacks

If you've ever gone searching for these on the web, you know it's a royal pain. Here they all are in one convenient place.

Windows 7/Vista

Application Data
C:\Users\[USERNAME]\AppData\Roaming\[APP_ID]
ELS
C:\Users\[USERNAME]\AppData\Roaming\Adobe\AIR\ELS\[APP_ID]

OS X

Application Data
OSX/Users/[USERNAME]/Library/Preferences/[APP_ID]
ELS
OSX/Users/[USERNAME]/Library/Application Support/Adobe/AIR/ELS/[APP_ID]

Windows XP

Application Data
C:\Documents and Settings\[USERNAME]\Application Data\[APP_ID]
ELS
C:\Documents and Settings\[USERNAME]\Application Data\Adobe\AIR\ELS\[APP_ID]

Posted in Adobe, AIR having 1 comment �

Adobe AIR: Avoiding SQL Recursion Lockups

April 9th, 2011 by Steven Sacks

Adobe AIR has very easy integration with SQLite. And because of this ease, there is likeihood of hitting a little-known limitation with SQLite recursion which can cause your application to lock up. Anyone who uses the local SQLite database in Adobe AIR may encounter this during their development when dealing with large sets of data. The purpose of this article is to explain the issue and provide the simple solution.

If you are using an asynchronous connection to your SQLite database, and you execute a statement on your async connection and wait for the SQLEvent.RESULT, it is easy to assume from the documentation that there is a break in the thread between your statement.execute() and the result event. However, this is not the case. If you execute the next statement in the event listener, that execute occurs in the same thread, which means your execute() statements eventually end up in one long thread, which causes a SQL recursion lock up.

The Basic Setup

In order to execute a list of items on something asynchronous, you need to take the list of data you want to insert/update/whatever, create an index to increment, and run them one at a time, waiting for the asynchronous api to tell you it is finished before running the next one.

The SQLInsertService class (not shown) takes a predefined number of arguments and passes them to a statement as parameters and then executes that statement and re-dispatches the SQLEvent.RESULT from the statement instance. The reason for this class is to use the same statement over and over so you're not constantly recreating the SQLStatement, and instead just changing the parameters (as recommended by the Adobe AIR documentation). I'm ignoring handling an error event for the purposes of this example.

pseudocode

private var insertService:SQLInsertService = new SQLInsertService();
insertService.addEventListener(SQLEvent.RESULT, onInsert);

private var index:int;
private var data:Array;

public function process(value:Array):void
{
	data = value;
	index = 0;
	executeNextInsertStatement();
}
private function executeNextInsertStatement():void
{
	var item:Object = data[index];
	insertService.execute(item.prop1, item.prop2, item.prop3);
}
private function onInsert(event:SQLEvent):void
{
	if (++index < data.length) executeNextInsertStatement();
	else trace("All Data Inserted");
}

If the asynchronous connection was asynchronous the way you might assume initially, then the above code would have a thread break between statement.execute() and the SQLEvent.RESULT each and every single iteration. However, when you pass a large amount of items to be executed, the SQLConnection instance will eventually lock up and become permanently unresponsive as it is creating one long thread of SQL statement executions (execute > result execute > result execute> result execute > etc.). This means that the Asynchronous SQLConnection is not actually running asynchronously as you might expect, such as the way making a call to a server would be asynchronous.

You used to get a weird runtime error that could not be caught by a try…catch and the exact error thrown would change based on where your try…catch was. It was confusing and so I reached out to the AIR team to help figure it out, and, after they investigated, I was told that it was caused by hitting a SQL recursion limit and that the limit was different from machine to machine. In recent builds of AIR, that runtime error no longer appears to trigger, so your SQL connection will die silently and you won't know about it. The rest of your application will work, but any statement executes through that SQLConnection instance will just stop working. No errors are thrown, it just stops working.

The Solution

Thankfully, the solution is simple. You create a 1ms timer to break the thread in the RESULT event handler. However, if you ran this timer after every single RESULT event, it would take you a very long time to process a large number of items (since you could only process 1000 per second, which is much slower than the SQL database is able to handle).

So, you pick a safe number of iterations at which to break the thread (the maximum number of executes per thread varies from computer to computer, and from my experience, it's no sooner than 150-200 that you hit the limit, so I use 100 iterations just to be safe), check the modulus of the index to that value, and run your 1ms timer whenever the modulus == 0. This will break the thread, end the recursion, and your SQL connection will never lock up.

private var executeInsertTimer:Timer = new Timer(1, 1);
executeInsertTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onExecuteInsertTimer);

private function onExecuteInsertTimer(event:TimerEvent):void
{
	executeInsertTimer.reset();
	executeNextInsertStatement();
}
private function onInsert(event:SQLEvent):void
{
	if (++index < queuedIndexItems.length)
	{
		if (index % 100 == 0) executeInsertTimer.start();
		else executeNextInsertStatement();
	}
	else dispatchEvent(new Event(Event.COMPLETE));
}

That's it. Problem solved. Granted, inserting 100,000 records will now take a minimum of 1 second, however, for normal use, this 1ms delay will be unnoticeable (and obviously less noticeable than your database connection locking up after processing 200+ records).

Of course, don't forget that you should be doing all of these inserts wrapped between a SQLConnection.begin() and SQLConnection.commit().

Because of this thread issue, I have to wonder if the asynchronous connection actually prevents the rest of your application from locking up while processing a large number of records (which is the whole reason for using asynchronous connections in the first place). I haven't done any tests of that so far, but, to be honest, unless you're dealing with hundreds of thousands of records, you're never going to notice any hiccups even on a synchronous connection, at least on the desktop. It's blazingly fast.

Also important to note is that this is not specific to insert statements. I was using insert for the example, but any executed statement whose SQLEvent.RESULT triggers another execute (over and over) will cause this.

Posted in Adobe, AIR, Tips/Tricks having 4 comments �

Help Japan

March 12th, 2011 by Steven Sacks

Like many people I know, I am shocked by the disaster unfolding in Japan. Japan is an amazing country and while the Japanese people are some of the most resilient on earth, the scope of this disaster is such that they need help from the international community.

The International Red Cross, Doctors Without Borders, and other aid services will use your donations to help in the relief and rescue efforts. Your donations will help tens of thousands of people who have lost everything. Whatever you can afford to give, please do.

American Red Cross
Doctors Without Borders
Oxfam
Global Living
Care

USA: Text REDCROSS to 90999 to donate $10

James White, aka Signalnoise, has information on his site on how to donate to help Japan recover from this disaster, as well as merchandise you can purchase whose proceeds go to helping with the disaster relief.

Signalnoise Help Japan

Posted in General having 1 comment �

Flash CS5 TLF Engine Causes Errors With Loaded SWFs

May 28th, 2010 by Steven Sacks

I have discovered an issue with Flash CS5 and a TLF TextField in a loaded swf.

If you use TLF Engine in a loaded swf, you cannot call a public function of a document class (runtime error function doesn't exist), you cannot cast the swf as its document class or cast it as an interface the document class implements (both cause a coercion error).

It's an extremely common use case to need one swf to load another swf and need that other swf to have TLF TextFields in it, and also need to call public functions on the swf, or cast the loader.content as the document class or interface.

You can download a trivial example here:
TLF_Fail.zip

In the zip, there are two .fla files, main.fla and other.fla, each with a document class. The other.fla has a TLF TextField on its stage and its document class has a public function customFunc() that traces that it was called. The Main document class creates a loader, loads the other.fla and when the COMPLETE event fires, it tries to call the public function or tries to cast the document class as the document class or cast the document class as its interface. Each of these causes an error.

Main.as

package
{
	import flash.display.Loader;
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.net.URLRequest;

	public class Main extends MovieClip
	{
		private var loader:Loader;

		public function Main()
		{
			super();
			init();
		}
		private function init():void
		{
			addChild(loader = new Loader());
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
			loader.load(new URLRequest("other.swf"));
		}
		private function onLoadComplete(event:Event):void
		{
			// this says that there's no such function on the __Preloader__
			MovieClip(loader.content).customFunc();

			// this causes a coercion error
			//Other(loader.content);

			// this causes a coercion error
			//ICustom(loader.content).customFunc();
		}
	}
}



Other.as

package
{
	import flash.display.MovieClip;
	import fl.text.TLFTextField;

	public class Other extends MovieClip implements ICustom
	{
		public var TLF_Foo:TLFTextField;

		public function Other()
		{
			super();
		}
		public function customFunc():void
		{
			trace("custom func called");
		}
	}
}



ICustom.as

package
{
	public interface ICustom
	{
		function customFunc():void;
	}
}



Here are the error messages:

MovieClip(loader.content).customFunc();

ReferenceError: Error #1069: Property customFunc not found on Other__Preloader__ and there is no default value.
	at Main/onLoadComplete()



Other(loader.content);

TypeError: Error #1034: Type Coercion failed: cannot convert Other__Preloader__@23c88121 to Other.
	at Main/onLoadComplete()
ReferenceError: Error #1056: Cannot create property __rslLoaders on Other.
	at fl.rsl::RSLPreloader/contentComplete()



ICustom(loader.content);

TypeError: Error #1034: Type Coercion failed: cannot convert Other__Preloader__@23c98121 to ICustom.
	at Main/onLoadComplete()


There are tens of thousands of developers worldwide who have a big stake in this issue being resolved because the Gaia Framework casts all of its pages as interfaces and calls functions on them and this bug makes it so developers cannot use the Gaia Framework with the TLF Engine.

Adobe, please fix this issue.

UPDATE 1
I decided to run a test to see how long before the loader.content is my swf and not Adobe's TLF Preloader. It never happens.

private var count:int;

private function onLoadComplete(event:Event):void
{
	addEventListener(Event.ENTER_FRAME, onFrame);
}
private function onFrame(event:Event):void
{
	trace(++count + ": " + loader.content);
}


Here's the output:

1: [object Other__Preloader__]
2: [object Other__Preloader__]
3: [object Other__Preloader__]
4: [object Other__Preloader__]
5: [object Other__Preloader__]
...
89: [object Other__Preloader__]
90: [object Other__Preloader__]
91: [object Other__Preloader__]



When you load a swf that has TLF inside it, the loader.content is never your swf.



UPDATE 2
If you go into the Actionscript settings of the swf using the TLF Engine and changed the Runtime Shared Library Settings Default linkage to "Merged into code" the errors go away. But, the problem with this workaround is that the swf increases in size by 125k.

This means that for enterprise sites that use multiple swfs, every swf that uses TLF has to increase by 125k. I can imagine that this will dissuade enterprise customers like Disney, HBO, Ford and other big companies who serve millions of visitors daily from using the TLF engine on their sites.



UPDATE 3
I've done some more investigating. I trace the parent stack when ADDED_TO_STAGE is fired inside the loaded swf. The ADDED_TO_STAGE event fires twice. Here's what happens.

When you use Loader to load a swf that has an RSL (which, in this case, is the TLF engine), and you load that swf, Adobe's Preloader is what it actually loads. Then, Adobe's Preloader creates a Loader and adds that Loader to its display stack, alongside a Shape that is the visual part of their preloader. Adobe's Preloader then tells its Loader to load your swf.

Once your swf loads, it's already on the stage, so ADDED_TO_STAGE is fired. Then, Adobe's Preloader calls removeChild() on the Loader and the Shape, and then calls addChild(loader.content), which triggers another ADDED_TO_STAGE event. After that, your loaded swf's parent is their Preloader, not their Preloader's Loader.

Now here's where it gets complicated. The Preloader attempts to add a property called __rslLoaders to your document class. If you reference the document class of the loaded swf in your Main document class before you load your swf (such as you write code that casts the loaded swf as your document class, i.e. Other(loader.content) you get a runtime error:
ReferenceError: Error #1056: Cannot create property __rslLoaders on Other.
at fl.rsl::RSLPreloader/contentComplete()

The Loader you use to load your swf fires its COMPLETE event before Adobe's Preloader is finished loading your swf. You cannot listen to Adobe's preloader to know when your swf has actually been loaded and is available, and, as far as I can tell, Adobe's Preloader provides no way to target your swf.

The unfortunate side effect of this is that using TLF makes it difficult to target the timeline of a loaded swf without jumping through some hoops.

Because you cannot bubble events from within the other.swf through the Loader that loaded it to the Main class, your loading class has no way of knowing when your loaded swf is actually available unless you do an ENTER_FRAME listener and loop through the children of MovieClip(loader.content) and look for your interface class (as mentioned above, you cannot cast as the document class or you get a runtime reference error, and you cannot change the ApplicationDomain or the SecurityDomain of Adobe's Preloader's Loader). Then, you need to store a reference to your swf to be able to access it, which means you have to be sure to clear that reference when unloading.

private var otherSWF:ICustom;

private function onLoadComplete(event:Event):void
{
	addEventListener(Event.ENTER_FRAME, onFrame);
}
private function onFrame(event:Event):void
{
	var i:int = MovieClip(loader.content).numChildren;
	while (i--)
	{
		if (MovieClip(loader.content).getChildAt(i) as ICustom != null)
		{
			otherSWF = MovieClip(loader.content).getChildAt(i) as ICustom;
			removeEventListener(Event.ENTER_FRAME, onFrame);
			break;
		}
	}
}



In my trivial example running on my local machine, it takes 5 frames before my loaded swf is available.

Posted in Adobe, AS3, Bugs, Flash, Flash CS5 having 42 comments �

Apple's Behavior Shouldn't Be A Surprise To Anyone…

May 3rd, 2010 by Steven Sacks

Read the following and consider its accuracy at analyzing the recent actions and tone Apple has taken.

Apple has always insisted on having a hardware monopoly, except for a brief period in the mid-1990s when they allowed clone-makers to compete with them, before subsequently putting them out of business. Macintosh hardware was, consequently, expensive. You didn't open it up and fool around with it because doing so would void the warranty. In fact the first Mac was specifically designed to be difficult to open–you needed a kit of exotic tools, which you could buy through little ads that began to appear in the back pages of magazines a few months after the Mac came out on the market. These ads always had a certain disreputable air about them, like pitches for lock-picking tools in the backs of lurid detective magazines.

This monopolistic policy can be explained in at least three different ways.

THE CHARITABLE EXPLANATION is that the hardware monopoly policy reflected a drive on Apple's part to provide a seamless, unified blending of hardware, operating system, and software. There is something to this. It is hard enough to make an OS that works well on one specific piece of hardware, designed and tested by engineers who work down the hallway from you, in the same company. Making an OS to work on arbitrary pieces of hardware, cranked out by rabidly entrepeneurial clonemakers on the other side of the International Date Line, is very difficult, and accounts for much of the troubles people have using Windows.

THE FINANCIAL EXPLANATION is that Apple, unlike Microsoft, is and always has been a hardware company. It simply depends on revenue from selling hardware, and cannot exist without it.

THE NOT-SO-CHARITABLE EXPLANATION has to do with Apple's corporate culture, which is rooted in Bay Area Baby Boomdom.

Now, since I'm going to talk for a moment about culture, full disclosure is probably in order, to protect myself against allegations of conflict of interest and ethical turpitude: (1) Geographically I am a Seattleite, of a Saturnine temperament, and inclined to take a sour view of the Dionysian Bay Area, just as they tend to be annoyed and appalled by us. (2) Chronologically I am a post-Baby Boomer. I feel that way, at least, because I never experienced the fun and exciting parts of the whole Boomer scene–just spent a lot of time dutifully chuckling at Boomers' maddeningly pointless anecdotes about just how stoned they got on various occasions, and politely fielding their assertions about how great their music was. But even from this remove it was possible to glean certain patterns, and one that recurred as regularly as an urban legend was the one about how someone would move into a commune populated by sandal-wearing, peace-sign flashing flower children, and eventually discover that, underneath this facade, the guys who ran it were actually control freaks; and that, as living in a commune, where much lip service was paid to ideals of peace, love and harmony, had deprived them of normal, socially approved outlets for their control-freakdom, it tended to come out in other, invariably more sinister, ways.

Applying this to the case of Apple Computer will be left as an exercise for the reader, and not a very difficult exercise.

It is a bit unsettling, at first, to think of Apple as a control freak, because it is completely at odds with their corporate image. Weren't these the guys who aired the famous Super Bowl ads showing suited, blindfolded executives marching like lemmings off a cliff? Isn't this the company that even now runs ads picturing the Dalai Lama (except in Hong Kong) and Einstein and other offbeat rebels?

It is indeed the same company, and the fact that they have been able to plant this image of themselves as creative and rebellious free-thinkers in the minds of so many intelligent and media-hardened skeptics really gives one pause. It is testimony to the insidious power of expensive slick ad campaigns and, perhaps, to a certain amount of wishful thinking in the minds of people who fall for them. It also raises the question of why Microsoft is so bad at PR, when the history of Apple demonstrates that, by writing large checks to good ad agencies, you can plant a corporate image in the minds of intelligent people that is completely at odds with reality. (The answer, for people who don't like Damoclean questions, is that since Microsoft has won the hearts and minds of the silent majority–the bourgeoisie–they don't give a damn about having a slick image, any more then Dick Nixon did. "I want to believe,"–the mantra that Fox Mulder has pinned to his office wall in The X-Files–applies in different ways to these two companies; Mac partisans want to believe in the image of Apple purveyed in those ads, and in the notion that Macs are somehow fundamentally different from other computers, while Windows people want to believe that they are getting something for their money, engaging in a respectable business transaction).

All done? This is an unmodified excerpt from Neal Stephenson's essay "In the Beginning…Was the Command Line". It was written in 1999. Before OSX.

Surprised?

You can read the entirety here. It's a fantastic read, even today.

Posted in Apple, Technology having no comments �

Setting up FDT to look and behave like FlashDevelop

April 30th, 2010 by Steven Sacks

If you are moving to FDT from FlashDevelop and want FDT to look and behave more closely to FlashDevelop, here's what you need to do.

Open Window > Preferences.

Under FDT > Editor > Code Assist copy and paste this into Auto activation triggers for AS, and set the delay to 0ms.

abcdefghijklmnopqrstuvwxyz_. :



Under Editor: Colors and Editor > > Semantic Highlight you're going to set the syntax colors. This is a bit involved. What you should do on Windows is create a custom color in the palette for each of these.

Here are the RGB values for each of the items you need to change under Colors. You should turn off Bold/Italic for all these items if you want to match FlashDevelop's defaults.

Keywords and all Keyword 'types': 21, 24, 255
Base Types 'Void': 0, 128, 128
Core Types from MM Classes Root: 0, 128, 128
Constants: 0, 0, 153
Single/Multi Line Comment: 0, 128, 0
SingleMulti Line Todo: 0, 128, 0
JavaDoc: 128, 0, 0
Strings/Characters: 163, 21, 21

And here are the RGB values for each of the items you need to change under Semantic Highlight (Click on the AS3 tab).

Class: 0, 128, 128
Field: 0, 0, 155
Function: 0, 0, 0
Getter: 0, 0, 0
Regular expression: 255, 0, 255
Setter: 0, 0, 0
Static field: 0, 0, 0
Static function: 0, 0, 0
Static getter: 0, 0, 0
Static getter: 0, 0, 0
Top level function: 0, 128, 128
Top level namespace: 0, 128, 128
Top level variable: 0, 128, 128



Under Problems > AS3 Problems:
Unresolvable > Unresolvable variable reference in E4X and Unresolvable member reference in dynamic object should both be set to Disabled.

If you're on Mac you can skip this step. Windows users should definitely do this. Under General > Keys find Quick Fix (type it into the top input box), click on it (should be bound to CTRL+1). You can add CTRL+SHIFT+1 if you want to make it like FlashDevelop, but I would leave it alone. However, I highly recommend adding ALT+1 or ALT+2. It's a more natural position for your hand to be in.

Apply all your changes and return to the FDT IDE.

On the left you should see the Flash Explorer panel. Drag that entire block, not just the panel tab but the entire block, to the right edge of the application. This will mimic the Project tab's position in FlashDevelop. I would take the Outline and ANT tabs from the other block and add them to the same block as Flash Explorer. I closed the ANT tab since I don't use it often enough to warrant it being always visible.

That's basically it. The rest is all up to personal style.

Posted in AS3, FlashDevelop, Tips/Tricks, Workflow having 5 comments �

Socket.timeout property doesn't work

March 27th, 2010 by Steven Sacks

AS3's Socket class has a public property called timeout, and it doesn't work.

The default value of timeout is 20000 milliseconds. Setting timeout to any other value doesn't change the 20000 millisecond timeout.

You can confirm this by attempting to connect to a Socket that doesn't respond. It will always take 20000 milliseconds for it to timeout, even if you set the timeout to a much smaller value, say 1000 or 5000.

Here is the workaround:

1. Make your own Timer and listen for TimerEvent.TIMER_COMPLETE.
2. Start the timer as soon as you call connect().
3. In the Event.CONNECT listener, reset the timer.
4. If the TIMER_COMPLETE event fires first, close the socket.

Posted in AS3, Bugs having 1 comment �

Adobe AIR Non-Transparent Window Bugs Part 3: Native Controls Showing Up

February 9th, 2010 by Steven Sacks

UPDATE:
This has been fixed in AIR 2.5.

In my previous post, I showed how sometimes Windows would draw native controls on top of your non-transparent windows, as pictured here:

Windows XP

However, I could not figure out a sure way to reproduce it. Now I have.

Here's how you do it:

1. Make a new AIR application in Flex Builder.

2. In your-app.xml, set systemChrome to none and visible to true.

3. Prevent MINIMIZE and MAXIMIZE in your WindowedApplication:

<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()">
    <mx:Script>
        <![CDATA[
            private function init():void
            {
             nativeWindow.addEventListener(NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING, onDisplayStateChanging);
            }
            private function onDisplayStateChanging(event:NativeWindowDisplayStateEvent):void
            {
                if (event.afterDisplayState == NativeWindowDisplayState.MINIMIZED || event.afterDisplayState == NativeWindowDisplayState.MAXIMIZED)
                {
                    event.preventDefault();
                }
            }
        ]]>
    </mx:Script>
</mx:WindowedApplication>

4. Launch the app.

5. Click twice on the taskbar button (should do nothing).

6. Right-click on the taskbar button. Windows native system controls appear.

This is just a reliable way to make it happen every time. You don't have to do this exact thing to make them show up, they show up on their own randomly. I hope this example makes it easy for the AIR team to debug this issue.

I've exported the sample project with the above code. Click here to download.

Posted in Adobe, AIR, Bugs having 3 comments �

Adobe AIR Non-Transparent Window Bugs Part 2: Maximize

February 4th, 2010 by Steven Sacks

Maximized AIR windows on Windows XP are positioned at -4,-4 and the width and height add 8 pixels to compensate. On Windows 7, this is doubled, so the position is -8,-8 and the width and height add 16 pixels to compensate. This works fine with transparent AIR windows.

Note: This behavior is not documented, and I hope they add it to the AIR documentation.

However, when you maximize a non-transparent non-system chrome window in AIR, the window is drawn 1 pixel in on all edges, so you can see the desktop as a 1 pixel border around the window. Additionally, the window is positioned at 1,1 instead of -4,-4 in Windows XP and -8,-8 in Windows 7, and the window is cropped to 1 pixel less than the visible bounds on width and height, which means 10 pixels are being cropped in Windows XP and 18 pixels are being cropped in Windows 7.

Here's a screenshot of what the window top and bottom look like when normal:
Normal AIR Window
Normal AIR Window Bottom

And this is what it looks like when it's maximized (I cropped out the middle to fit the blog):
Maximized AIR Window
Maximized AIR Window Bottom

As you can see from the maximized image, the window is incorrectly rendered. There are two primary issues.

The NativeWindowBoundsEvent says it's positioned at -4,-4 / -8,-8, but it's actually positioned at 1,1.

The NativeWindowBoundsEvent width and height are correct at +8 for Windows XP and +16 for Windows 7. But, because the window is cropped at 2 pixels less width and height than the window is drawn at, on Windows XP the window is cropped by 10 pixels, and on Windows 7 the window is cropped by 18 pixels.

Here are screenshots where you can see the 1 pixel border on Windows XP and Windows 7. I made the desktop bright red (#FF0000) so you can clearly see the borders. There's also another bug with Windows XP that I'll go into below.

Windows XP
Windows XP

Windows 7
Windows 7

If you look at the Windows XP one, you can see that native window controls are being drawn (this happens in Windows 7, as well). This happens sometimes when you maximize from a normal state on a non-transparent window. There's nothing reliable that causes it, but it does happen sometimes. This is the first time I've been able to take a screenshot of it. What's weird is rolling over the graphics in the window hides the controls but you can still click on them. What's weirder is that if you click on the native control to restore the window (go back to normal), AIR doesn't fire a state change event!

———————————————-
Update: This has been fixed in AIR 2.5

There's one more issue with non-transparent AIR windows. It's the same bug that the CS4 suite of software has when the windows are maximized. When you have two monitors and your CS4 application is maximized (like Flash or Photoshop) and you drag a window across the right edge of the application, you see ugly ghosting of the window you're dragging.

Windows XP Edge Bug

See all that ugly blue? That was me dragging the FlexBuilder window around. The blue is the color of the title bar of the native window. Any window will cause that, though. On AIR, the problem is even worse because that 1 pixel border on the top and bottom also suffer from the ghosting.

I don't know why, but CS4 and AIR both have the same exact issue.

Here's what it looks like in Flash CS4 and Photoshop CS4:

Flash CS4
Windows XP Edge Bug

Photoshop CS4
Windows XP Edge Bug

The ghosting goes away if you change state, like minimizing then restoring or clicking the restore button. This only happens when the window is maximized.

Please, please, please fix this in AIR (and CS5)!

Added to Adobe Bugs on Feb 8th

Update: Workaround

While there's no way to deal with the 1 pixel border or the ugly ghosting that happens, I have figured out a straightforward workaround for the resize. Because you cannot move Mac windows to negative y positions (AFAIK), this if statement will only happen on windows.

private function onResize(event:NativeWindowBoundsEvent):void
{
	var offset:int;
	if ((event.afterBounds.x == -4 && event.afterBounds.y == -4) || (event.afterBounds.x == -8 && event.afterBounds.y == -8)) offset = event.afterBounds.x * -2;
	width = event.afterBounds.width - offset;
	height = event.afterBounds.height - offset;
}

Posted in Adobe, AIR, Bugs having 11 comments �

Adobe AIR has bad redraw bugs on Windows

January 27th, 2010 by Steven Sacks

In Adobe AIR, I recently had to change from using a transparent window to a non-transparent one, and I've never really worked with non-transparent windows without system chrome. Doing so has created a very painful situation due to AIR's redraw bugs on Windows when using non-transparent windows.

Transparency performance on Windows is bad, but it's not Adobe's fault. Windows has bad transparent window performance. However, on Windows, there are major redraw issues with AIR on a non-transparent window, and none of these issues are present in non-AIR applications.

When you minimize a window in AIR, it resizes the application to 160×31, and moves it to -32000, -32000. I can understand the move, but the resize doesn't make sense to me. Especially considering the minSize of the Window is much larger than that.

Because of the resize to 160×31, when you restore the app from a minimized state it resizes the application from that small size back to the size it was before you minimized. On transparent windows it resizes before you can see it, which is why I never saw this before. But, on a non-transparent window, you can actually see your application redraw from 160×31 to the size it was before you minimized. This looks terrible.

What happens whenever you change the window state from Minimized to Normal or from Normal to Maximized, or Maximized to Normal, the move happens first, then, about 100ms later (though getTimer() claims they happen in the same millisecond), the resize happens. This is an clearly visible to the user, and really doesn't look good.

Additionally, on this specific project, when AIR resizes my window to 160×31, it causes all kinds of negative issues with elements that are not meant to draw to that small of a dimension (I set the minSize of the window for a reason). Because of the delay in resizing after the move as outlined above, these issues are visible for a moment when you restore from a minimized state, but sometimes, elements inside an HTMLLoader do not resize correctly unless I resize the application by 1 pixel after restoring.

The best I can do is in the RESIZE listeners check to see if the afterBounds is 160×31 and ignore it if so. So, I do the check for 160×31 in my onResize listener, ignoring the resize if those are the afterBounds values, and that mitigates the issue except for a couple important exceptions.

First, you still see the two step redraw occur, except that since the application isn't resized smaller, it just looks like the application is masked for that split second. The second issue is that I have an HTMLLoader playing a swf with wmode="opaque" inside it and what happens on redraw is that even though the application is "masked" by the smaller window size (since I'm ignoring the resize), the swf in the HTMLLoader is visible beyond the bounds of the NativeWindow 160×31 size until the resize occurs after the move. This isn't the worst thing in the world, but it's not that great either. It's definitely better than the visual consequences of redrawing the application to 160×31.

Another example of this move-wait-resize behavior is when you maximize a non-transparent AIR window. First, it moves the window to -4,-4 (-8,-8 on Windows 7) and then resizes the window to fill the screen. Because of the delay, you see maximize happen in two steps. You see the window jump up to the top left corner first and then you see it resize to fill the screen. This doesn't happen with transparent windows and it doesn't happen with any other application on Windows.

It's really disappointing that something so basic is so buggy. I don't like that AIR resizes the window to 160×31 when you minimize, and I really wish they could move and resize non-transparent windows at the same time since other applications on Windows work that way.

As it is, I'm stuck trying to work around these issues and mitigate them as much as possible. I'm still hoping our commercial application will be a shining example of the kinds of things you can make with AIR, despite AIR's flaws. Most of the bad rap AIR gets is undeserved since most of the time, it is developers not knowing how to manage memory and other such nonsense. But in cases like this, it's something fundamental and specific to how the window draws on the most common target platform, Windows, and is completely out of my control.

All I can do is mitigate the issues as best I can, and write blog posts outlining the problems in the hops that the AIR engineering team will fix these issues in a future release.

Added to Adobe Bugs on Feb 8th

Posted in AIR, Bugs having 7 comments �

About Steven Sacks

I am a professional Flash developer with over 13 years of programming experience. I have consulted for high-profile agencies and companies in San Francisco, Los Angeles, Atlanta and New York, and developed numerous award-winning websites and rich internet applications for clients including Adobe, Fox Sports, FX Networks, Anheuser-Busch, GE, DirecTV, ESPN, The Weather Channel, Home Depot, and Coca-Cola.

I am the author of the open-source Gaia Framework for Adobe Flash, which dramatically reduces development time and makes developing Flash sites much easier.