Bug 1876888 - Try estimate percentiles as "shortest 5%" instead of "top 95%". r=ahal...
[gecko.git] / testing / mochitest / MochiKit / Signal.js
blob74199c170eb7b366bdfcadf958fa5283d2238abd
1 /***
3 MochiKit.Signal 1.4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
7 (c) 2006 Jonathan Gardner, Beau Hartshorne, Bob Ippolito.  All rights Reserved.
9 ***/
11 if (typeof(dojo) != 'undefined') {
12     dojo.provide('MochiKit.Signal');
13     dojo.require('MochiKit.Base');
14     dojo.require('MochiKit.DOM');
15     dojo.require('MochiKit.Style');
17 if (typeof(JSAN) != 'undefined') {
18     JSAN.use('MochiKit.Base', []);
19     JSAN.use('MochiKit.DOM', []);
20     JSAN.use('MochiKit.Style', []);
23 try {
24     if (typeof(MochiKit.Base) == 'undefined') {
25         throw '';
26     }
27 } catch (e) {
28     throw 'MochiKit.Signal depends on MochiKit.Base!';
31 try {
32     if (typeof(MochiKit.DOM) == 'undefined') {
33         throw '';
34     }
35 } catch (e) {
36     throw 'MochiKit.Signal depends on MochiKit.DOM!';
39 try {
40     if (typeof(MochiKit.Style) == 'undefined') {
41         throw '';
42     }
43 } catch (e) {
44     throw 'MochiKit.Signal depends on MochiKit.Style!';
47 if (typeof(MochiKit.Signal) == 'undefined') {
48     MochiKit.Signal = {};
51 MochiKit.Signal.NAME = 'MochiKit.Signal';
52 MochiKit.Signal.VERSION = '1.4';
54 MochiKit.Signal._observers = [];
56 /** @id MochiKit.Signal.Event */
57 MochiKit.Signal.Event = function (src, e) {
58     this._event = e || window.event;
59     this._src = src;
62 MochiKit.Base.update(MochiKit.Signal.Event.prototype, {
64     __repr__: function () {
65         var repr = MochiKit.Base.repr;
66         var str = '{event(): ' + repr(this.event()) +
67             ', src(): ' + repr(this.src()) +
68             ', type(): ' + repr(this.type()) +
69             ', target(): ' + repr(this.target()) +
70             ', modifier(): ' + '{alt: ' + repr(this.modifier().alt) +
71             ', ctrl: ' + repr(this.modifier().ctrl) +
72             ', meta: ' + repr(this.modifier().meta) +
73             ', shift: ' + repr(this.modifier().shift) +
74             ', any: ' + repr(this.modifier().any) + '}';
76         if (this.type() && this.type().indexOf('key') === 0) {
77             str += ', key(): {code: ' + repr(this.key().code) +
78                 ', string: ' + repr(this.key().string) + '}';
79         }
81         if (this.type() && (
82             this.type().indexOf('mouse') === 0 ||
83             this.type().indexOf('click') != -1 ||
84             this.type() == 'contextmenu')) {
86             str += ', mouse(): {page: ' + repr(this.mouse().page) +
87                 ', client: ' + repr(this.mouse().client);
89             if (this.type() != 'mousemove') {
90                 str += ', button: {left: ' + repr(this.mouse().button.left) +
91                     ', middle: ' + repr(this.mouse().button.middle) +
92                     ', right: ' + repr(this.mouse().button.right) + '}}';
93             } else {
94                 str += '}';
95             }
96         }
97         if (this.type() == 'mouseover' || this.type() == 'mouseout') {
98             str += ', relatedTarget(): ' + repr(this.relatedTarget());
99         }
100         str += '}';
101         return str;
102     },
104      /** @id MochiKit.Signal.Event.prototype.toString */
105     toString: function () {
106         return this.__repr__();
107     },
109     /** @id MochiKit.Signal.Event.prototype.src */
110     src: function () {
111         return this._src;
112     },
114     /** @id MochiKit.Signal.Event.prototype.event  */
115     event: function () {
116         return this._event;
117     },
119     /** @id MochiKit.Signal.Event.prototype.type */
120     type: function () {
121         return this._event.type || undefined;
122     },
124     /** @id MochiKit.Signal.Event.prototype.target */
125     target: function () {
126         return this._event.target || this._event.srcElement;
127     },
129     _relatedTarget: null,
130     /** @id MochiKit.Signal.Event.prototype.relatedTarget */
131     relatedTarget: function () {
132         if (this._relatedTarget !== null) {
133             return this._relatedTarget;
134         }
136         var elem = null;
137         if (this.type() == 'mouseover') {
138             elem = (this._event.relatedTarget ||
139                 this._event.fromElement);
140         } else if (this.type() == 'mouseout') {
141             elem = (this._event.relatedTarget ||
142                 this._event.toElement);
143         }
144         if (elem !== null) {
145             this._relatedTarget = elem;
146             return elem;
147         }
149         return undefined;
150     },
152     _modifier: null,
153     /** @id MochiKit.Signal.Event.prototype.modifier */
154     modifier: function () {
155         if (this._modifier !== null) {
156             return this._modifier;
157         }
158         var m = {};
159         m.alt = this._event.altKey;
160         m.ctrl = this._event.ctrlKey;
161         m.meta = this._event.metaKey || false; // IE and Opera punt here
162         m.shift = this._event.shiftKey;
163         m.any = m.alt || m.ctrl || m.shift || m.meta;
164         this._modifier = m;
165         return m;
166     },
168     _key: null,
169     /** @id MochiKit.Signal.Event.prototype.key */
170     key: function () {
171         if (this._key !== null) {
172             return this._key;
173         }
174         var k = {};
175         if (this.type() && this.type().indexOf('key') === 0) {
177             /*
179                 If you're looking for a special key, look for it in keydown or
180                 keyup, but never keypress. If you're looking for a Unicode
181                 chracter, look for it with keypress, but never keyup or
182                 keydown.
184                 Notes:
186                 FF key event behavior:
187                 key     event   charCode    keyCode
188                 DOWN    ku,kd   0           40
189                 DOWN    kp      0           40
190                 ESC     ku,kd   0           27
191                 ESC     kp      0           27
192                 a       ku,kd   0           65
193                 a       kp      97          0
194                 shift+a ku,kd   0           65
195                 shift+a kp      65          0
196                 1       ku,kd   0           49
197                 1       kp      49          0
198                 shift+1 ku,kd   0           0
199                 shift+1 kp      33          0
201                 IE key event behavior:
202                 (IE doesn't fire keypress events for special keys.)
203                 key     event   keyCode
204                 DOWN    ku,kd   40
205                 DOWN    kp      undefined
206                 ESC     ku,kd   27
207                 ESC     kp      27
208                 a       ku,kd   65
209                 a       kp      97
210                 shift+a ku,kd   65
211                 shift+a kp      65
212                 1       ku,kd   49
213                 1       kp      49
214                 shift+1 ku,kd   49
215                 shift+1 kp      33
217                 Safari key event behavior:
218                 (Safari sets charCode and keyCode to something crazy for
219                 special keys.)
220                 key     event   charCode    keyCode
221                 DOWN    ku,kd   63233       40
222                 DOWN    kp      63233       63233
223                 ESC     ku,kd   27          27
224                 ESC     kp      27          27
225                 a       ku,kd   97          65
226                 a       kp      97          97
227                 shift+a ku,kd   65          65
228                 shift+a kp      65          65
229                 1       ku,kd   49          49
230                 1       kp      49          49
231                 shift+1 ku,kd   33          49
232                 shift+1 kp      33          33
234             */
236             /* look for special keys here */
237             if (this.type() == 'keydown' || this.type() == 'keyup') {
238                 k.code = this._event.keyCode;
239                 k.string = (MochiKit.Signal._specialKeys[k.code] ||
240                     'KEY_UNKNOWN');
241                 this._key = k;
242                 return k;
244             /* look for characters here */
245             } else if (this.type() == 'keypress') {
247                 /*
249                     Special key behavior:
251                     IE: does not fire keypress events for special keys
252                     FF: sets charCode to 0, and sets the correct keyCode
253                     Safari: sets keyCode and charCode to something stupid
255                 */
257                 k.code = 0;
258                 k.string = '';
260                 if (typeof(this._event.charCode) != 'undefined' &&
261                     this._event.charCode !== 0 &&
262                     !MochiKit.Signal._specialMacKeys[this._event.charCode]) {
263                     k.code = this._event.charCode;
264                     k.string = String.fromCharCode(k.code);
265                 } else if (this._event.keyCode &&
266                     typeof(this._event.charCode) == 'undefined') { // IE
267                     k.code = this._event.keyCode;
268                     k.string = String.fromCharCode(k.code);
269                 }
271                 this._key = k;
272                 return k;
273             }
274         }
275         return undefined;
276     },
278     _mouse: null,
279     /** @id MochiKit.Signal.Event.prototype.mouse */
280     mouse: function () {
281         if (this._mouse !== null) {
282             return this._mouse;
283         }
285         var m = {};
286         var e = this._event;
288         if (this.type() && (
289             this.type().indexOf('mouse') === 0 ||
290             this.type().indexOf('click') != -1 ||
291             this.type() == 'contextmenu')) {
293             m.client = new MochiKit.Style.Coordinates(0, 0);
294             if (e.clientX || e.clientY) {
295                 m.client.x = (!e.clientX || e.clientX < 0) ? 0 : e.clientX;
296                 m.client.y = (!e.clientY || e.clientY < 0) ? 0 : e.clientY;
297             }
299             m.page = new MochiKit.Style.Coordinates(0, 0);
300             if (e.pageX || e.pageY) {
301                 m.page.x = (!e.pageX || e.pageX < 0) ? 0 : e.pageX;
302                 m.page.y = (!e.pageY || e.pageY < 0) ? 0 : e.pageY;
303             } else {
304                 /*
306                     The IE shortcut can be off by two. We fix it. See:
307                     http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getboundingclientrect.asp
309                     This is similar to the method used in
310                     MochiKit.Style.getElementPosition().
312                 */
313                 var de = MochiKit.DOM._document.documentElement;
314                 var b = MochiKit.DOM._document.body;
316                 m.page.x = e.clientX +
317                     (de.scrollLeft || b.scrollLeft) -
318                     (de.clientLeft || 0);
320                 m.page.y = e.clientY +
321                     (de.scrollTop || b.scrollTop) -
322                     (de.clientTop || 0);
324             }
325             if (this.type() != 'mousemove') {
326                 m.button = {};
327                 m.button.left = false;
328                 m.button.right = false;
329                 m.button.middle = false;
331                 /* we could check e.button, but which is more consistent */
332                 if (e.which) {
333                     m.button.left = (e.which == 1);
334                     m.button.middle = (e.which == 2);
335                     m.button.right = (e.which == 3);
337                     /*
339                         Mac browsers and right click:
341                             - Safari doesn't fire any click events on a right
342                               click:
343                               http://bugzilla.opendarwin.org/show_bug.cgi?id=6595
345                             - Firefox fires the event, and sets ctrlKey = true
347                             - Opera fires the event, and sets metaKey = true
349                         oncontextmenu is fired on right clicks between
350                         browsers and across platforms.
352                     */
354                 } else {
355                     m.button.left = !!(e.button & 1);
356                     m.button.right = !!(e.button & 2);
357                     m.button.middle = !!(e.button & 4);
358                 }
359             }
360             this._mouse = m;
361             return m;
362         }
363         return undefined;
364     },
366     /** @id MochiKit.Signal.Event.prototype.stop */
367     stop: function () {
368         this.stopPropagation();
369         this.preventDefault();
370     },
372     /** @id MochiKit.Signal.Event.prototype.stopPropagation */
373     stopPropagation: function () {
374         if (this._event.stopPropagation) {
375             this._event.stopPropagation();
376         } else {
377             this._event.cancelBubble = true;
378         }
379     },
381     /** @id MochiKit.Signal.Event.prototype.preventDefault */
382     preventDefault: function () {
383         if (this._event.preventDefault) {
384             this._event.preventDefault();
385         } else if (this._confirmUnload === null) {
386             this._event.returnValue = false;
387         }
388     },
390     _confirmUnload: null,
392     /** @id MochiKit.Signal.Event.prototype.confirmUnload */
393     confirmUnload: function (msg) {
394         if (this.type() == 'beforeunload') {
395             this._confirmUnload = msg;
396             this._event.returnValue = msg;
397         }
398     }
401 /* Safari sets keyCode to these special values onkeypress. */
402 MochiKit.Signal._specialMacKeys = {
403     3: 'KEY_ENTER',
404     63289: 'KEY_NUM_PAD_CLEAR',
405     63276: 'KEY_PAGE_UP',
406     63277: 'KEY_PAGE_DOWN',
407     63275: 'KEY_END',
408     63273: 'KEY_HOME',
409     63234: 'KEY_ARROW_LEFT',
410     63232: 'KEY_ARROW_UP',
411     63235: 'KEY_ARROW_RIGHT',
412     63233: 'KEY_ARROW_DOWN',
413     63302: 'KEY_INSERT',
414     63272: 'KEY_DELETE'
417 /* for KEY_F1 - KEY_F12 */
418 (function () {
419     var _specialMacKeys = MochiKit.Signal._specialMacKeys;
420     for (i = 63236; i <= 63242; i++) {
421         // no F0
422         _specialMacKeys[i] = 'KEY_F' + (i - 63236 + 1);
423     }
424 })();
426 /* Standard keyboard key codes. */
427 MochiKit.Signal._specialKeys = {
428     8: 'KEY_BACKSPACE',
429     9: 'KEY_TAB',
430     12: 'KEY_NUM_PAD_CLEAR', // weird, for Safari and Mac FF only
431     13: 'KEY_ENTER',
432     16: 'KEY_SHIFT',
433     17: 'KEY_CTRL',
434     18: 'KEY_ALT',
435     19: 'KEY_PAUSE',
436     20: 'KEY_CAPS_LOCK',
437     27: 'KEY_ESCAPE',
438     32: 'KEY_SPACEBAR',
439     33: 'KEY_PAGE_UP',
440     34: 'KEY_PAGE_DOWN',
441     35: 'KEY_END',
442     36: 'KEY_HOME',
443     37: 'KEY_ARROW_LEFT',
444     38: 'KEY_ARROW_UP',
445     39: 'KEY_ARROW_RIGHT',
446     40: 'KEY_ARROW_DOWN',
447     44: 'KEY_PRINT_SCREEN',
448     45: 'KEY_INSERT',
449     46: 'KEY_DELETE',
450     59: 'KEY_SEMICOLON', // weird, for Safari and IE only
451     91: 'KEY_WINDOWS_LEFT',
452     92: 'KEY_WINDOWS_RIGHT',
453     93: 'KEY_SELECT',
454     106: 'KEY_NUM_PAD_ASTERISK',
455     107: 'KEY_NUM_PAD_PLUS_SIGN',
456     109: 'KEY_NUM_PAD_HYPHEN-MINUS',
457     110: 'KEY_NUM_PAD_FULL_STOP',
458     111: 'KEY_NUM_PAD_SOLIDUS',
459     144: 'KEY_NUM_LOCK',
460     145: 'KEY_SCROLL_LOCK',
461     186: 'KEY_SEMICOLON',
462     187: 'KEY_EQUALS_SIGN',
463     188: 'KEY_COMMA',
464     189: 'KEY_HYPHEN-MINUS',
465     190: 'KEY_FULL_STOP',
466     191: 'KEY_SOLIDUS',
467     192: 'KEY_GRAVE_ACCENT',
468     219: 'KEY_LEFT_SQUARE_BRACKET',
469     220: 'KEY_REVERSE_SOLIDUS',
470     221: 'KEY_RIGHT_SQUARE_BRACKET',
471     222: 'KEY_APOSTROPHE'
472     // undefined: 'KEY_UNKNOWN'
475 (function () {
476     /* for KEY_0 - KEY_9 */
477     var _specialKeys = MochiKit.Signal._specialKeys;
478     for (var i = 48; i <= 57; i++) {
479         _specialKeys[i] = 'KEY_' + (i - 48);
480     }
482     /* for KEY_A - KEY_Z */
483     for (i = 65; i <= 90; i++) {
484         _specialKeys[i] = 'KEY_' + String.fromCharCode(i);
485     }
487     /* for KEY_NUM_PAD_0 - KEY_NUM_PAD_9 */
488     for (i = 96; i <= 105; i++) {
489         _specialKeys[i] = 'KEY_NUM_PAD_' + (i - 96);
490     }
492     /* for KEY_F1 - KEY_F12 */
493     for (i = 112; i <= 123; i++) {
494         // no F0
495         _specialKeys[i] = 'KEY_F' + (i - 112 + 1);
496     }
497 })();
499 MochiKit.Base.update(MochiKit.Signal, {
501     __repr__: function () {
502         return '[' + this.NAME + ' ' + this.VERSION + ']';
503     },
505     toString: function () {
506         return this.__repr__();
507     },
509     _unloadCache: function () {
510         var self = MochiKit.Signal;
511         var observers = self._observers;
513         for (var i = 0; i < observers.length; i++) {
514             self._disconnect(observers[i]);
515         }
517         delete self._observers;
519         try {
520             window.onload = undefined;
521         } catch(e) {
522             // pass
523         }
525         try {
526             window.onunload = undefined;
527         } catch(e) {
528             // pass
529         }
530     },
532     _listener: function (src, func, obj, isDOM) {
533         var self = MochiKit.Signal;
534         var E = self.Event;
535         if (!isDOM) {
536             return MochiKit.Base.bind(func, obj);
537         }
538         obj = obj || src;
539         if (typeof(func) == "string") {
540             return function (nativeEvent) {
541                 obj[func].apply(obj, [new E(src, nativeEvent)]);
542             };
543         } else {
544             return function (nativeEvent) {
545                 func.apply(obj, [new E(src, nativeEvent)]);
546             };
547         }
548     },
550     _browserAlreadyHasMouseEnterAndLeave: function () {
551         return /MSIE/.test(navigator.userAgent);
552     },
554     _mouseEnterListener: function (src, sig, func, obj) {
555         var E = MochiKit.Signal.Event;
556         return function (nativeEvent) {
557             var e = new E(src, nativeEvent);
558             try {
559                 e.relatedTarget().nodeName;
560             } catch (err) {
561                 /* probably hit a permission denied error; possibly one of
562                  * firefox's screwy anonymous DIVs inside an input element.
563                  * Allow this event to propogate up.
564                  */
565                 return;
566             }
567             e.stop();
568             if (MochiKit.DOM.isChildNode(e.relatedTarget(), src)) {
569                 /* We've moved between our node and a child. Ignore. */
570                 return;
571             }
572             e.type = function () { return sig; };
573             if (typeof(func) == "string") {
574                 return obj[func].apply(obj, [e]);
575             } else {
576                 return func.apply(obj, [e]);
577             }
578         };
579     },
581     _getDestPair: function (objOrFunc, funcOrStr) {
582         var obj = null;
583         var func = null;
584         if (typeof(funcOrStr) != 'undefined') {
585             obj = objOrFunc;
586             func = funcOrStr;
587             if (typeof(funcOrStr) == 'string') {
588                 if (typeof(objOrFunc[funcOrStr]) != "function") {
589                     throw new Error("'funcOrStr' must be a function on 'objOrFunc'");
590                 }
591             } else if (typeof(funcOrStr) != 'function') {
592                 throw new Error("'funcOrStr' must be a function or string");
593             }
594         } else if (typeof(objOrFunc) != "function") {
595             throw new Error("'objOrFunc' must be a function if 'funcOrStr' is not given");
596         } else {
597             func = objOrFunc;
598         }
599         return [obj, func];
601     },
603     /** @id MochiKit.Signal.connect */
604     connect: function (src, sig, objOrFunc/* optional */, funcOrStr) {
605         src = MochiKit.DOM.getElement(src);
606         var self = MochiKit.Signal;
608         if (typeof(sig) != 'string') {
609             throw new Error("'sig' must be a string");
610         }
612         var destPair = self._getDestPair(objOrFunc, funcOrStr);
613         var obj = destPair[0];
614         var func = destPair[1];
615         if (typeof(obj) == 'undefined' || obj === null) {
616             obj = src;
617         }
619         var isDOM = !!(src.addEventListener || src.attachEvent);
620         if (isDOM && (sig === "onmouseenter" || sig === "onmouseleave")
621                   && !self._browserAlreadyHasMouseEnterAndLeave()) {
622             var listener = self._mouseEnterListener(src, sig.substr(2), func, obj);
623             if (sig === "onmouseenter") {
624                 sig = "onmouseover";
625             } else {
626                 sig = "onmouseout";
627             }
628         } else {
629             var listener = self._listener(src, func, obj, isDOM);
630         }
632         if (src.addEventListener) {
633             src.addEventListener(sig.substr(2), listener, false);
634         } else if (src.attachEvent) {
635             src.attachEvent(sig, listener); // useCapture unsupported
636         }
638         var ident = [src, sig, listener, isDOM, objOrFunc, funcOrStr, true];
639         self._observers.push(ident);
642         if (!isDOM && typeof(src.__connect__) == 'function') {
643             var args = MochiKit.Base.extend([ident], arguments, 1);
644             src.__connect__.apply(src, args);
645         }
648         return ident;
649     },
651     _disconnect: function (ident) {
652         // already disconnected
653         if (!ident[6]) { return; }
654         ident[6] = false;
655         // check isDOM
656         if (!ident[3]) { return; }
657         var src = ident[0];
658         var sig = ident[1];
659         var listener = ident[2];
660         if (src.removeEventListener) {
661             src.removeEventListener(sig.substr(2), listener, false);
662         } else if (src.detachEvent) {
663             src.detachEvent(sig, listener); // useCapture unsupported
664         } else {
665             throw new Error("'src' must be a DOM element");
666         }
667     },
669      /** @id MochiKit.Signal.disconnect */
670     disconnect: function (ident) {
671         var self = MochiKit.Signal;
672         var observers = self._observers;
673         var m = MochiKit.Base;
674         if (arguments.length > 1) {
675             // compatibility API
676             var src = MochiKit.DOM.getElement(arguments[0]);
677             var sig = arguments[1];
678             var obj = arguments[2];
679             var func = arguments[3];
680             for (var i = observers.length - 1; i >= 0; i--) {
681                 var o = observers[i];
682                 if (o[0] === src && o[1] === sig && o[4] === obj && o[5] === func) {
683                     self._disconnect(o);
684                     if (!self._lock) {
685                         observers.splice(i, 1);
686                     } else {
687                         self._dirty = true;
688                     }
689                     return true;
690                 }
691             }
692         } else {
693             var idx = m.findIdentical(observers, ident);
694             if (idx >= 0) {
695                 self._disconnect(ident);
696                 if (!self._lock) {
697                     observers.splice(idx, 1);
698                 } else {
699                     self._dirty = true;
700                 }
701                 return true;
702             }
703         }
704         return false;
705     },
707     /** @id MochiKit.Signal.disconnectAllTo */
708     disconnectAllTo: function (objOrFunc, /* optional */funcOrStr) {
709         var self = MochiKit.Signal;
710         var observers = self._observers;
711         var disconnect = self._disconnect;
712         var locked = self._lock;
713         var dirty = self._dirty;
714         if (typeof(funcOrStr) === 'undefined') {
715             funcOrStr = null;
716         }
717         for (var i = observers.length - 1; i >= 0; i--) {
718             var ident = observers[i];
719             if (ident[4] === objOrFunc &&
720                     (funcOrStr === null || ident[5] === funcOrStr)) {
721                 disconnect(ident);
722                 if (locked) {
723                     dirty = true;
724                 } else {
725                     observers.splice(i, 1);
726                 }
727             }
728         }
729         self._dirty = dirty;
730     },
732     /** @id MochiKit.Signal.disconnectAll */
733     disconnectAll: function (src/* optional */, sig) {
734         src = MochiKit.DOM.getElement(src);
735         var m = MochiKit.Base;
736         var signals = m.flattenArguments(m.extend(null, arguments, 1));
737         var self = MochiKit.Signal;
738         var disconnect = self._disconnect;
739         var observers = self._observers;
740         var i, ident;
741         var locked = self._lock;
742         var dirty = self._dirty;
743         if (signals.length === 0) {
744             // disconnect all
745             for (i = observers.length - 1; i >= 0; i--) {
746                 ident = observers[i];
747                 if (ident[0] === src) {
748                     disconnect(ident);
749                     if (!locked) {
750                         observers.splice(i, 1);
751                     } else {
752                         dirty = true;
753                     }
754                 }
755             }
756         } else {
757             var sigs = {};
758             for (i = 0; i < signals.length; i++) {
759                 sigs[signals[i]] = true;
760             }
761             for (i = observers.length - 1; i >= 0; i--) {
762                 ident = observers[i];
763                 if (ident[0] === src && ident[1] in sigs) {
764                     disconnect(ident);
765                     if (!locked) {
766                         observers.splice(i, 1);
767                     } else {
768                         dirty = true;
769                     }
770                 }
771             }
772         }
773         self._dirty = dirty;
774     },
776     /** @id MochiKit.Signal.signal */
777     signal: function (src, sig) {
778         var self = MochiKit.Signal;
779         var observers = self._observers;
780         src = MochiKit.DOM.getElement(src);
781         var args = MochiKit.Base.extend(null, arguments, 2);
782         var errors = [];
783         self._lock = true;
784         for (var i = 0; i < observers.length; i++) {
785             var ident = observers[i];
786             if (ident[0] === src && ident[1] === sig) {
787                 try {
788                     ident[2].apply(src, args);
789                 } catch (e) {
790                     errors.push(e);
791                 }
792             }
793         }
794         self._lock = false;
795         if (self._dirty) {
796             self._dirty = false;
797             for (var i = observers.length - 1; i >= 0; i--) {
798                 if (!observers[i][6]) {
799                     observers.splice(i, 1);
800                 }
801             }
802         }
803         if (errors.length == 1) {
804             throw errors[0];
805         } else if (errors.length > 1) {
806             var e = new Error("Multiple errors thrown in handling 'sig', see errors property");
807             e.errors = errors;
808             throw e;
809         }
810     }
814 MochiKit.Signal.EXPORT_OK = [];
816 MochiKit.Signal.EXPORT = [
817     'connect',
818     'disconnect',
819     'signal',
820     'disconnectAll',
821     'disconnectAllTo'
824 MochiKit.Signal.__new__ = function (win) {
825     var m = MochiKit.Base;
826     this._document = document;
827     this._window = win;
828     this._lock = false;
829     this._dirty = false;
831     try {
832         this.connect(window, 'onunload', this._unloadCache);
833     } catch (e) {
834         // pass: might not be a browser
835     }
837     this.EXPORT_TAGS = {
838         ':common': this.EXPORT,
839         ':all': m.concat(this.EXPORT, this.EXPORT_OK)
840     };
842     m.nameFunctions(this);
845 MochiKit.Signal.__new__(this);
848 // XXX: Internet Explorer blows
850 if (MochiKit.__export__) {
851     connect = MochiKit.Signal.connect;
852     disconnect = MochiKit.Signal.disconnect;
853     disconnectAll = MochiKit.Signal.disconnectAll;
854     signal = MochiKit.Signal.signal;
857 MochiKit.Base._exportSymbols(this, MochiKit.Signal);