/** * Garbage collector for Ext.dom.Element instances. Automatically cleans up Elements * that are no longer in the dom, but were not properly destroyed using * {@link Ext.dom.Element#destroy destroy()}. Recommended practice is for Components to * clean up their own elements, but the GarbageCollector runs on regularly scheduled * intervals to attempt to clean up orphaned Elements that may have slipped through the cracks. * @private */Ext.define('Ext.dom.GarbageCollector', { singleton: true, /** * @property {Number} * The interval at which to run Element garbage collection. Set this property directly * to tune the interval. * * Ext.dom.GarbageCollector.interval = 60000; // run garbage collection every one minute */ interval: 30000, constructor: function() { var me = this; me.lastTime = Ext.now(); me.onTick = me.onTick.bind(me); //<debug> me.onTick.$skipTimerCheck = true; //</debug> me.resume(); }, /** * Collects orphaned Ext.dom.Elements by removing their listeners and evicting them * from the cache. Runs on a regularly scheduled {@link #interval} but can be called * directly to force garbage collection. * @return {String[]} An array containing the IDs of the elements that were garbage * collected, prefixed by their tag names. Only applies in dev mode. Returns nothing * in a production build. */ collect: function() { var me = this, cache = Ext.cache, eid, dom, el, t, isGarbage, tagName; //<debug> var collectedIds = []; // eslint-disable-line vars-on-top, one-var //</debug> for (eid in cache) { if (!cache.hasOwnProperty(eid)) { continue; } el = cache[eid]; if (el.skipGarbageCollection) { continue; } dom = el.dom; //<debug> // Should always have a DOM node if (!dom) { Ext.raise('Missing DOM node in element garbage collection: ' + eid); } //</debug> try { // In IE, accessing any properties of the window object of an orphaned iframe // can result in a "Permission Denied" error. The same error also occurs // when accessing any properties of orphaned documentElement or body inside // of an iframe (documentElement and body become orphaned when the iframe // contentWindow is unloaded) isGarbage = Ext.isGarbage(dom); } catch (e) { // if an error was thrown in isGarbage it is most likely because we are // dealing with an inaccessible window or documentElement inside an orphaned // iframe in IE. In this case we can't do anything except remove the // cache entry. delete cache[eid]; //<debug> collectedIds.push('#' + el.id); //</debug> continue; } if (isGarbage) { isGarbage = false; if (el && el.dom) { //<debug> tagName = el.dom.tagName; //</debug> el.collect(); //<debug> collectedIds.push((tagName ? tagName : '') + '#' + el.id); //</debug> } } } //<feature legacyBrowser> // Cleanup IE Object leaks if (Ext.isIE9m) { t = {}; for (eid in cache) { if (cache.hasOwnProperty(eid)) { t[eid] = cache[eid]; } } Ext.cache = Ext.dom.Element.cache = t; } //</feature> me.lastTime = Ext.now(); //<debug> return collectedIds; //</debug> }, onTick: function() { this.timerId = null; if (Ext.enableGarbageCollector) { this.collect(); } this.resume(); }, /** * Pauses the timer and stops garbage collection */ pause: function() { var timerId = this.timerId; if (timerId) { this.timerId = null; Ext.undefer(timerId); } }, /** * Resumes garbage collection at the specified {@link #interval} */ resume: function() { var me = this, lastTime = me.lastTime; if (Ext.enableGarbageCollector && (Ext.now() - lastTime) > me.interval) { me.collect(); } if (!me.timerId) { me.timerId = Ext.defer(me.onTick, me.interval); } }});