tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Signal.js (28231B)


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