1 var fcleanup = function( nm ) {
2 return nm.replace(/[^\w\s\.\|`]/g, function( ch ) {
8 * A number of helper functions used for managing events.
9 * Many of the ideas behind this code originated from
10 * Dean Edwards' addEvent library.
14 // Bind an event to an element
15 // Original by Dean Edwards
16 add: function( elem, types, handler, data ) {
17 if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
21 // For whatever reason, IE has trouble passing the window object
22 // around, causing it to be cloned in the process
23 if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) {
27 // Make sure that the function being executed has a unique ID
28 if ( !handler.guid ) {
29 handler.guid = jQuery.guid++;
32 // if data is passed, bind to handler
33 if ( data !== undefined ) {
34 // Create temporary function pointer to original handler
37 // Create unique handler function, wrapped around original handler
38 handler = jQuery.proxy( fn );
40 // Store data in unique handler
44 // Init the element's event structure
45 var events = jQuery.data( elem, "events" ) || jQuery.data( elem, "events", {} ),
46 handle = jQuery.data( elem, "handle" ), eventHandle;
49 eventHandle = function() {
50 // Handle the second event of a trigger and when
51 // an event is called after a page has unloaded
52 return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
53 jQuery.event.handle.apply( eventHandle.elem, arguments ) :
57 handle = jQuery.data( elem, "handle", eventHandle );
60 // If no handle is found then we must be trying to bind to one of the
61 // banned noData elements
66 // Add elem as a property of the handle function
67 // This is to prevent a memory leak with non-native
71 // Handle multiple events separated by a space
72 // jQuery(...).bind("mouseover mouseout", fn);
73 types = types.split( /\s+/ );
77 while ( (type = types[ i++ ]) ) {
78 // Namespaced event handlers
79 var namespaces = type.split(".");
80 type = namespaces.shift();
83 handler = jQuery.proxy( handler );
85 if ( data !== undefined ) {
90 handler.type = namespaces.slice(0).sort().join(".");
92 // Get the current list of functions bound to this event
93 var handlers = events[ type ],
94 special = this.special[ type ] || {};
96 // Init the event handler queue
98 handlers = events[ type ] = {};
100 // Check for a special event handler
101 // Only use addEventListener/attachEvent if the special
102 // events handler returns false
103 if ( !special.setup || special.setup.call( elem, data, namespaces, handler) === false ) {
104 // Bind the global event handler to the element
105 if ( elem.addEventListener ) {
106 elem.addEventListener( type, handle, false );
107 } else if ( elem.attachEvent ) {
108 elem.attachEvent( "on" + type, handle );
114 var modifiedHandler = special.add.call( elem, handler, data, namespaces, handlers );
115 if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) {
116 modifiedHandler.guid = modifiedHandler.guid || handler.guid;
117 modifiedHandler.data = modifiedHandler.data || handler.data;
118 modifiedHandler.type = modifiedHandler.type || handler.type;
119 handler = modifiedHandler;
123 // Add the function to the element's handler list
124 handlers[ handler.guid ] = handler;
126 // Keep track of which events have been used, for global triggering
127 this.global[ type ] = true;
130 // Nullify elem to prevent memory leaks in IE
136 // Detach an event or set of events from an element
137 remove: function( elem, types, handler ) {
138 // don't do events on text and comment nodes
139 if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
143 var events = jQuery.data( elem, "events" ), ret, type, fn;
146 // Unbind all events for the element
147 if ( types === undefined || (typeof types === "string" && types.charAt(0) === ".") ) {
148 for ( type in events ) {
149 this.remove( elem, type + (types || "") );
152 // types is actually an event object here
154 handler = types.handler;
158 // Handle multiple events separated by a space
159 // jQuery(...).unbind("mouseover mouseout", fn);
160 types = types.split(/\s+/);
162 while ( (type = types[ i++ ]) ) {
163 // Namespaced event handlers
164 var namespaces = type.split(".");
165 type = namespaces.shift();
166 var all = !namespaces.length,
167 cleaned = jQuery.map( namespaces.slice(0).sort(), fcleanup ),
168 namespace = new RegExp("(^|\\.)" + cleaned.join("\\.(?:.*\\.)?") + "(\\.|$)"),
169 special = this.special[ type ] || {};
171 if ( events[ type ] ) {
172 // remove the given handler for the given type
174 fn = events[ type ][ handler.guid ];
175 delete events[ type ][ handler.guid ];
177 // remove all handlers for the given type
179 for ( var handle in events[ type ] ) {
180 // Handle the removal of namespaced events
181 if ( all || namespace.test( events[ type ][ handle ].type ) ) {
182 delete events[ type ][ handle ];
187 if ( special.remove ) {
188 special.remove.call( elem, namespaces, fn);
191 // remove generic event handler if no more handlers exist
192 for ( ret in events[ type ] ) {
196 if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
197 if ( elem.removeEventListener ) {
198 elem.removeEventListener( type, jQuery.data( elem, "handle" ), false );
199 } else if ( elem.detachEvent ) {
200 elem.detachEvent( "on" + type, jQuery.data( elem, "handle" ) );
204 delete events[ type ];
210 // Remove the expando if it's no longer used
211 for ( ret in events ) {
215 var handle = jQuery.data( elem, "handle" );
219 jQuery.removeData( elem, "events" );
220 jQuery.removeData( elem, "handle" );
225 // bubbling is internal
226 trigger: function( event, data, elem /*, bubbling */ ) {
227 // Event object or event type
228 var type = event.type || event,
229 bubbling = arguments[3];
232 event = typeof event === "object" ?
233 // jQuery.Event object
234 event[expando] ? event :
236 jQuery.extend( jQuery.Event(type), event ) :
237 // Just the event type (string)
240 if ( type.indexOf("!") >= 0 ) {
241 event.type = type = type.slice(0, -1);
242 event.exclusive = true;
245 // Handle a global trigger
247 // Don't bubble custom events when global (to avoid too much overhead)
248 event.stopPropagation();
250 // Only trigger if we've ever bound an event for it
251 if ( this.global[ type ] ) {
252 jQuery.each( jQuery.cache, function() {
253 if ( this.events && this.events[type] ) {
254 jQuery.event.trigger( event, data, this.handle.elem );
260 // Handle triggering a single element
262 // don't do events on text and comment nodes
263 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
267 // Clean up in case it is reused
268 event.result = undefined;
271 // Clone the incoming data, if any
272 data = jQuery.makeArray( data );
273 data.unshift( event );
276 event.currentTarget = elem;
278 // Trigger the event, it is assumed that "handle" is a function
279 var handle = jQuery.data( elem, "handle" );
281 handle.apply( elem, data );
284 var parent = elem.parentNode || elem.ownerDocument;
286 // Trigger an inline bound script
288 if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
289 if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) {
290 event.result = false;
294 // prevent IE from throwing an error for some elements with some event types, see #3533
297 if ( !event.isPropagationStopped() && parent ) {
298 jQuery.event.trigger( event, data, parent, true );
300 } else if ( !event.isDefaultPrevented() ) {
301 var target = event.target, old,
302 isClick = jQuery.nodeName(target, "a") && type === "click";
304 if ( !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) {
306 if ( target[ type ] ) {
307 // Make sure that we don't accidentally re-trigger the onFOO events
308 old = target[ "on" + type ];
311 target[ "on" + type ] = null;
314 this.triggered = true;
318 // prevent IE from throwing an error for some elements with some event types, see #3533
322 target[ "on" + type ] = old;
325 this.triggered = false;
330 handle: function( event ) {
331 // returned undefined or false
334 event = arguments[0] = jQuery.event.fix( event || window.event );
335 event.currentTarget = this;
337 // Namespaced event handlers
338 var namespaces = event.type.split(".");
339 event.type = namespaces.shift();
341 // Cache this now, all = true means, any handler
342 all = !namespaces.length && !event.exclusive;
344 var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
346 handlers = ( jQuery.data(this, "events") || {} )[ event.type ];
348 for ( var j in handlers ) {
349 var handler = handlers[ j ];
351 // Filter the functions by class
352 if ( all || namespace.test(handler.type) ) {
353 // Pass in a reference to the handler function itself
354 // So that we can later remove it
355 event.handler = handler;
356 event.data = handler.data;
358 var ret = handler.apply( this, arguments );
360 if ( ret !== undefined ) {
362 if ( ret === false ) {
363 event.preventDefault();
364 event.stopPropagation();
368 if ( event.isImmediatePropagationStopped() ) {
378 props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
380 fix: function( event ) {
381 if ( event[ expando ] ) {
385 // store a copy of the original event object
386 // and "clone" to set read-only properties
387 var originalEvent = event;
388 event = jQuery.Event( originalEvent );
390 for ( var i = this.props.length, prop; i; ) {
391 prop = this.props[ --i ];
392 event[ prop ] = originalEvent[ prop ];
395 // Fix target property, if necessary
396 if ( !event.target ) {
397 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
400 // check if target is a textnode (safari)
401 if ( event.target.nodeType === 3 ) {
402 event.target = event.target.parentNode;
405 // Add relatedTarget, if necessary
406 if ( !event.relatedTarget && event.fromElement ) {
407 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
410 // Calculate pageX/Y if missing and clientX/Y available
411 if ( event.pageX == null && event.clientX != null ) {
412 var doc = document.documentElement, body = document.body;
413 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
414 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
417 // Add which for key events
418 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
419 event.which = event.charCode || event.keyCode;
422 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
423 if ( !event.metaKey && event.ctrlKey ) {
424 event.metaKey = event.ctrlKey;
427 // Add which for click: 1 === left; 2 === middle; 3 === right
428 // Note: button is not normalized, so don't use it
429 if ( !event.which && event.button !== undefined ) {
430 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
436 // Deprecated, use jQuery.guid instead
439 // Deprecated, use jQuery.proxy instead
444 // Make sure the ready event is setup
445 setup: jQuery.bindReady,
446 teardown: jQuery.noop
450 add: function( proxy, data, namespaces, live ) {
451 jQuery.extend( proxy, data || {} );
453 proxy.guid += data.selector + data.live;
454 data.liveProxy = proxy;
456 jQuery.event.add( this, data.live, liveHandler, data );
460 remove: function( namespaces ) {
461 if ( namespaces.length ) {
462 var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
464 jQuery.each( (jQuery.data(this, "events").live || {}), function() {
465 if ( name.test(this.type) ) {
471 jQuery.event.remove( this, namespaces[0], liveHandler );
478 setup: function( data, namespaces, fn ) {
479 // We only want to do this special case on windows
480 if ( this.setInterval ) {
481 this.onbeforeunload = fn;
486 teardown: function( namespaces, fn ) {
487 if ( this.onbeforeunload === fn ) {
488 this.onbeforeunload = null;
495 jQuery.Event = function( src ) {
496 // Allow instantiation without the 'new' keyword
497 if ( !this.preventDefault ) {
498 return new jQuery.Event( src );
502 if ( src && src.type ) {
503 this.originalEvent = src;
504 this.type = src.type;
510 // timeStamp is buggy for some events on Firefox(#3843)
511 // So we won't rely on the native value
512 this.timeStamp = now();
515 this[ expando ] = true;
518 function returnFalse() {
521 function returnTrue() {
525 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
526 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
527 jQuery.Event.prototype = {
528 preventDefault: function() {
529 this.isDefaultPrevented = returnTrue;
531 var e = this.originalEvent;
536 // if preventDefault exists run it on the original event
537 if ( e.preventDefault ) {
540 // otherwise set the returnValue property of the original event to false (IE)
541 e.returnValue = false;
543 stopPropagation: function() {
544 this.isPropagationStopped = returnTrue;
546 var e = this.originalEvent;
550 // if stopPropagation exists run it on the original event
551 if ( e.stopPropagation ) {
554 // otherwise set the cancelBubble property of the original event to true (IE)
555 e.cancelBubble = true;
557 stopImmediatePropagation: function() {
558 this.isImmediatePropagationStopped = returnTrue;
559 this.stopPropagation();
561 isDefaultPrevented: returnFalse,
562 isPropagationStopped: returnFalse,
563 isImmediatePropagationStopped: returnFalse
566 // Checks if an event happened on an element within another element
567 // Used in jQuery.event.special.mouseenter and mouseleave handlers
568 var withinElement = function( event ) {
569 // Check if mouse(over|out) are still within the same parent element
570 var parent = event.relatedTarget;
572 // Traverse up the tree
573 while ( parent && parent !== this ) {
574 // Firefox sometimes assigns relatedTarget a XUL element
575 // which we cannot access the parentNode property of
577 parent = parent.parentNode;
579 // assuming we've left the element since we most likely mousedover a xul element
585 if ( parent !== this ) {
586 // set the correct event type
587 event.type = event.data;
589 // handle event if we actually just moused on to a non sub-element
590 jQuery.event.handle.apply( this, arguments );
595 // In case of event delegation, we only need to rename the event.type,
596 // liveHandler will take care of the rest.
597 delegate = function( event ) {
598 event.type = event.data;
599 jQuery.event.handle.apply( this, arguments );
602 // Create mouseenter and mouseleave events
604 mouseenter: "mouseover",
605 mouseleave: "mouseout"
606 }, function( orig, fix ) {
607 jQuery.event.special[ orig ] = {
608 setup: function( data ) {
609 jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
611 teardown: function( data ) {
612 jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
618 if ( !jQuery.support.submitBubbles ) {
620 jQuery.event.special.submit = {
621 setup: function( data, namespaces, fn ) {
622 if ( this.nodeName.toLowerCase() !== "form" ) {
623 jQuery.event.add(this, "click.specialSubmit." + fn.guid, function( e ) {
624 var elem = e.target, type = elem.type;
626 if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
627 return trigger( "submit", this, arguments );
631 jQuery.event.add(this, "keypress.specialSubmit." + fn.guid, function( e ) {
632 var elem = e.target, type = elem.type;
634 if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
635 return trigger( "submit", this, arguments );
644 remove: function( namespaces, fn ) {
645 jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") );
646 jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") );
652 // change delegation, happens here so we have bind.
653 if ( !jQuery.support.changeBubbles ) {
655 var formElems = /textarea|input|select/i;
657 function getVal( elem ) {
658 var type = elem.type, val = elem.value;
660 if ( type === "radio" || type === "checkbox" ) {
663 } else if ( type === "select-multiple" ) {
664 val = elem.selectedIndex > -1 ?
665 jQuery.map( elem.options, function( elem ) {
666 return elem.selected;
670 } else if ( elem.nodeName.toLowerCase() === "select" ) {
671 val = elem.selectedIndex;
677 function testChange( e ) {
678 var elem = e.target, data, val;
680 if ( !formElems.test( elem.nodeName ) || elem.readOnly ) {
684 data = jQuery.data( elem, "_change_data" );
687 // the current data will be also retrieved by beforeactivate
688 if ( e.type !== "focusout" || elem.type !== "radio" ) {
689 jQuery.data( elem, "_change_data", val );
692 if ( data === undefined || val === data ) {
696 if ( data != null || val ) {
698 return jQuery.event.trigger( e, arguments[1], elem );
702 jQuery.event.special.change = {
704 focusout: testChange,
706 click: function( e ) {
707 var elem = e.target, type = elem.type;
709 if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
710 return testChange.call( this, e );
714 // Change has to be called before submit
715 // Keydown will be called before keypress, which is used in submit-event delegation
716 keydown: function( e ) {
717 var elem = e.target, type = elem.type;
719 if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
720 (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
721 type === "select-multiple" ) {
722 return testChange.call( this, e );
726 // Beforeactivate happens also before the previous element is blurred
727 // with this event you can't trigger a change event, but you can store
728 // information/focus[in] is not needed anymore
729 beforeactivate: function( e ) {
732 if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" ) {
733 jQuery.data( elem, "_change_data", getVal(elem) );
737 setup: function( data, namespaces, fn ) {
738 for ( var type in changeFilters ) {
739 jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] );
742 return formElems.test( this.nodeName );
744 remove: function( namespaces, fn ) {
745 for ( var type in changeFilters ) {
746 jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] );
749 return formElems.test( this.nodeName );
753 var changeFilters = jQuery.event.special.change.filters;
757 function trigger( type, elem, args ) {
759 return jQuery.event.handle.apply( elem, args );
762 // Create "bubbling" focus and blur events
763 if ( document.addEventListener ) {
764 jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
765 jQuery.event.special[ fix ] = {
767 this.addEventListener( orig, handler, true );
769 teardown: function() {
770 this.removeEventListener( orig, handler, true );
774 function handler( e ) {
775 e = jQuery.event.fix( e );
777 return jQuery.event.handle.call( this, e );
782 jQuery.each(["bind", "one"], function( i, name ) {
783 jQuery.fn[ name ] = function( type, data, fn ) {
784 // Handle object literals
785 if ( typeof type === "object" ) {
786 for ( var key in type ) {
787 this[ name ](key, data, type[key], fn);
792 if ( jQuery.isFunction( data ) ) {
797 var handler = name === "one" ? jQuery.proxy( fn, function( event ) {
798 jQuery( this ).unbind( event, handler );
799 return fn.apply( this, arguments );
802 return type === "unload" && name !== "one" ?
803 this.one( type, data, fn ) :
804 this.each(function() {
805 jQuery.event.add( this, type, handler, data );
811 unbind: function( type, fn ) {
812 // Handle object literals
813 if ( typeof type === "object" && !type.preventDefault ) {
814 for ( var key in type ) {
815 this.unbind(key, type[key]);
820 return this.each(function() {
821 jQuery.event.remove( this, type, fn );
824 trigger: function( type, data ) {
825 return this.each(function() {
826 jQuery.event.trigger( type, data, this );
830 triggerHandler: function( type, data ) {
832 var event = jQuery.Event( type );
833 event.preventDefault();
834 event.stopPropagation();
835 jQuery.event.trigger( event, data, this[0] );
840 toggle: function( fn ) {
841 // Save reference to arguments for access in closure
842 var args = arguments, i = 1;
844 // link all the functions, so any of them can unbind this click handler
845 while ( i < args.length ) {
846 jQuery.proxy( fn, args[ i++ ] );
849 return this.click( jQuery.proxy( fn, function( event ) {
850 // Figure out which function to execute
851 var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i;
852 jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 );
854 // Make sure that clicks stop
855 event.preventDefault();
857 // and execute the function
858 return args[ lastToggle ].apply( this, arguments ) || false;
862 hover: function( fnOver, fnOut ) {
863 return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
867 jQuery.each(["live", "die"], function( i, name ) {
868 jQuery.fn[ name ] = function( types, data, fn ) {
871 if ( jQuery.isFunction( data ) ) {
876 types = (types || "").split( /\s+/ );
878 while ( (type = types[ i++ ]) != null ) {
879 type = type === "focus" ? "focusin" : // focus --> focusin
880 type === "blur" ? "focusout" : // blur --> focusout
881 type === "hover" ? types.push("mouseleave") && "mouseenter" : // hover support
884 if ( name === "live" ) {
886 jQuery( this.context ).bind( liveConvert( type, this.selector ), {
887 data: data, selector: this.selector, live: type
891 // unbind live handler
892 jQuery( this.context ).unbind( liveConvert( type, this.selector ), fn ? { guid: fn.guid + this.selector + type } : null );
900 function liveHandler( event ) {
901 var stop, elems = [], selectors = [], args = arguments,
902 related, match, fn, elem, j, i, l, data,
903 live = jQuery.extend({}, jQuery.data( this, "events" ).live);
905 // Make sure we avoid non-left-click bubbling in Firefox (#3861)
906 if ( event.button && event.type === "click" ) {
912 if ( fn.live === event.type ||
913 fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) {
916 if ( !(data.beforeFilter && data.beforeFilter[event.type] &&
917 !data.beforeFilter[event.type](event)) ) {
918 selectors.push( fn.selector );
925 match = jQuery( event.target ).closest( selectors, event.currentTarget );
927 for ( i = 0, l = match.length; i < l; i++ ) {
930 elem = match[i].elem;
933 if ( match[i].selector === fn.selector ) {
934 // Those two events require additional checking
935 if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) {
936 related = jQuery( event.relatedTarget ).closest( fn.selector )[0];
939 if ( !related || related !== elem ) {
940 elems.push({ elem: elem, fn: fn });
946 for ( i = 0, l = elems.length; i < l; i++ ) {
948 event.currentTarget = match.elem;
949 event.data = match.fn.data;
950 if ( match.fn.apply( match.elem, args ) === false ) {
959 function liveConvert( type, selector ) {
960 return "live." + (type ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&");
963 jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
964 "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
965 "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
967 // Handle event binding
968 jQuery.fn[ name ] = function( fn ) {
969 return fn ? this.bind( name, fn ) : this.trigger( name );
972 if ( jQuery.attrFn ) {
973 jQuery.attrFn[ name ] = true;
977 // Prevent memory leaks in IE
978 // Window isn't included so as not to unbind existing unload events
980 // - http://isaacschlueter.com/2006/10/msie-memory-leaks/
981 if ( window.attachEvent && !window.addEventListener ) {
982 window.attachEvent("onunload", function() {
983 for ( var id in jQuery.cache ) {
984 if ( jQuery.cache[ id ].handle ) {
985 // Try/Catch is to handle iframes being unloaded, see #4280
987 jQuery.event.remove( jQuery.cache[ id ].handle.elem );