How to fix double click support in Firefox 3.6 for MacOS

For whatever reason, double clicking in Flash/Flex apps stopped working in Firefox 3.6 on MacOS.

I was able to ‘fix’ this by building a manager class that listened to click events trickling up from the app. The class looked like this:

package com.yadda.yadda.utils.doubleClickManager {
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.external.ExternalInterface;
	import flash.utils.Timer;

	import mx.core.Application;
	import mx.core.UIComponent;

	/**
	 * the flash player appears to not be able to capture double click events on some browser/os combinations.
	 * in those scenarios, we manually intercept double click events and handle them appropriately
	 */
	public class DoubleClickManager {

		public static const DOUBLE_CLICK_INTERVAL:uint = 400;

		private static var _instance:DoubleClickManager;

		private var doubleClickTimer:Timer = new Timer(DOUBLE_CLICK_INTERVAL, 1);
		private var lastObjectInspected:String;
		private var browserOSCombosToMonitorList:Array = [];

		public static function getInstance():DoubleClickManager {
			if (_instance == null) {
				_instance = new DoubleClickManager();
			}

			return _instance;
		}

		public function initialize():void {
			populateBrowsersToWatch();

			var agent:String = String(ExternalInterface.call("navigator.userAgent.toString")).toLowerCase();
			for each (var obj:Object in browserOSCombosToMonitorList) {
				if ((agent.indexOf(obj["os"]) > -1) && (agent.indexOf(obj["browser"]) > -1)) {
					//we have found a browser/os combination that needs to be watched
					startMonitoring();
					break;
				}
			}
		}

		/**
		 * populate list of browser and os combinations that need to be handled
		 */
		internal function populateBrowsersToWatch():void {
			browserOSCombosToMonitorList.push({os:"macintosh", browser:"firefox/3.6"});
		}

		/**
		 * globally listen for child add and removal events
		 */
		internal function startMonitoring():void {
			(Application.application).systemManager.getSandboxRoot().addEventListener(Event.ADDED, onComponentAdd);
			(Application.application).systemManager.getSandboxRoot().addEventListener(Event.REMOVED, onComponentRemove);
		}

		/**
		 * if the component being added matches our criteria, we keep track of it. this way, we dont accidentally intercept
		 * duplicate events from components nested inside of it. this is easy to track thanks to flash's single threaded model -
		 * only one uicomponent and its child descriptors will be processed at a time.
		 * if we catch one of these guys, we listen for click events
		 */
		internal function onComponentAdd(evn:Event):void {
			if (checkComponentForCompatibility(evn.target)) {
				lastObjectInspected = evn.target.toString();
				evn.target.addEventListener(MouseEvent.CLICK, onComponentClick);
			}
		}

		/**
		 * if we were monitoring this component, we remove event listeners
		 */
		internal function onComponentRemove(evn:Event):void {
			if (checkComponentForCompatibility(evn.target)) {
				evn.target.removeEventListener(MouseEvent.CLICK, onComponentClick);
			}
		}

		/**
		 * check to make sure that the toString of the target is not a descendent of the last object that we found to
		 * be compatible - basically prevents duplicate event handling.
		 * then we make sure the target is a UIComponent and has its doubleClickEnabled property set to true.
		 */
		internal function checkComponentForCompatibility(target:Object):Boolean {
			if (lastObjectInspected != null && (target.toString().indexOf(lastObjectInspected) > -1)) {
				return false;
			}

			return ((target is UIComponent)	&& ((target as UIComponent).doubleClickEnabled == true));
		}

		/**
		 * if the timer is running, we can assume that the user is double clicking. in that usecase, we fire a DOUBLE_CLICK
		 * event from the component. otherwise, we reset and start the timer.
		 */
		internal function onComponentClick(evn:MouseEvent):void {
			if (doubleClickTimer.running) {
				evn.target.dispatchEvent(new MouseEvent(MouseEvent.DOUBLE_CLICK));
			} else {
				doubleClickTimer.reset();
				doubleClickTimer.start();
			}
		}

	}
}

Now, you just need to initialize this bad boy…
In the preInitialize() event handler of your application, do this:
DoubleClickManager.getInstance().initialize();

We have used this with Flex SDK 3.x and 4.0 on FlashPlayer 10.1 and it works quite well. Also, we are using this in a fairly large complex app, but have not noticed any slow down due to recursion or anything.

Hope somebody can find this of use… it is a very annoying bug!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s