Index: typo3/sysext/rtehtmlarea/htmlarea/htmlarea.js =================================================================== --- typo3/sysext/rtehtmlarea/htmlarea/htmlarea.js (révision 7637) +++ typo3/sysext/rtehtmlarea/htmlarea/htmlarea.js (copie de travail) @@ -466,6 +466,10 @@ }, hotkey: { fn: this.onHotKey + }, + beforedestroy: { + fn: this.onBeforeDestroy, + single: true } }); // Monitor toolbar updates in order to refresh the state of the combo @@ -609,6 +613,15 @@ // Restore the selection if combo was triggered this.mon(iframe.getEl(), 'focus', this.restoreSelection, this); } + }, + /* + * Cleanup + */ + onBeforeDestroy: function () { + this.controlRange = null; + this.bookmark = null; + this.getStore().removeAll(); + this.getStore().destroy(); } }); Ext.reg('htmlareacombo', Ext.ux.form.HTMLAreaCombo); @@ -646,6 +659,12 @@ * Initialize listeners */ initEventListeners: function () { + this.addListener({ + beforedestroy: { + fn: this.onBeforeDestroy, + single: true + } + }); // Monitor editor becoming ready this.mon(this.getEditor(), 'editorready', this.update, this, {single: true}); }, @@ -736,6 +755,13 @@ endPointsInSameBlock = editor.endPointsInSameBlock(); } this.fireEvent('update', mode, selectionEmpty, ancestors, endPointsInSameBlock); + }, + /* + * Cleanup + */ + onBeforeDestroy: function () { + this.removeAll(true); + return true; } }); Ext.reg('htmlareatoolbar', HTMLArea.Toolbar); @@ -760,7 +786,7 @@ fn: this.initEventListeners, single: true }, - beforeDestroy: { + beforedestroy: { fn: this.onBeforeDestroy, single: true } @@ -1087,9 +1113,8 @@ * Start listening to things happening in the iframe */ startListening: function () { - var documentElement = Ext.get(this.document.documentElement); // Create keyMap so that plugins may bind key handlers - this.keyMap = new Ext.KeyMap(documentElement, [], (Ext.isIE || Ext.isWebKit) ? 'keydown' : 'keypress'); + this.keyMap = new Ext.KeyMap(Ext.get(this.document.documentElement), [], (Ext.isIE || Ext.isWebKit) ? 'keydown' : 'keypress'); // Special keys map this.keyMap.addBinding([ { @@ -1141,7 +1166,7 @@ scope: this }); } - // Hot key map (on keydown for all brwosers) + // Hot key map (on keydown for all browsers) var hotKeys = ''; Ext.iterate(this.config.hotKeyList, function (key) { if (key.length == 1) { @@ -1149,7 +1174,7 @@ } }); // Make hot key map available, even if empty, so that plugins may add bindings - this.hotKeyMap = new Ext.KeyMap(documentElement); + this.hotKeyMap = new Ext.KeyMap(Ext.get(this.document.documentElement)); if (!Ext.isEmpty(hotKeys)) { this.hotKeyMap.addBinding({ key: hotKeys, @@ -1160,10 +1185,10 @@ scope: this }); } - this.mon(documentElement, (Ext.isIE || Ext.isWebKit) ? 'keydown' : 'keypress', this.onAnyKey, this); - this.mon(documentElement, 'mouseup', this.onMouse, this); - this.mon(documentElement, 'click', this.onMouse, this); - this.mon(documentElement, Ext.isWebKit ? 'dragend' : 'drop', this.onDrop, this); + this.mon(Ext.get(this.document.documentElement), (Ext.isIE || Ext.isWebKit) ? 'keydown' : 'keypress', this.onAnyKey, this); + this.mon(Ext.get(this.document.documentElement), 'mouseup', this.onMouse, this); + this.mon(Ext.get(this.document.documentElement), 'click', this.onMouse, this); + this.mon(Ext.get(this.document.documentElement), Ext.isWebKit ? 'dragend' : 'drop', this.onDrop, this); }, /* * Handler for other key events @@ -1340,8 +1365,33 @@ * Cleanup */ onBeforeDestroy: function () { + // ExtJS KeyMap object makes IE leak memory + // Nullify EXTJS private handlers + Ext.each(this.keyMap.bindings, function (binding, index) { + this.keyMap.bindings[index] = null; + }); + this.keyMap.handleKeyDown = null; + Ext.each(this.hotKeyMap.bindings, function (binding, index) { + this.hotKeyMap.bindings[index] = null; + }); + this.hotKeyMap.handleKeyDown = null; this.keyMap.disable(); this.hotKeyMap.disable(); + // Cleaning references to DOM in order to avoid IE memory leaks + Ext.get(this.document.body).purgeAllListeners(); + Ext.get(this.document.body).dom = null; + Ext.get(this.document.documentElement).purgeAllListeners(); + Ext.get(this.document.documentElement).dom = null; + this.document = null; + this.getEditor().document = null; + this.getEditor()._doc = null; + this.getEditor()._iframe = null; + Ext.each(this.nestedParentElements.sorted, function (nested) { + Ext.get(nested).purgeAllListeners(); + Ext.get(nested).dom = null; + }); + Ext.destroy(this.autoEl, this.el, this.resizeEl, this.positionEl); + return true; } }); Ext.reg('htmlareaiframe', HTMLArea.Iframe); @@ -1789,7 +1839,7 @@ initEventListeners: function () { this.addListener({ beforedestroy: { - fn: this.clear, + fn: this.onBeforeDestroy, single: true } }); @@ -1833,6 +1883,8 @@ this.statusBarTree.removeAllListeners(); Ext.each(this.statusBarTree.query('a'), function (node) { Ext.QuickTips.unregister(node); + Ext.get(node).dom.ancestor = null; + Ext.destroy(node); }); this.statusBarTree.update(''); this.setSelection(null); @@ -1995,6 +2047,15 @@ onContextMenu: function (event, target) { this.selectElement(target); return this.getEditor().getPlugin('ContextMenu') ? this.getEditor().getPlugin('ContextMenu').show(event, target.ancestor) : false; + }, + /* + * Cleanup + */ + onBeforeDestroy: function() { + this.clear(); + this.removeAll(true); + Ext.destroy(this.statusBarTree, this.statusBarTextMode); + return true; } }); Ext.reg('htmlareastatusbar', HTMLArea.StatusBar); @@ -2254,10 +2315,21 @@ */ onBeforeDestroy: function () { Ext.EventManager.removeResizeListener(this.onWindowResize, this); + // Cleaning references to DOM in order to avoid IE memory leaks var form = this.textArea.dom.form; if (form) { form.htmlAreaPreviousOnReset = null; + Ext.get(form).dom = null; } + Ext.getBody().dom = null; + // ExtJS is not releasing any resources when the iframe is unloaded + this.toolbar.destroy(); + this.statusBar.destroy(); + this.removeAll(true); + if (this.resizable) { + this.resizer.destroy(); + } + return true; } }); Ext.reg('htmlareaframework', HTMLArea.Framework); @@ -2322,7 +2394,12 @@ * @event modeChange * Fires when the editor changes mode */ - 'modeChange' + 'modeChange', + /* + * @event beforedestroy + * Fires before the editor is to be destroyed + */ + 'beforedestroy' ); }, /* @@ -2381,12 +2458,23 @@ itemId: 'textAreaContainer', anchor: '100%', width: (this.textAreaInitialSize.width.indexOf('%') === -1) ? parseInt(this.textAreaInitialSize.width) : 300, - // Let the framework swallow the textarea + // Let the framework swallow the textarea and throw it back listeners: { - afterRender: { - fn: function (textAreaContainer) { textAreaContainer.getEl().appendChild(this.textArea); }, + afterrender: { + fn: function (textAreaContainer) { + this.originalParent = this.textArea.parent().dom; + textAreaContainer.getEl().appendChild(this.textArea); + }, single: true, scope: this + }, + beforedestroy: { + fn: function (textAreaContainer) { + this.originalParent.appendChild(this.textArea.dom); + return true; + }, + single: true, + scope: this } } } @@ -2593,6 +2681,16 @@ return (this.plugins[pluginName] ? this.plugins[pluginName].instance : null); }, /* + * Unregister the instance of the specified plugin + * + * @param string pluginName: the name of the plugin + * @return void + */ + unRegisterPlugin: function(pluginName) { + delete this.plugins[pluginName].instance; + delete this.plugins[pluginName]; + }, + /* * Focus on the editor */ focus: function () { @@ -2626,7 +2724,7 @@ /* * Iframe unload handler: Update the textarea for submission and cleanup */ - onUnload: function (event) {; + onUnload: function (event) { // Save the HTML content into the original textarea for submit, back/forward, etc. if (this.ready) { this.textArea.set({ @@ -2634,8 +2732,19 @@ }, false); } // Cleanup + this.fireEvent('beforedestroy'); Ext.TaskMgr.stopAll(); + // ExtJS is not releasing any resources when the iframe is unloaded this.htmlArea.destroy(); + Ext.iterate(this.plugins, function (pluginId) { + this.unRegisterPlugin(pluginId); + }, this); + this.purgeListeners() + // Cleaning references to DOM in order to avoid IE memory leaks + this.wizards.dom = null; + this.textArea.parent().parent().dom = null; + this.textArea.parent().dom = null; + this.textArea.dom = null;; RTEarea[this.editorId].editor = null; } }); @@ -2736,6 +2845,7 @@ if (docHeader) { size.height -= docHeader.getHeight(); } + docHeader.dom = null; return size; } } Index: typo3/sysext/rtehtmlarea/htmlarea/plugins/ContextMenu/context-menu.js =================================================================== --- typo3/sysext/rtehtmlarea/htmlarea/plugins/ContextMenu/context-menu.js (révision 7637) +++ typo3/sysext/rtehtmlarea/htmlarea/plugins/ContextMenu/context-menu.js (copie de travail) @@ -54,13 +54,13 @@ * Registering plugin "About" information */ var pluginInformation = { - version : "3.0", - developer : "Mihai Bazon & Stanislas Rolland", - developerUrl : "http://www.sjbr.ca/", - copyrightOwner : "dynarch.com & Stanislas Rolland", - sponsor : "American Bible Society & SJBR", - sponsorUrl : "http://www.sjbr.ca/", - license : "GPL" + version : '3.0', + developer : 'Mihai Bazon & Stanislas Rolland', + developerUrl : 'http://www.sjbr.ca/', + copyrightOwner : 'dynarch.com & Stanislas Rolland', + sponsor : 'American Bible Society & SJBR', + sponsorUrl : 'http://www.sjbr.ca/', + license : 'GPL' }; this.registerPluginInformation(pluginInformation); return true; @@ -91,6 +91,8 @@ }, this.pageTSConfiguration)); // Monitor contextmenu clicks on the iframe this.menu.mon(Ext.get(this.editor.document.documentElement), 'contextmenu', this.show, this, {single: true}); + // Monitor editor being destroyed + this.menu.mon(this.editor, 'beforedestroy', this.onBeforeDestroy, this, {single: true}); }, /* * Create the menu items config @@ -258,5 +260,15 @@ this.deleteTarget.remove(); this.editor.updateToolbar(); } + }, + /* + * Handler invoked when the editor is about to be destroyed + */ + onBeforeDestroy: function () { + this.menu.items.each(function (menuItem) { + Ext.QuickTips.unregister(menuItem); + }); + this.menu.removeAll(true); + this.menu.destroy(); } });