Fixes memory leaks relating to events in IE with page unload and with jQuery methods remove, html and empty (#1610, #1618, #1697 and #1731). Also re-worked variable names to be more consistent with the core.

This commit is contained in:
Brandon Aaron 2007-10-06 17:04:20 +00:00
parent bb998f2518
commit 92aac3abd6
2 changed files with 47 additions and 37 deletions

View File

@ -1251,17 +1251,20 @@ jQuery.each({
remove: function( selector ) { remove: function( selector ) {
if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) { if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) {
jQuery.removeData( this ); // Prevent memory leaks
jQuery( "*", this ).add(this).each(function(){
jQuery.event.remove(this);
jQuery.removeData(this);
});
this.parentNode.removeChild( this ); this.parentNode.removeChild( this );
} }
}, },
empty: function() { empty: function() {
// Clean up the cache // Remove element nodes and prevent memory leaks
jQuery( "*", this ).each(function(){ jQuery( ">*", this ).remove();
jQuery.removeData(this);
});
// Remove any remaining nodes
while ( this.firstChild ) while ( this.firstChild )
this.removeChild( this.firstChild ); this.removeChild( this.firstChild );
} }

View File

@ -43,7 +43,7 @@ jQuery.event = {
// Init the element's event structure // Init the element's event structure
var events = jQuery.data(element, "events") || jQuery.data(element, "events", {}); var events = jQuery.data(element, "events") || jQuery.data(element, "events", {});
var handle = jQuery.data(element, "handle", function(){ var handle = jQuery.data(element, "handle") || jQuery.data(element, "handle", function(){
// returned undefined or false // returned undefined or false
var val; var val;
@ -149,10 +149,10 @@ jQuery.event = {
} else { } else {
var val, ret, fn = jQuery.isFunction( element[ type ] || null ), var val, ret, fn = jQuery.isFunction( element[ type ] || null ),
// Check to see if we need to provide a fake event, or not // Check to see if we need to provide a fake event, or not
evt = !data[0] || !data[0].preventDefault; event = !data[0] || !data[0].preventDefault;
// Pass along a fake event // Pass along a fake event
if ( evt ) if ( event )
data.unshift( this.fix({ type: type, target: element }) ); data.unshift( this.fix({ type: type, target: element }) );
// Enforce the right trigger type // Enforce the right trigger type
@ -167,7 +167,7 @@ jQuery.event = {
val = false; val = false;
// Extra functions don't get the custom event object // Extra functions don't get the custom event object
if ( evt ) if ( event )
data.shift(); data.shift();
// Handle triggering of extra function // Handle triggering of extra function
@ -197,23 +197,24 @@ jQuery.event = {
var parts = event.type.split("."); var parts = event.type.split(".");
event.type = parts[0]; event.type = parts[0];
var c = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], args = Array.prototype.slice.call( arguments, 1 ); var handlers = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], args = Array.prototype.slice.call( arguments, 1 );
args.unshift( event ); args.unshift( event );
for ( var j in c ) { for ( var j in handlers ) {
var handler = handlers[j];
// Pass in a reference to the handler function itself // Pass in a reference to the handler function itself
// So that we can later remove it // So that we can later remove it
args[0].handler = c[j]; args[0].handler = handler;
args[0].data = c[j].data; args[0].data = handler.data;
// Filter the functions by class // Filter the functions by class
if ( !parts[1] || c[j].type == parts[1] ) { if ( !parts[1] || handler.type == parts[1] ) {
var tmp = c[j].apply( this, args ); var ret = handler.apply( this, args );
if ( val !== false ) if ( val !== false )
val = tmp; val = ret;
if ( tmp === false ) { if ( ret === false ) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
} }
@ -265,9 +266,9 @@ jQuery.event = {
// Calculate pageX/Y if missing and clientX/Y available // Calculate pageX/Y if missing and clientX/Y available
if ( event.pageX == null && event.clientX != null ) { if ( event.pageX == null && event.clientX != null ) {
var e = document.documentElement, b = document.body; var doc = document.documentElement, body = document.body;
event.pageX = event.clientX + (e && e.scrollLeft || b && b.scrollLeft || 0); event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0);
event.pageY = event.clientY + (e && e.scrollTop || b && b.scrollTop || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0);
} }
// Add which for key events // Add which for key events
@ -322,54 +323,54 @@ jQuery.fn.extend({
toggle: function() { toggle: function() {
// Save reference to arguments for access in closure // Save reference to arguments for access in closure
var a = arguments; var args = arguments;
return this.click(function(e) { return this.click(function(event) {
// Figure out which function to execute // Figure out which function to execute
this.lastToggle = 0 == this.lastToggle ? 1 : 0; this.lastToggle = 0 == this.lastToggle ? 1 : 0;
// Make sure that clicks stop // Make sure that clicks stop
e.preventDefault(); event.preventDefault();
// and execute the function // and execute the function
return a[this.lastToggle].apply( this, [e] ) || false; return args[this.lastToggle].apply( this, [event] ) || false;
}); });
}, },
hover: function(f,g) { hover: function(fnOver, fnOut) {
// A private function for handling mouse 'hovering' // A private function for handling mouse 'hovering'
function handleHover(e) { function handleHover(event) {
// Check if mouse(over|out) are still within the same parent element // Check if mouse(over|out) are still within the same parent element
var p = e.relatedTarget; var parent = event.relatedTarget;
// Traverse up the tree // Traverse up the tree
while ( p && p != this ) try { p = p.parentNode; } catch(e) { p = this; }; while ( parent && parent != this ) try { parent = parent.parentNode; } catch(error) { parent = this; };
// If we actually just moused on to a sub-element, ignore it // If we actually just moused on to a sub-element, ignore it
if ( p == this ) return false; if ( parent == this ) return false;
// Execute the right function // Execute the right function
return (e.type == "mouseover" ? f : g).apply(this, [e]); return (event.type == "mouseover" ? fnOver : fnOut).apply(this, [event]);
} }
// Bind the function to the two event listeners // Bind the function to the two event listeners
return this.mouseover(handleHover).mouseout(handleHover); return this.mouseover(handleHover).mouseout(handleHover);
}, },
ready: function(f) { ready: function(fn) {
// Attach the listeners // Attach the listeners
bindReady(); bindReady();
// If the DOM is already ready // If the DOM is already ready
if ( jQuery.isReady ) if ( jQuery.isReady )
// Execute the function immediately // Execute the function immediately
f.apply( document, [jQuery] ); fn.apply( document, [jQuery] );
// Otherwise, remember the function for later // Otherwise, remember the function for later
else else
// Add the function to the wait list // Add the function to the wait list
jQuery.readyList.push( function() { return f.apply(this, [jQuery]); } ); jQuery.readyList.push( function() { return fn.apply(this, [jQuery]); } );
return this; return this;
} }
@ -409,11 +410,11 @@ jQuery.extend({
jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," + jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
"mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
"submit,keydown,keypress,keyup,error").split(","), function(i,o){ "submit,keydown,keypress,keyup,error").split(","), function(i, name){
// Handle event binding // Handle event binding
jQuery.fn[o] = function(f){ jQuery.fn[name] = function(fn){
return f ? this.bind(o, f) : this.trigger(o); return fn ? this.bind(name, fn) : this.trigger(name);
}; };
}); });
@ -451,3 +452,9 @@ function bindReady(){
// A fallback to window.onload, that will always work // A fallback to window.onload, that will always work
jQuery.event.add( window, "load", jQuery.ready ); jQuery.event.add( window, "load", jQuery.ready );
} }
// Prevent memory leaks in IE
if ( jQuery.browser.msie )
jQuery(window).bind("unload", function() {
$("*").add([document, window]).unbind();
});