Flex/Flash/Actionscript

Monkey Patching FlexSprite to list all event listeners on any Flex component

A while back Ben Clinkinbeard asked a question on flexcoders saying “Am I the only one who wishes EventDispatcher exposed its listeners?” The fundamental issue was that he wanted to get a list of all the event listeners that had been added to a given component.

The approach: using FlexSprite
Someone suggested monkey patching UIComponent to add this functionality, since each UIComponent gets notified each time a call to addEventListener is called, and you could keep track of the references locally. The thing is, monkey patching UIComponent is kind of a pain in the ass since it’s such a big class. When you monkey patch a class you basically need a complete copy of the whole class in your code so you don’t lose the functionality of that class. It’s perfectly doable with UIComponent, but gets kinda nasty.

So I was looking for an alternative and realized that the beautifully small FlexSprite class, which is the lowest level class you can get source code for (everything above it like Sprite, etc is built into the player). FlexSprite is under 100 lines of code and almost all of that is comments. When you get down to it there’s only a constructor and a single method in FlexSprite. So that makes it super duper easy to monkey patch the bastard. To refresh everyone’s memory the inheritance goes: Sprite -> FlexSprite -> UIComponent.

So if you’re trying to add in functionality that will trickle down into every component in the Flex framework, and you don’t need stuff specific to UIComponent, then the easiest way is to monkey patch FlexSprite.

The result
The good news: you can get a list of every event listener that is currently added to any component (at least anything that inherits from FlexSprite). This list will tell you the main details of the listeners, like type (ie “change”, “click”, etc), priority, useCapture, and useWeakReference. You also get the time when the listener was added (which is stored using getTimer to indicate the number of milliseconds since the app launched).

So now anything that inherits from FlexSprite (every display object in the Flex framework) has the following additional property and methods:

  • eventListeners – returns an Array of every event listener currently registered with the component. The Array contains ListenerTracker objects, which is a new class that simply holds the details of the listeners (like priority, etc).
  • removeEventListeners(type:String) – this will remove all event listeners for a given event type. You don’t have to have the reference to the actual listener function to remove it.
  • removeAllEventListeners() – removes all event listeners of all types. This completely wipes out all event listeners for the component all at once.

Here’s a very simple demo application that simply adds a button via MXML and shows all the event listeners on that button in a DataGrid. You can click the bottom button to remove all the listeners on the top button, which will effectively disable all interaction with the button, which should make sense.

This movie requires Flash Player 9.

Simple example | source

The bad news: so you get the function that was added as the listener, but you can’t get anything useful out of this, like the actual function name in the code. You also can’t get the name or reference of the object that contains the function, or that made the call to addEventListener. So that blows.

UPDATE: We can actually get the name of the class that called addEventListener(), the method name where it was called, and the exact line number. See my post about a sneaky trick to do this.

Basically you can see how many listeners of each type (and priority, etc) there are and you can remove any individual ones (or all of them at once). But we are not being able to get much information about a Function object itself. In the addEventListener function we get passed a Function, but you can’t get any more information about that object (see the listener column in the above example, it just shows the “function Function () {}” string, which doesn’t help at all). I’ve tried using describeType() on the Function and everything else I could think of but couldn’t figure out how to get any additional useful information about a function. If anyone has any ideas shoot me an email to let me know.

A cooler example
So I remembered seeing a pretty badass Flex library that let you explore all the properties and styles of any ui components in your application. I went back and found it, it’s called flexspy and is a project hosted on Google code created by Arnaud Pichery. So that’s cool, you drop the FlexSpy swc into your app and suddenly you get access to this cool real-time property and style inspector. (BTW, FlexSpy is licensed under the Do What the Fuck You Want To Public License, ha, awesome)

So I went and extended the FlexSpy component and added my own Event Listeners tab to the main inspectors. Now when you launch my version of FlexSpy you can see all the event listeners on whatever component you select. Dope. I added in buttons to let you remove any individual listeners, or all the listeners for any particular component. Here’s a screenshot, and the links to the example app and the source (view source is also enabled on the app).

screenshot0021.jpg

Enhanced FlexSpy demo | source

Standard

19 thoughts on “Monkey Patching FlexSprite to list all event listeners on any Flex component

  1. Pingback: Flex Monkey Patches » Blog Archive » Kudos to Doug McCune: Monkey Patching FlexSprite to list all event listeners on any Flex component

  2. Hi Doug,

    That’s something I really wish sometimes that event dispatchers would have. Although monkey patching is evil, I’m certainly going to use this in some debugging situations.

    I’m not sure that really what you wanted to obtain, but you can get the function that called the addEventListener like this ():
    package {
    import flash.display.Sprite;

    public class FunctionName extends Sprite
    {
    public function FunctionName()
    {
    showMyName()
    }

    private function showMyName():void
    {
    var e:Error = new Error();
    var s:String = e.getStackTrace();
    var i:int = s.indexOf(“]”) + 6;
    var j:int = s.indexOf(“()”, i);
    trace(s.substring(i, j));
    }
    }
    }

    (Inspired by Alex Harui’s debugging tricks post)

  3. Pingback: dougmccune.com » Blog Archive » Sneaky Flex trick to get the name of any calling functions

  4. Doug, I’ve been wondering how monkey-patching will play with the Flex framework caching… As in, if you opt to use the framework caching, can you still use monkey-patching? Thoughts?

  5. Pingback: Joeflash’s Enigmacopaedia » Announcing the WTFPL License

  6. Pingback: dougmccune.com » Blog Archive » Examples from my 360|Flex session - Using Open Source Community Projects

  7. Pingback: jun :: realeyes media » Blog Archive » Flex 3 BYOP: Bring your own profiler…flash.sampler

  8. Doug, very nice!
    I set the button as visible=”false” and tried to access the panel via Ctrl+F12 and it seems that only the flexspy portion comes up. Anyway to also have your portion also show up using Ctrl+F12. This option is nice as to not have to have the button show all the time.

  9. Xavier Agnetti says:

    Hi Doug!

    Does this code work on the FP release?
    I thought Error.getStackTrace() only works in the FP debugger version.

  10. Srikanth says:

    Two questions.

    1) Can i find if my app is running in debugger or not. The Capabilities.isDebugger() tells if the flash player is a debugger version or not. But i want to know, programatically, if the app is running in debug mode or not.

    2) How easy is it to a button to the flex spy that highlights the current component that has the focus?

    What version of flexspy did you use to build your extension.

    Thanks in advance.

  11. SpookyLuke says:

    @Srikanth – answer to question 1

    /**
    * Obtain the URL of the application
    */
    public static function getApplicationURL():String
    {
    var application:Application = Application.application as Application;
    var appName:String = application.className;
    var appURL:String = application.url;
    appURL = appURL.replace(appName+”.swf”,””);
    return appURL;
    }

    /**
    * Obtain the current list of parameters on the applications url
    */
    public static function getUrlParamaters():Dictionary
    {
    var urlParams:Dictionary = new Dictionary();
    var fullUrl:String = ExternalInterface.call(‘eval’,’document.location.href’);

    var paramStr:String = fullUrl.split(‘?’)[1];
    if (paramStr != null)
    {
    var params:Array = paramStr.split(‘&’);
    for (var i:int = 0; i < params.length; i++)
    {
    var kv:Array = params[i].split(‘=’);
    urlParams[kv[0]] = kv[1];
    }
    }

    return urlParams;
    }

    /**
    * Indicate whether this application is being run in debug mode
    */
    public static function isDebugMode():Boolean
    {
    var params:Dictionary = ApplicationUtils.getUrlParamaters();
    if(params.hasOwnProperty(“debug”))
    {
    if(params[“debug”] == ‘true’)
    {
    return true;
    }
    return false;
    }
    return false;
    }

  12. SpookyLuke says:

    @Srikanth – NB Doesnt work if you are debugging a local app url e.g file:///C:\Developmen\Somthing\Else\MySpunkyApp.swf as there are no url parameters to parse.

  13. Hi Doug,

    I don’t know if your are aware of it but when I follow the links to the simple example and enhanced flexspy demo – the app starts loading but hangs on ‘initializing…’.
    Does it work for FP10?
    Cheers,
    T

  14. Pingback: flex sdk core classes veraendern - Flashforum

Comments are closed.