tor-browser

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

Async.js (20183B)


      1 /***
      2 
      3 MochiKit.Async 1.4
      4 
      5 See <http://mochikit.com/> for documentation, downloads, license, etc.
      6 
      7 (c) 2005 Bob Ippolito.  All rights Reserved.
      8 
      9 ***/
     10 
     11 if (typeof(dojo) != 'undefined') {
     12    dojo.provide("MochiKit.Async");
     13    dojo.require("MochiKit.Base");
     14 }
     15 if (typeof(JSAN) != 'undefined') {
     16    JSAN.use("MochiKit.Base", []);
     17 }
     18 
     19 try {
     20    if (typeof(MochiKit.Base) == 'undefined') {
     21        throw "";
     22    }
     23 } catch (e) {
     24    throw "MochiKit.Async depends on MochiKit.Base!";
     25 }
     26 
     27 if (typeof(MochiKit.Async) == 'undefined') {
     28    MochiKit.Async = {};
     29 }
     30 
     31 MochiKit.Async.NAME = "MochiKit.Async";
     32 MochiKit.Async.VERSION = "1.4";
     33 MochiKit.Async.__repr__ = function () {
     34    return "[" + this.NAME + " " + this.VERSION + "]";
     35 };
     36 MochiKit.Async.toString = function () {
     37    return this.__repr__();
     38 };
     39 
     40 /** @id MochiKit.Async.Deferred */
     41 MochiKit.Async.Deferred = function (/* optional */ canceller) {
     42    this.chain = [];
     43    this.id = this._nextId();
     44    this.fired = -1;
     45    this.paused = 0;
     46    this.results = [null, null];
     47    this.canceller = canceller;
     48    this.silentlyCancelled = false;
     49    this.chained = false;
     50 };
     51 
     52 MochiKit.Async.Deferred.prototype = {
     53    /** @id MochiKit.Async.Deferred.prototype.repr */
     54    repr: function () {
     55        var state;
     56        if (this.fired == -1) {
     57            state = 'unfired';
     58        } else if (this.fired === 0) {
     59            state = 'success';
     60        } else {
     61            state = 'error';
     62        }
     63        return 'Deferred(' + this.id + ', ' + state + ')';
     64    },
     65 
     66    toString: MochiKit.Base.forwardCall("repr"),
     67 
     68    _nextId: MochiKit.Base.counter(),
     69 
     70    /** @id MochiKit.Async.Deferred.prototype.cancel */
     71    cancel: function () {
     72        var self = MochiKit.Async;
     73        if (this.fired == -1) {
     74            if (this.canceller) {
     75                this.canceller(this);
     76            } else {
     77                this.silentlyCancelled = true;
     78            }
     79            if (this.fired == -1) {
     80                this.errback(new self.CancelledError(this));
     81            }
     82        } else if ((this.fired === 0) && (this.results[0] instanceof self.Deferred)) {
     83            this.results[0].cancel();
     84        }
     85    },
     86 
     87    _resback: function (res) {
     88        /***
     89 
     90        The primitive that means either callback or errback
     91 
     92        ***/
     93        this.fired = ((res instanceof Error) ? 1 : 0);
     94        this.results[this.fired] = res;
     95        this._fire();
     96    },
     97 
     98    _check: function () {
     99        if (this.fired != -1) {
    100            if (!this.silentlyCancelled) {
    101                throw new MochiKit.Async.AlreadyCalledError(this);
    102            }
    103            this.silentlyCancelled = false;
    104            return;
    105        }
    106    },
    107 
    108    /** @id MochiKit.Async.Deferred.prototype.callback */
    109    callback: function (res) {
    110        this._check();
    111        if (res instanceof MochiKit.Async.Deferred) {
    112            throw new Error("Deferred instances can only be chained if they are the result of a callback");
    113        }
    114        this._resback(res);
    115    },
    116 
    117    /** @id MochiKit.Async.Deferred.prototype.errback */
    118    errback: function (res) {
    119        this._check();
    120        var self = MochiKit.Async;
    121        if (res instanceof self.Deferred) {
    122            throw new Error("Deferred instances can only be chained if they are the result of a callback");
    123        }
    124        if (!(res instanceof Error)) {
    125            res = new self.GenericError(res);
    126        }
    127        this._resback(res);
    128    },
    129 
    130    /** @id MochiKit.Async.Deferred.prototype.addBoth */
    131    addBoth: function (fn) {
    132        if (arguments.length > 1) {
    133            fn = MochiKit.Base.partial.apply(null, arguments);
    134        }
    135        return this.addCallbacks(fn, fn);
    136    },
    137 
    138    /** @id MochiKit.Async.Deferred.prototype.addCallback */
    139    addCallback: function (fn) {
    140        if (arguments.length > 1) {
    141            fn = MochiKit.Base.partial.apply(null, arguments);
    142        }
    143        return this.addCallbacks(fn, null);
    144    },
    145 
    146    /** @id MochiKit.Async.Deferred.prototype.addErrback */
    147    addErrback: function (fn) {
    148        if (arguments.length > 1) {
    149            fn = MochiKit.Base.partial.apply(null, arguments);
    150        }
    151        return this.addCallbacks(null, fn);
    152    },
    153 
    154    /** @id MochiKit.Async.Deferred.prototype.addCallbacks */
    155    addCallbacks: function (cb, eb) {
    156        if (this.chained) {
    157            throw new Error("Chained Deferreds can not be re-used");
    158        }
    159        this.chain.push([cb, eb]);
    160        if (this.fired >= 0) {
    161            this._fire();
    162        }
    163        return this;
    164    },
    165 
    166    _fire: function () {
    167        /***
    168 
    169        Used internally to exhaust the callback sequence when a result
    170        is available.
    171 
    172        ***/
    173        var chain = this.chain;
    174        var fired = this.fired;
    175        var res = this.results[fired];
    176        var self = this;
    177        var cb = null;
    178        while (chain.length > 0 && this.paused === 0) {
    179            // Array
    180            var pair = chain.shift();
    181            var f = pair[fired];
    182            if (f === null) {
    183                continue;
    184            }
    185            try {
    186                res = f(res);
    187                fired = ((res instanceof Error) ? 1 : 0);
    188                if (res instanceof MochiKit.Async.Deferred) {
    189                    cb = function (res) {
    190                        self._resback(res);
    191                        self.paused--;
    192                        if ((self.paused === 0) && (self.fired >= 0)) {
    193                            self._fire();
    194                        }
    195                    };
    196                    this.paused++;
    197                }
    198            } catch (err) {
    199                fired = 1;
    200                if (!(err instanceof Error)) {
    201                    err = new MochiKit.Async.GenericError(err);
    202                }
    203                res = err;
    204            }
    205        }
    206        this.fired = fired;
    207        this.results[fired] = res;
    208        if (cb && this.paused) {
    209            // this is for "tail recursion" in case the dependent deferred
    210            // is already fired
    211            res.addBoth(cb);
    212            res.chained = true;
    213        }
    214    }
    215 };
    216 
    217 MochiKit.Base.update(MochiKit.Async, {
    218    /** @id MochiKit.Async.evalJSONRequest */
    219    evalJSONRequest: function (req) {
    220        return MochiKit.Base.evalJSON(req.responseText);
    221    },
    222 
    223    /** @id MochiKit.Async.succeed */
    224    succeed: function (/* optional */result) {
    225        var d = new MochiKit.Async.Deferred();
    226        d.callback.apply(d, arguments);
    227        return d;
    228    },
    229 
    230    /** @id MochiKit.Async.fail */
    231    fail: function (/* optional */result) {
    232        var d = new MochiKit.Async.Deferred();
    233        d.errback.apply(d, arguments);
    234        return d;
    235    },
    236 
    237    /** @id MochiKit.Async.getXMLHttpRequest */
    238    getXMLHttpRequest: function () {
    239        var self = arguments.callee;
    240        if (!self.XMLHttpRequest) {
    241            var tryThese = [
    242                function () { return new XMLHttpRequest(); },
    243                function () { return new ActiveXObject('Msxml2.XMLHTTP'); },
    244                function () { return new ActiveXObject('Microsoft.XMLHTTP'); },
    245                function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); },
    246                function () {
    247                    throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest");
    248                }
    249            ];
    250            for (var i = 0; i < tryThese.length; i++) {
    251                var func = tryThese[i];
    252                try {
    253                    self.XMLHttpRequest = func;
    254                    return func();
    255                } catch (e) {
    256                    // pass
    257                }
    258            }
    259        }
    260        return self.XMLHttpRequest();
    261    },
    262 
    263    _xhr_onreadystatechange: function (d) {
    264        // MochiKit.Logging.logDebug('this.readyState', this.readyState);
    265        var m = MochiKit.Base;
    266        if (this.readyState == 4) {
    267            // IE SUCKS
    268            try {
    269                this.onreadystatechange = null;
    270            } catch (e) {
    271                try {
    272                    this.onreadystatechange = m.noop;
    273                } catch (e) {
    274                }
    275            }
    276            var status = null;
    277            try {
    278                status = this.status;
    279                if (!status && m.isNotEmpty(this.responseText)) {
    280                    // 0 or undefined seems to mean cached or local
    281                    status = 304;
    282                }
    283            } catch (e) {
    284                // pass
    285                // MochiKit.Logging.logDebug('error getting status?', repr(items(e)));
    286            }
    287            // 200 is OK, 201 is CREATED, 204 is NO CONTENT
    288            // 304 is NOT MODIFIED, 1223 is apparently a bug in IE
    289            if (status == 200 || status == 201 || status == 204 ||
    290                    status == 304 || status == 1223) {
    291                d.callback(this);
    292            } else {
    293                var err = new MochiKit.Async.XMLHttpRequestError(this, "Request failed");
    294                if (err.number) {
    295                    // XXX: This seems to happen on page change
    296                    d.errback(err);
    297                } else {
    298                    // XXX: this seems to happen when the server is unreachable
    299                    d.errback(err);
    300                }
    301            }
    302        }
    303    },
    304 
    305    _xhr_canceller: function (req) {
    306        // IE SUCKS
    307        try {
    308            req.onreadystatechange = null;
    309        } catch (e) {
    310            try {
    311                req.onreadystatechange = MochiKit.Base.noop;
    312            } catch (e) {
    313            }
    314        }
    315        req.abort();
    316    },
    317 
    318 
    319    /** @id MochiKit.Async.sendXMLHttpRequest */
    320    sendXMLHttpRequest: function (req, /* optional */ sendContent) {
    321        if (typeof(sendContent) == "undefined" || sendContent === null) {
    322            sendContent = "";
    323        }
    324 
    325        var m = MochiKit.Base;
    326        var self = MochiKit.Async;
    327        var d = new self.Deferred(m.partial(self._xhr_canceller, req));
    328 
    329        try {
    330            req.onreadystatechange = m.bind(self._xhr_onreadystatechange,
    331                req, d);
    332            req.send(sendContent);
    333        } catch (e) {
    334            try {
    335                req.onreadystatechange = null;
    336            } catch (ignore) {
    337                // pass
    338            }
    339            d.errback(e);
    340        }
    341 
    342        return d;
    343 
    344    },
    345 
    346    /** @id MochiKit.Async.doXHR */
    347    doXHR: function (url, opts) {
    348        /*
    349            Work around a Firefox bug by dealing with XHR during
    350            the next event loop iteration. Maybe it's this one:
    351            https://bugzilla.mozilla.org/show_bug.cgi?id=249843
    352        */
    353        var self = MochiKit.Async;
    354        return self.callLater(0, self._doXHR, url, opts);
    355    },
    356 
    357    _doXHR: function (url, opts) {
    358        var m = MochiKit.Base;
    359        opts = m.update({
    360            method: 'GET',
    361            sendContent: ''
    362            /*
    363            queryString: undefined,
    364            username: undefined,
    365            password: undefined,
    366            headers: undefined,
    367            mimeType: undefined
    368            */
    369        }, opts);
    370        var self = MochiKit.Async;
    371        var req = self.getXMLHttpRequest();
    372        if (opts.queryString) {
    373            var qs = m.queryString(opts.queryString);
    374            if (qs) {
    375                url += "?" + qs;
    376            }
    377        }
    378        // Safari will send undefined:undefined, so we have to check.
    379        // We can't use apply, since the function is native.
    380        if ('username' in opts) {
    381            req.open(opts.method, url, true, opts.username, opts.password);
    382        } else {
    383            req.open(opts.method, url, true);
    384        }
    385        if (req.overrideMimeType && opts.mimeType) {
    386            req.overrideMimeType(opts.mimeType);
    387        }
    388        if (opts.headers) {
    389            var headers = opts.headers;
    390            if (!m.isArrayLike(headers)) {
    391                headers = m.items(headers);
    392            }
    393            for (var i = 0; i < headers.length; i++) {
    394                var header = headers[i];
    395                var name = header[0];
    396                var value = header[1];
    397                req.setRequestHeader(name, value);
    398            }
    399        }
    400        return self.sendXMLHttpRequest(req, opts.sendContent);
    401    },
    402 
    403    _buildURL: function (url/*, ...*/) {
    404        if (arguments.length > 1) {
    405            var m = MochiKit.Base;
    406            var qs = m.queryString.apply(null, m.extend(null, arguments, 1));
    407            if (qs) {
    408                return url + "?" + qs;
    409            }
    410        }
    411        return url;
    412    },
    413 
    414    /** @id MochiKit.Async.doSimpleXMLHttpRequest */
    415    doSimpleXMLHttpRequest: function (url/*, ...*/) {
    416        var self = MochiKit.Async;
    417        url = self._buildURL.apply(self, arguments);
    418        return self.doXHR(url);
    419    },
    420 
    421    /** @id MochiKit.Async.loadJSONDoc */
    422    loadJSONDoc: function (url/*, ...*/) {
    423        var self = MochiKit.Async;
    424        url = self._buildURL.apply(self, arguments);
    425        var d = self.doXHR(url, {
    426            'mimeType': 'text/plain',
    427            'headers': [['Accept', 'application/json']]
    428        });
    429        d = d.addCallback(self.evalJSONRequest);
    430        return d;
    431    },
    432 
    433    /** @id MochiKit.Async.wait */
    434    wait: function (seconds, /* optional */value) {
    435        var d = new MochiKit.Async.Deferred();
    436        var m = MochiKit.Base;
    437        if (typeof(value) != 'undefined') {
    438            d.addCallback(function () { return value; });
    439        }
    440        var timeout = setTimeout(
    441            m.bind("callback", d),
    442            Math.floor(seconds * 1000));
    443        d.canceller = function () {
    444            try {
    445                clearTimeout(timeout);
    446            } catch (e) {
    447                // pass
    448            }
    449        };
    450        return d;
    451    },
    452 
    453    /** @id MochiKit.Async.callLater */
    454    callLater: function (seconds, func) {
    455        var m = MochiKit.Base;
    456        var pfunc = m.partial.apply(m, m.extend(null, arguments, 1));
    457        return MochiKit.Async.wait(seconds).addCallback(
    458            function (res) { return pfunc(); }
    459        );
    460    }
    461 });
    462 
    463 
    464 /** @id MochiKit.Async.DeferredLock */
    465 MochiKit.Async.DeferredLock = function () {
    466    this.waiting = [];
    467    this.locked = false;
    468    this.id = this._nextId();
    469 };
    470 
    471 MochiKit.Async.DeferredLock.prototype = {
    472    __class__: MochiKit.Async.DeferredLock,
    473    /** @id MochiKit.Async.DeferredLock.prototype.acquire */
    474    acquire: function () {
    475        var d = new MochiKit.Async.Deferred();
    476        if (this.locked) {
    477            this.waiting.push(d);
    478        } else {
    479            this.locked = true;
    480            d.callback(this);
    481        }
    482        return d;
    483    },
    484    /** @id MochiKit.Async.DeferredLock.prototype.release */
    485    release: function () {
    486        if (!this.locked) {
    487            throw TypeError("Tried to release an unlocked DeferredLock");
    488        }
    489        this.locked = false;
    490        if (this.waiting.length > 0) {
    491            this.locked = true;
    492            this.waiting.shift().callback(this);
    493        }
    494    },
    495    _nextId: MochiKit.Base.counter(),
    496    repr: function () {
    497        var state;
    498        if (this.locked) {
    499            state = 'locked, ' + this.waiting.length + ' waiting';
    500        } else {
    501            state = 'unlocked';
    502        }
    503        return 'DeferredLock(' + this.id + ', ' + state + ')';
    504    },
    505    toString: MochiKit.Base.forwardCall("repr")
    506 
    507 };
    508 
    509 /** @id MochiKit.Async.DeferredList */
    510 MochiKit.Async.DeferredList = function (list, /* optional */fireOnOneCallback, fireOnOneErrback, consumeErrors, canceller) {
    511 
    512    // call parent constructor
    513    MochiKit.Async.Deferred.apply(this, [canceller]);
    514 
    515    this.list = list;
    516    var resultList = [];
    517    this.resultList = resultList;
    518 
    519    this.finishedCount = 0;
    520    this.fireOnOneCallback = fireOnOneCallback;
    521    this.fireOnOneErrback = fireOnOneErrback;
    522    this.consumeErrors = consumeErrors;
    523 
    524    var cb = MochiKit.Base.bind(this._cbDeferred, this);
    525    for (var i = 0; i < list.length; i++) {
    526        var d = list[i];
    527        resultList.push(undefined);
    528        d.addCallback(cb, i, true);
    529        d.addErrback(cb, i, false);
    530    }
    531 
    532    if (list.length === 0 && !fireOnOneCallback) {
    533        this.callback(this.resultList);
    534    }
    535 
    536 };
    537 
    538 MochiKit.Async.DeferredList.prototype = new MochiKit.Async.Deferred();
    539 
    540 MochiKit.Async.DeferredList.prototype._cbDeferred = function (index, succeeded, result) {
    541    this.resultList[index] = [succeeded, result];
    542    this.finishedCount += 1;
    543    if (this.fired == -1) {
    544        if (succeeded && this.fireOnOneCallback) {
    545            this.callback([index, result]);
    546        } else if (!succeeded && this.fireOnOneErrback) {
    547            this.errback(result);
    548        } else if (this.finishedCount == this.list.length) {
    549            this.callback(this.resultList);
    550        }
    551    }
    552    if (!succeeded && this.consumeErrors) {
    553        result = null;
    554    }
    555    return result;
    556 };
    557 
    558 /** @id MochiKit.Async.gatherResults */
    559 MochiKit.Async.gatherResults = function (deferredList) {
    560    var d = new MochiKit.Async.DeferredList(deferredList, false, true, false);
    561    d.addCallback(function (results) {
    562        var ret = [];
    563        for (var i = 0; i < results.length; i++) {
    564            ret.push(results[i][1]);
    565        }
    566        return ret;
    567    });
    568    return d;
    569 };
    570 
    571 /** @id MochiKit.Async.maybeDeferred */
    572 MochiKit.Async.maybeDeferred = function (func) {
    573    var self = MochiKit.Async;
    574    var result;
    575    try {
    576        var r = func.apply(null, MochiKit.Base.extend([], arguments, 1));
    577        if (r instanceof self.Deferred) {
    578            result = r;
    579        } else if (r instanceof Error) {
    580            result = self.fail(r);
    581        } else {
    582            result = self.succeed(r);
    583        }
    584    } catch (e) {
    585        result = self.fail(e);
    586    }
    587    return result;
    588 };
    589 
    590 
    591 MochiKit.Async.EXPORT = [
    592    "AlreadyCalledError",
    593    "CancelledError",
    594    "BrowserComplianceError",
    595    "GenericError",
    596    "XMLHttpRequestError",
    597    "Deferred",
    598    "succeed",
    599    "fail",
    600    "getXMLHttpRequest",
    601    "doSimpleXMLHttpRequest",
    602    "loadJSONDoc",
    603    "wait",
    604    "callLater",
    605    "sendXMLHttpRequest",
    606    "DeferredLock",
    607    "DeferredList",
    608    "gatherResults",
    609    "maybeDeferred",
    610    "doXHR"
    611 ];
    612 
    613 MochiKit.Async.EXPORT_OK = [
    614    "evalJSONRequest"
    615 ];
    616 
    617 MochiKit.Async.__new__ = function () {
    618    var m = MochiKit.Base;
    619    var ne = m.partial(m._newNamedError, this);
    620 
    621    ne("AlreadyCalledError",
    622        /** @id MochiKit.Async.AlreadyCalledError */
    623        function (deferred) {
    624            /***
    625 
    626            Raised by the Deferred if callback or errback happens
    627            after it was already fired.
    628 
    629            ***/
    630            this.deferred = deferred;
    631        }
    632    );
    633 
    634    ne("CancelledError",
    635        /** @id MochiKit.Async.CancelledError */
    636        function (deferred) {
    637            /***
    638 
    639            Raised by the Deferred cancellation mechanism.
    640 
    641            ***/
    642            this.deferred = deferred;
    643        }
    644    );
    645 
    646    ne("BrowserComplianceError",
    647        /** @id MochiKit.Async.BrowserComplianceError */
    648        function (msg) {
    649            /***
    650 
    651            Raised when the JavaScript runtime is not capable of performing
    652            the given function.  Technically, this should really never be
    653            raised because a non-conforming JavaScript runtime probably
    654            isn't going to support exceptions in the first place.
    655 
    656            ***/
    657            this.message = msg;
    658        }
    659    );
    660 
    661    ne("GenericError",
    662        /** @id MochiKit.Async.GenericError */
    663        function (msg) {
    664            this.message = msg;
    665        }
    666    );
    667 
    668    ne("XMLHttpRequestError",
    669        /** @id MochiKit.Async.XMLHttpRequestError */
    670        function (req, msg) {
    671            /***
    672 
    673            Raised when an XMLHttpRequest does not complete for any reason.
    674 
    675            ***/
    676            this.req = req;
    677            this.message = msg;
    678            try {
    679                // Strange but true that this can raise in some cases.
    680                this.number = req.status;
    681            } catch (e) {
    682                // pass
    683            }
    684        }
    685    );
    686 
    687 
    688    this.EXPORT_TAGS = {
    689        ":common": this.EXPORT,
    690        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
    691    };
    692 
    693    m.nameFunctions(this);
    694 
    695 };
    696 
    697 MochiKit.Async.__new__();
    698 
    699 MochiKit.Base._exportSymbols(this, MochiKit.Async);