Search code examples
flashinternet-explorermemory-leaksexternalinterface

ExternalInterface.call leaks in IE 9


I created a really simple SWF to demonstrate:

package {
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.external.ExternalInterface;

    public class FlashIELeak extends MovieClip {

        public function FlashIELeak() {
            addEventListener(Event.ENTER_FRAME, onFrame);
        }

        private function onFrame(e:Event):void {
            ExternalInterface.call("test", null);
        }
    }
}

Load that in Chrome, no problems. Memory stays more or less fixed.

Load that in IE and memory just keeps going up and up - about 30k/s. You don't even need to declare a test function. Just embed the swf in the page and you've got a leak. In our project, we're passing an object with several properties and IE is leaking like 120k/s. Not good.

Anyone seen this before? I wasn't able to find any other posts related to this.


Solution

  • There was post in famous russian blog about 2 years ago. Alexander Kozlovskij (aka Fizzer) explained this problem. Here is workaround he created:

    package ru.kozlovskij.external
    {
        import flash.external.ExternalInterface;
        /**
         * @author a.kozlovskij
         * @copy flash.external.ExternalInterface
         */
        public final class ExternalInterfaceExtended
        {
            private static const instance :ExternalInterfaceExtended= new ExternalInterfaceExtended();
            private static var methodName :String;
    
            /**
             * U can't instanciate this class, because it implements a Singletone pattern.
             * @copy flash.external.ExternalInterface.ExternalInterface()
             */
            public function ExternalInterfaceExtended()
            {
                instance && new ExternalInterface();
                methodName = '__flash__addCallback_' + ExternalInterface.objectID;
            }
    
            private static function updateJS():void
            {
                const jsFunc :String = 'function(){ ' +
                    methodName + ' = __flash__addCallback = function(flashObj, methodName)' +
                    '{' +
                    '    alert("JS: called overridden __flash__addCallback(" + arguments[0] + ", " + arguments[1] + ")");' +
                    '    flashObj[methodName] = ' +
                    '     (function(methodName)' +
                    '     {' +
                    '     return function()' +
                    '     {' +
                    '     this.CallFunction(\'\' + __flash__argumentsToXML(arguments,  + \'\');' +
                    '     };' + //dangling semi-colon for IE 6
                    '     })(methodName);' + //force re-closure to prevent IE memory leaks
                    '};' +
                    '}';
    
                ExternalInterface.call(jsFunc);
            }
    
            /**
             * Fixed: Mem leaks in native addCallback-js-closure.
             * @copy flash.external.ExternalInterface.addCallback()
             */
            public static function addCallback(functionName :String, closure :Function):void
            {
                updateJS();
                ExternalInterface.addCallback(functionName, closure);
                //ExternalInterface.call(methodName, functionName);
                //ExternalInterface.call('__flash__addCallback_ext', null, functionName);
            }
    
            /**
             * @copy flash.external.ExternalInterface.call()
             */
            public static function call(functionName :String, ...parameters :Array):*
            {
                parameters.unshift(functionName);
                return ExternalInterface.call.apply(ExternalInterfaceExtended, parameters);
            }
    
            /**
             * @copy flash.external.ExternalInterface.available
             */
            public static function get available():Boolean
            {
                return ExternalInterface.available;
            }
    
            /**
             * @copy flash.external.ExternalInterface.objectID
             */
            public static function get objectID():String
            {
                return ExternalInterface.objectID;
            }
    
            /**
             * @copy flash.external.ExternalInterface.marshallExceptions
             */
            public static function get marshallExceptions():Boolean
            {
                return ExternalInterface.marshallExceptions;
            }
    
            public static function set marshallExceptions(value :Boolean):void
            {
                ExternalInterface.marshallExceptions = value;
            }
        }
    }
    

    So, you just need to use ExternalInterfaceExtended class instead of regular ExternalInterface.