Start Google Chrome with local file access on Mac OS X

Debugging local contents with Google Chrome on Mac OS X can be hard since local file access is disabled by default.

Enabling local file access is a simple terminal command. So just open your terminal anywhere, make sure Google Chrome is currently not running, copy paste this line and hit enter:

open /Applications/Google\ Chrome.app --args --allow-file-access-from-files

Using Google Chrome Canary is also possible:

open /Applications/Google\ Chrome\ Canary.app --args --allow-file-access-from-files

[Update]
File XMLHttpRequest can also be enabled in Opera visiting opera:config#UserPrefs|AllowFileXMLHttpRequest

Publish Flash layers to single SWFs

In case you need too, this little Flash IDE script will let you publish every single Flash timeline layer as a separate SWF file. Just use unique and URL save layer names and you will get all your SWFs dynamically created as flaFilename + layerName + ".swf".

Just save this little code as a .jsfl file and open it with the Flash IDE:

function filterLayers( timeline, layerName )
{
	var layers = timeline.layers,
		layer,
		c,
		i;
 
	for ( i = layers.length - 1; 0 < i; i-- )
	{
		layer = layers[ i ];
 
		if ( layer && layer.layerType !== "folder" && layer.name !== layerName )
		{
			timeline.deleteLayer( i );
		}
	}
}
 
function publishNewFile( fileURL, layerName )
{
    var document = fl.openDocument( fileURL );
    if ( document )
    {
		var documentDOM = fl.getDocumentDOM();
		if ( documentDOM )
		{
			var timeline = documentDOM.getTimeline();
			if ( timeline )
			{
				filterLayers( timeline, layerName );
			}
		}
 
		document.publish();
		document.save();
		document.close();
    }
}
 
function layerToSWF( layerName, fileURL, fileName, parentDirURL )
{
	var newFileURL = parentDirURL + layerName + ".fla";
 
	// create new file as copy
	var success = FLfile.copy( fileURL, newFileURL );
	if ( success )
	{
		// publish new file
		publishNewFile( newFileURL, layerName );
		// remove new file (but leave SWF).
		FLfile.remove( newFileURL );
	}
	else
	{
		alert( "Copy from\n" + fileURL +"\nto\n" + newFileURL + "\nfailed." );
	}
 
	return success;
}
 
function timelineToSWFs( timeline, fileURL, fileName, parentDirURL )
{
	var layers = timeline.layers,
		layer,
		i = 0;
		c = layers.length;
 
	for ( i; i < c; i++ )
	{
		layer = layers[ i ];
 
		if ( layer && layer.layerType === "normal" && layer.name )
		{
			if ( ! layerToSWF( layer.name, fileURL, fileName, parentDirURL ) )
			{
				break;
			}
		}
	}
}
 
function layersToSWFs()
{
	var fileURL = fl.browseForFileURL( "open" );
	if ( fileURL )
	{
		var document = fl.openDocument( fileURL );
		if ( document )
		{
			var documentDOM = fl.getDocumentDOM();
			if ( documentDOM )
			{
				var timeline = documentDOM.getTimeline();
				if ( timeline )
				{
					var parentDirURL = fileURL.split( "/" );
 
					var fileName = parentDirURL.pop();
						fileName = fileName.split( "." );
						fileName.pop();
						fileName = fileName.join( "." );
 
					parentDirURL = parentDirURL.join( "/" ) + "/" + fileName;
 
					timelineToSWFs( timeline, fileURL, fileName, parentDirURL );
				}
			}
		}
	}
}
 
// http://blog.derRaab.com - saves you some time - get yourself some fresh air! ;)
layersToSWFs();

Have a nice day!

Supersized Apps with AIR on iOS

One of our latest customer projects was mainly intended as an offline iPad app but should also ship on USB-sticks and run in browsers for all desktops. A perfect fit for Adobe AIR. Read here what we’ve learned.

Adobe AIR is really a powerful tool when it comes to projects for multiple target devices. We’ve already done a couple of projects based on our data driven ActionScript framework which runs nicely in any browser supporting the latest versions of Flash player, as standalone player for DVDs and USB-sticks and within the desktop version of AIR.

We spent a lot of time improving the iPad version to ensure good render performance and a really low memory footprint.

The project I’m talking about uses loads of assets, currently almost 2 GB of bitmap and video files (will grow) and 6000+ unique “slide” XML files with the actual content. Everything loads fast and smoothly with the browser and even faster on desktops and of course iOS.

Development

Development was pretty straight forward since we did most of the debugging with the local AIR application which is fast and easy to profile. After big milestones we also packaged the iPad version and tested directly on the device. This post is not about how you do this step effectively but keep in mind that thousands of files means long packaging time with ADT and takes XCode a long time pushing the app onto the iPad. Upgrading to a new SSD dropped packaging time significantly so using a really fast drive definitely pays of!

2 GB maximum size

With our apple development certificate it was no problem to install the app on all iPads, but it simply wasn’t possible with the customers release certificates. We figured out that there’s a size restriction for iPad apps which allows only 2 GB but we already had 2.2 GB. Boom. Luckily we where able to encode all the videos again and save almost 500 MB so we could ship for now.

Delivery

The customer uses an enterprise solution for delivering iPad apps to all it’s employees and it turned out that the technical provider restricts the maximum app size to less then 200 MB. Boom again.

AIR on iOS FileStream bug

So we implemented a rock solid update process which initially loads almost 1.5 GB of files but never completed on the iPad. The app crashed hard but was still open, crashed again hard after restart but completed the download process after another restart. We had a hard time tracking this issue down to it’s real roots and finally catched this error: 3005 - Insufficient system resources. Google found this Adobe forum thread about it which contained at least a workaround found by Jason Moore:

We did find a workaround , but using open() instead of openAsync().. everything then seems to function correctly.

Please vote for this AIR on iOS bug, it’s from 2011 but still open:
https://bugbase.adobe.com/index.cfm?event=bug&id=3077653

So if you struggle deciding whether AIR is a good fit for your big app I definitely say yes, but hopefully this post helps a little to ask the right questions and avoids running into the same issues.

Updating my MacBook Pro from HDD to Crucial m4 SSD using Time Machine and Mac OS X Lion

Finally I upgraded my Mid 2010 MacBook Pro with a nice Crucial m4 SSD with 512GB. I was thinking about changing my hard drive for month but now the price dropped from 600 to 400 EUR and I went for it.

It took a little while to collect relevant informations so I want to share a workflow:

  1. Backup your complete system with time machine!
  2. Swap your drives (ifixit.com) without touching your battery.
  3. Check if your SSD runs the newest firmware and upgrade if necessary.
  4. Boot from your OS X Lion recovery partition (hold ALT key on Mac startup).
  5. Use the disk utility to restore from time machine.
  6. Go outside or hang out with friends or family!
  7. Enable trim support in OS X Lion for your SSD.

Done.

Since a couple of days it really makes fun using my MacBook Pro again. It dropped packaging time for a 1,5 GB big IPA file with ADT from 12 to almost 6 minutes. This is a huge benefit, so if you’re still using normal HDDs for daily work I recommend switching to SSD as soon as possible!

UPDATE – added xbench results:

Name Score Detail
System Info    
  Xbench Version   1.3
  System Version   10.7.4 (11E53)
  Physical RAM   8192 MB
  Model   MacBookPro6,2
  Drive Type   M4-CT512M4SSD2
Disk Test 409.65  
Sequential 253.88  
  Uncached Write 405.78 249.14 MB/sec [4K blocks]
  Uncached Write 324.52 183.61 MB/sec [256K blocks]
  Uncached Read 128.80 37.69 MB/sec [4K blocks]
  Uncached Read 408.86 205.49 MB/sec [256K blocks]
Random 1060.10  
  Uncached Write 1452.92 153.81 MB/sec [4K blocks]
  Uncached Write 608.33 194.75 MB/sec [256K blocks]
  Uncached Read 2231.07 15.81 MB/sec [4K blocks]
  Uncached Read 1007.15 186.88 MB/sec [256K blocks]

ActionScript performance test for Array, Object, Vector literals and Array.push, Vector.push methods

Yesterday a “tweet” pointed me to a nice article about ActionScript 3.0 optimization. Most of these techniques are quite common within the ActionScript developer scene but one thing caught my attention: A link to Jackson Dunstans blog post about runtime performance and the const and final keywords. Since I always tried to use at least final classes I was a little disappointed that my extra work time doesn’t have any performance benefit at runtime.

Due to that circumstance I was curious about some other common techniques like “Use Object and Array Literals Whenever Possible” or “Add Elements to the End of an Array Without Pushing”. I wanted to check performance benefits for myself just to make sure it’s worth the effort. So I wrote a little test script:

var result : String = "Test playerType: " + Capabilities.playerType + " version: " + Capabilities.version + "\n",
	i : int = 0,
	c : int = 1000000,
	array : Array,
	object : Object,
	vectorInt : Vector.<int>,
	startTime : int;
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { object = new Object(); }
result += c + " times: object = new Object()             -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { object = {}; }
result += c + " times: object = {}                       -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { array = new Array(); }
result += c + " times: array = new Array()               -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { array = []; }
result += c + " times: array = []                        -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { vectorInt = new Vector.<int>(); }
result += c + " times: vectorInt = new Vector.<int>()    -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { vectorInt = new <int>[]; }
result += c + " times: vectorInt = new <int>[]           -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { array.push( i ); }
result += c + " times: array.push( i )                   -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { array[ array.length ] = i; }
result += c + " times: array[ array.length ] = i         -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { vectorInt.push( i ); }
result += c + " times: vectorInt.push( i )               -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { vectorInt[ vectorInt.length ] = i; }
result += c + " times: vectorInt[ vectorInt.length ] = i -> duration: " + String( getTimer() - startTime ) + "\n";
 
var textFormat : TextFormat = new TextFormat();
	textFormat.font = "Courier";
	textFormat.size = 14;
 
var textField : TextField = new TextField();
	textField.autoSize = TextFieldAutoSize.LEFT;
	textField.background = true;
	textField.backgroundColor = 0xFFFFFF;
	textField.defaultTextFormat = textFormat;
	textField.multiline = true;
	textField.text = result;
 
addChild( textField );

It turns out that the benefits differ greatly on platform and player type but basically it’s always a good idea to avoid using the .push methods and new keyword. While there’s almost no speed difference in Flash Players and Plug-ins, the debug versions are really slow. And check out the blazing fast object creation with AIR on iOS! Or is this a compiler optimization? Here my results:


Flash CS 5 publish
Test playerType: External version: MAC 10,1,52,14

1000000 times: object = new Object() -> duration: 246
1000000 times: object = {} -> duration: 427
1000000 times: array = new Array() -> duration: 1288
1000000 times: array = [] -> duration: 339
1000000 times: vectorInt = new Vector.() -> duration: 586
1000000 times: vectorInt = new [] -> duration: 596
1000000 times: array.push( i ) -> duration: 134
1000000 times: array[ array.length ] = i -> duration: 137
1000000 times: vectorInt.push( i ) -> duration: 113
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 114


Flash Player Debugger.app
Test playerType: StandAlone version: MAC 11,1,102,62

1000000 times: object = new Object() -> duration: 179
1000000 times: object = {} -> duration: 311
1000000 times: array = new Array() -> duration: 1072
1000000 times: array = [] -> duration: 313
1000000 times: vectorInt = new Vector.() -> duration: 397
1000000 times: vectorInt = new [] -> duration: 402
1000000 times: array.push( i ) -> duration: 118
1000000 times: array[ array.length ] = i -> duration: 108
1000000 times: vectorInt.push( i ) -> duration: 111
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 89


Flash Player.app
Test playerType: StandAlone version: MAC 11,1,102,62

1000000 times: object = new Object() -> duration: 127
1000000 times: object = {} -> duration: 183
1000000 times: array = new Array() -> duration: 227
1000000 times: array = [] -> duration: 189
1000000 times: vectorInt = new Vector.() -> duration: 215
1000000 times: vectorInt = new [] -> duration: 218
1000000 times: array.push( i ) -> duration: 72
1000000 times: array[ array.length ] = i -> duration: 62
1000000 times: vectorInt.push( i ) -> duration: 61
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 42


Browser Debug Plug-in (Safari)
Test playerType: PlugIn version: MAC 11,1,102,62

1000000 times: object = new Object() -> duration: 157
1000000 times: object = {} -> duration: 249
1000000 times: array = new Array() -> duration: 1015
1000000 times: array = [] -> duration: 293
1000000 times: vectorInt = new Vector.() -> duration: 281
1000000 times: vectorInt = new [] -> duration: 282
1000000 times: array.push( i ) -> duration: 124
1000000 times: array[ array.length ] = i -> duration: 117
1000000 times: vectorInt.push( i ) -> duration: 124
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 94


Browser Plug-in (Google Chrome)
Test playerType: PlugIn version: MAC 11,2,202,235

1000000 times: object = new Object() -> duration: 147
1000000 times: object = {} -> duration: 146
1000000 times: array = new Array() -> duration: 237
1000000 times: array = [] -> duration: 224
1000000 times: vectorInt = new Vector.() -> duration: 257
1000000 times: vectorInt = new [] -> duration: 266
1000000 times: array.push( i ) -> duration: 54
1000000 times: array[ array.length ] = i -> duration: 61
1000000 times: vectorInt.push( i ) -> duration: 70
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 51


AIR SDK 3.2
Test playerType: Desktop version: MAC 11,2,202,223

1000000 times: object = new Object() -> duration: 274
1000000 times: object = {} -> duration: 243
1000000 times: array = new Array() -> duration: 1344
1000000 times: array = [] -> duration: 379
1000000 times: vectorInt = new Vector.() -> duration: 431
1000000 times: vectorInt = new [] -> duration: 426
1000000 times: array.push( i ) -> duration: 135
1000000 times: array[ array.length ] = i -> duration: 136
1000000 times: vectorInt.push( i ) -> duration: 153
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 74


iOS (iPad 3) debug interpreter
Test playerType: StandAlone version: MAC 11,1,102,62

1000000 times: object = new Object() -> duration: 4707
1000000 times: object = {} -> duration: 2033
1000000 times: array = new Array() -> duration: 22191
1000000 times: array = [] -> duration: 2993
1000000 times: vectorInt = new Vector.() -> duration: 10235
1000000 times: vectorInt = new [] -> duration: 10156
1000000 times: array.push( i ) -> duration: 1728
1000000 times: array[ array.length ] = i -> duration: 1670
1000000 times: vectorInt.push( i ) -> duration: 2056
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 1951


iOS (iPad 3) ad-hoc
Test playerType: Desktop version: IOS 11,2,202,223

1000000 times: object = new Object() -> duration: 1224
1000000 times: object = {} -> duration: 0 !!!
1000000 times: array = new Array() -> duration: 5886
1000000 times: array = [] -> duration: 1048
1000000 times: vectorInt = new Vector.() -> duration: 2737
1000000 times: vectorInt = new [] -> duration: 2784
1000000 times: array.push( i ) -> duration: 2043
1000000 times: array[ array.length ] = i -> duration: 234
1000000 times: vectorInt.push( i ) -> duration: 1524
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 643

Always worth reading: http://gskinner.com/talks/quick/

My little Flash library cleanup script

Just save this little code as a .jsfl file:

function editItem( lib, item )
{
	var success = true,
		linkageIdentifier = item.linkageIdentifier,
		itemType = item.itemType,
		path = [];
 
	// separate export symbols from assets
	path.push( ( linkageIdentifier ) ? "_export" : "_asset" );
 
	// don't touch existing folders
	if ( itemType !== "folder" )
	{
		// separate different item types
		path.push( itemType );
 
		success = libItemMoveToFolder( lib, path.join( "/" ), item, false );
	}
 
	return success;
}
 
function libItemMoveToFolder( lib, path, item, replace )
{
	fl.outputPanel.trace( "libItemMoveToFolder( " + lib + ", " + path + ", " + item.name + ", " + replace + " ) CALL" );  
	var folderCreated = lib.newFolder( path ),
		success = false;
 
	if ( folderCreated )
	{
		// Can't move symbol names with empty space
		if ( item.name.indexOf( " " ) )
		{
			// But even this doesn't work - so avoid spaces in item names!
			item.name = item.name.split( " " ).join( "" );
		}
		success = lib.moveToFolder( path, item.name, replace );
		if ( ! success )
		{
			fl.outputPanel.trace( "libItemMoveToFolder( " + lib + ", " + path + ", " + item.name + ", " + replace + " ) Error Can't move item!" );  
		}
	}
	else
	{
		fl.outputPanel.trace( "libItemMoveToFolder( " + lib + ", " + path + ", " + item.name + ", " + replace + " ) Error Can't create folder!" );  
	}
 
	return success;
}
 
function iterateAllItems( lib )
{
	var items = lib.items,
		item,
		c = items.length,
		i = 0,
		success;
 
	for ( i; i < c; i++ )
	{
		item = items[ i ];
 
		if ( item )
		{
			success = editItem( lib, item )
			if ( ! success )
			{
				break;
			}
		}
	}
}
 
// Select .fla
var avatarFLASource = fl.browseForFileURL( "open");
if ( avatarFLASource )
{
	// Open .fla
	var doc = fl.openDocument( avatarFLASource );
	if ( doc )
	{
		var lib = doc.library;
		if ( lib )
		{
			iterateAllItems( lib );
		}
	}
}

If you use empty spaces within your item names these items won’t move at all. I was to busy fixing this issue but maybe you know how to solve this little problem?

Avoid iFrame content clipping with CSS transform on iOS

The HTML(5) version of our SCORM based e-learning runtime heavily uses i-frames as template containers. This architecture gives a lot of flexibility but since customers mainly request iPad support we where very unhappy with Safari’s rendering capabilities on iOS. Of course we use CSS transform to get the best animation performance on mobile devices, but in Apple’s mobile browser you’ll likely see huge clipping rectangles during hardware accelerated transitions.

There was not much information about that issue online but at least this entry on stackoverflow suggested there is not much you can do. Well, it took a lot of trail an error testing time but finally we nailed it. The solution is quite simple:

Whenever you use CSS transform with i-frames or it’s parents you also need to apply a basic CSS transform to the body tag of your i-frame content. That’s it. You can also use other contents within the loaded frame HTML page but the body tag seems to be the perfect target.

So this is a little JavaScript code snipped:

// Reference to your iFrame HTML element
var iFrameElement = document.getElementById( "yourIFrameID" );
 
// Avoid clipping by applying simple css transfrom3D to the iframe content itself
$( iFrameElement.contentWindow.document.body ).css( "-webkit-transform", "translate3d(0,0,0)" );
 
// Start your iFrame transition (from a visible position)
$( iFrameElement ).css( "-webkit-transform", "translate3d(-1000px,0,0)" );

UPDATE: Make sure you start from a visible position!

Don’t forget to remove CSS transform after your animation is done:

// Don't forget to reset after your transition has finished!
$( iFrameElement.contentWindow.document.body ).css( "-webkit-transform", "none" );

I suggest to use “none” as the default value. We sometimes experienced weird behavior using an empty string…

Have fun coding!

Removing comments in CSS, HTML and ECMAScript (JavaScript)

While working on an Adobe AIR based source code editor I was looking for an easy way to remove comments from different kind of source codes. What first seemed like an easy regular expression turned out to be much more complex because comment pre- and suffixes can also occur in string literals or regular expressions and our old friend Internet Explorer allows HTML conditional comments and JavaScript conditional compilation. Great.

First I had a look at YUICompressors source code and Google Page Speed but then I decided to port a script for removing JavaScript comments created by James Padolsey.

I just improved the recognition for regular expressions a little and since this code should work with all ECMAScript based source codes I named my class ECMAScriptParser instead of JavaScriptParser:

package de.superclass.parser
{
	/**
	 * @author Markus Raab (superclass.de | blog.derRaab.com)
	 */
	public class ECMAScriptParser
	{
		/**
		 * Removes inline and block comments from an ECMAScript source string. 
		 * 
		 * This is a port of James Padolsey	 with an slightly improved RegExp recognition.
		 * @see: http://james.padolsey.com/javascript/removing-comments-in-javascript
		 * 
		 * @param ecmaScript		ECMAScript source string
		 * @param removeCondComp	Optional (default=false) - Whether to remove Internet Explorers JavasScript conditional compilation comments or not.   
		 * @return					ECMAScript source string without comments
		 */
		public static function removeComments( ecmaScript : String, removeCondComp : Boolean = false ) : String
		{
			var modeSingleQuote		: Boolean = false;
			var modeDoubleQuote		: Boolean = false;
			var modeRegExp			: Boolean = false;
			var modeBlockComment	: Boolean = false;
			var modeLineComment		: Boolean = false;
			var modeCondComp		: Boolean = false; 
 
			var vector : Vector.<String> = Vector.<String>( ( '__' + ecmaScript + '__' ).split( '' ) );
				vector.fixed = true;
 
			var c : int = vector.length;
 
			for ( var i : int = 0; i < c; i++ )
			{
				var string : String = vector[ i ];
 
				if ( modeRegExp )
				{
					if ( string === '/' && vector[ i - 1 ] !== '\\' )
					{
						modeRegExp = false;
					}
					continue;
				}
 
				if ( modeSingleQuote )
				{
					if ( string === "'" && vector[ i - 1 ] !== '\\' )
					{
						modeSingleQuote = false;
					}
					continue;
				}
 
				if ( modeDoubleQuote )
				{
					if ( string === '"' && vector[ i - 1 ] !== '\\' )
					{
						modeDoubleQuote = false;
					}
					continue;
				}
 
				if ( modeBlockComment )
				{
					if ( string === '*' && vector[ i + 1 ] === '/' )
					{
						vector[ i + 1 ] = '';
						modeBlockComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( modeLineComment)
				{
					string = vector[ i + 1 ];
					if ( string === '\n' || string === '\r' )
					{
						modeLineComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( modeCondComp )
				{
					if ( vector[ i - 2 ] === '@' && vector[ i - 1 ] === '*' && string === '/' )
					{
						modeCondComp = false;
					}
					continue;
				}
 
				if ( string === '"' )
				{
					modeDoubleQuote = true;
					continue;
				}
 
				if ( string === "'" )
				{
					modeSingleQuote = true;
					continue;
				}
 
				if ( string === '/' )
				{
					if ( ! removeCondComp && vector[ i + 1 ] === '*' && vector[ i + 2 ] === '@' )
					{
						modeCondComp = true;
						continue;
					}
 
					if ( vector[ i + 1 ] === '*' )
					{
						vector[ i ] = '';
 
						modeBlockComment = true;
						continue;
					}
 
					if ( vector[ i + 1 ] === '/' )
					{
						vector[ i ] = '';
						modeLineComment = true;
						continue;
					}
 
					for ( var k : int = i - 1; true; k-- )
					{
						string = vector[ k ];
						if ( string !== ' ' )
						{
							if ( string === '=' )
							{
								modeRegExp = true;
							}
							break;
						}
					}
				}
			}
			return vector.join( '' ).slice( 2, -2 );
		}
	}
}

Allright – CSS doesn’t allow inline or conditional comments so I removed this script functionality and created a CSSParser class:

package de.superclass.parser
{
	/**
	 * @author Markus Raab (superclass.de | blog.derRaab.com)
	 */
	public class CSSParser
	{
		/**
		 * Removes comments from a CSS string. 
		 * 
		 * This is a shrinked port of James Padolseys script.
		 * @see: http://james.padolsey.com/javascript/removing-comments-in-javascript
		 * 
		 * @param css				CSS string
		 * @return					CSS string without comments
		 */
		public static function removeComments( css : String) : String
		{
			var modeSingleQuote		: Boolean = false;
			var modeDoubleQuote		: Boolean = false;
			var modeBlockComment	: Boolean = false;
 
			var vector : Vector.<String> = Vector.<String>( ( '__' + css + '__' ).split( '' ) );
				vector.fixed = true;
 
			var c : int = vector.length;
 
			for ( var i : int = 0; i < c; i++ )
			{
				var string : String = vector[ i ];
 
				if ( modeSingleQuote )
				{
					if ( string === "'" && vector[ i - 1 ] !== '\\' )
					{
						modeSingleQuote = false;
					}
					continue;
				}
 
				if ( modeDoubleQuote )
				{
					if ( string === '"' && vector[ i - 1 ] !== '\\' )
					{
						modeDoubleQuote = false;
					}
					continue;
				}
 
				if ( modeBlockComment )
				{
					if ( string === '*' && vector[ i + 1 ] === '/' )
					{
						vector[ i + 1 ] = '';
						modeBlockComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( string === '"' )
				{
					modeDoubleQuote = true;
					continue;
				}
 
				if ( string === "'" )
				{
					modeSingleQuote = true;
					continue;
				}
 
				if ( string === '/' )
				{
					if ( vector[ i + 1 ] === '*' )
					{
						vector[ i ] = '';
 
						modeBlockComment = true;
						continue;
					}
				}
			}
			return vector.join( '' ).slice( 2, -2 );
		}
	}
}

Good. Lastly I extended the code to remove all comments within HTML source code, which can also contain JavaScript and of course CSS. This is the HTMLParser:

package de.superclass.parser
{
	/**
	 * @author Markus Raab (superclass.de | blog.derRaab.com)
	 */
	public class HTMLParser
	{
		/**
		 * Removes all HTML, CSS and ECMAScript comments from a HTML string.
		 * 
		 * This is an extended port of James Padolseys script with an improved RegExp recognition.
		 * @see: http://james.padolsey.com/javascript/removing-comments-in-javascript
		 * 
		 * @param html						HTML string
		 * @param removeHTMLCondComment		Optional (default=false) - Whether to remove Internet Explorers HTML conditional comments or not.
		 * @param removeJSCondComp			Optional (default=false) - Whether to remove Internet Explorers JavasScript conditional compilation comments or not.
		 * @return							HTML string without comments
		 */
		public static function removeComments( html : String, removeHTMLCondComment : Boolean = false, removeJSCondComp : Boolean = false ) : String
		{
			var modeSingleQuote		: Boolean = false;
			var modeDoubleQuote		: Boolean = false;
			var modeRegExp			: Boolean = false;
			var modeBlockComment	: Boolean = false;
			var modeLineComment		: Boolean = false;
 
			var modeJSCondComp		: Boolean = false;
 
			var modeHTMLComment		: Boolean = false;
			var modeHTMLCondComment	: Boolean = false;
 
			var vector : Vector.<String> = Vector.<String>( ( '__' + html + '__' ).split( '' ) );
				vector.fixed = true;
 
			var c : int = vector.length;
 
			for ( var i : int = 0; i < c; i++ )
			{
				var string : String = vector[ i ];
 
				if ( modeRegExp )
				{
					if ( string === '/' && vector[ i - 1 ] !== '\\' )
					{
						modeRegExp = false;
					}
					continue;
				}
 
				if ( modeSingleQuote )
				{
					if ( string === "'" && vector[ i - 1 ] !== '\\' )
					{
						modeSingleQuote = false;
					}
					continue;
				}
 
				if ( modeDoubleQuote )
				{
					if ( string === '"' && vector[ i - 1 ] !== '\\')
					{
						modeDoubleQuote = false;
					}
					continue;
				}
 
				if ( modeBlockComment )
				{
					if ( string === '*' && vector[ i + 1 ] === '/')
					{
						vector[ i + 1 ] = '';
						modeBlockComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( modeLineComment)
				{
					string = vector[ i + 1 ];
					if ( string === '\n' || string === '\r' )
					{
						modeLineComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( modeJSCondComp )
				{
					if ( vector[ i - 2 ] === '@' && vector[ i - 1 ] === '*' && string === '/' )
					{
						modeJSCondComp = false;
					}
					continue;
				}
 
				if ( modeHTMLComment )
				{
					// --> 
					if ( string === '-' && vector[ i + 1 ] === '-' && vector[ i + 2 ] === '>' )
					{
						vector[ i + 1 ] = '';
						vector[ i + 2 ] = '';
 
						modeHTMLComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( modeHTMLCondComment )
				{
					if ( string === 'i' && vector[ i + 1 ] === 'f' && vector[ i + 2 ] === ']' && vector[ i + 3 ] === '-' && vector[ i + 4 ] === '-' && vector[ i + 5 ] === '>' ) // if]--> 
					{
						modeHTMLCondComment = false;
						i += 5;
					}
					continue;
				}
 
				if ( string === '<' && vector[ i + 1 ] === '!' && vector[ i + 2 ] === '-' && vector[ i + 3 ] === '-' ) // <!--
				{
					if ( ! removeHTMLCondComment && vector[ i + 4 ] === '[' && vector[ i + 5 ] === 'i' && vector[ i + 6 ] === 'f' ) // <!--[if
					{
						modeHTMLCondComment = true;
					}
					else
					{
						vector[ i ] = '';
						modeHTMLComment = true;
					}
				}
 
				if ( string === '"' )
				{
					modeDoubleQuote = true;
					continue;
				}
 
				if ( string === "'" )
				{
					modeSingleQuote = true;
					continue;
				}
 
				if ( string === '/' )
				{
					if ( ! removeJSCondComp && vector[ i + 1 ] === '*' && vector[ i + 2 ] === '@' )
					{
						modeJSCondComp = true;
						continue;
					}
 
					if ( vector[ i + 1 ] === '*' )
					{
						vector[ i ] = '';
 
						modeBlockComment = true;
						continue;
					}
 
					if ( vector[ i + 1 ] === '/' )
					{
						vector[ i ] = '';
						modeLineComment = true;
						continue;
					}
 
					for ( var k : int = i - 1; true; k-- )
					{
						string = vector[ k ];
						if ( string !== ' ' )
						{
							if ( string === '=' )
							{
								modeRegExp = true;
							}
							break;
						}
					}
				}
			}
			return vector.join( '' ).slice( 2, -2 );
		}
	}
}

Let me know if I made a mistake but it seems to work quite well. Or do you know a good library that already does this kind of optimizations?

Well, that’s it for now. Have fun coding!