password-manager-mirror/frontend/delta/js/MochiKit/Async.js

734 lines
22 KiB
JavaScript

/*
Copyright 2008-2013 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
/***
MochiKit.Async 1.5
See <http://mochikit.com/> for documentation, downloads, license, etc.
(c) 2005 Bob Ippolito. All rights Reserved.
***/
MochiKit.Base.module(MochiKit, 'Async', '1.5', ['Base']);
/** @id MochiKit.Async.Deferred */
MochiKit.Async.Deferred = function (/* optional */ canceller) {
this.chain = [];
this.id = this._nextId();
this.fired = -1;
this.paused = 0;
this.results = [null, null];
this.canceller = canceller;
this.silentlyCancelled = false;
this.chained = false;
this.finalized = false;
};
MochiKit.Async.Deferred.prototype = {
/** @id MochiKit.Async.Deferred.prototype.repr */
repr: function () {
return 'Deferred(' + this.id + ', ' + this.state() + ')';
},
toString: MochiKit.Base.forwardCall("repr"),
_nextId: MochiKit.Base.counter(),
/** @id MochiKit.Async.Deferred.prototype.state */
state: function () {
if (this.fired == -1) {
return 'unfired';
} else if (this.fired === 0) {
return 'success';
} else {
return 'error';
}
},
/** @id MochiKit.Async.Deferred.prototype.cancel */
cancel: function (e) {
var self = MochiKit.Async;
if (this.fired == -1) {
if (this.canceller) {
this.canceller(this);
} else {
this.silentlyCancelled = true;
}
if (this.fired == -1) {
if (typeof(e) === 'string') {
e = new self.GenericError(e);
} else if (!(e instanceof Error)) {
e = new self.CancelledError(this);
}
this.errback(e);
}
} else if ((this.fired === 0) && (this.results[0] instanceof self.Deferred)) {
this.results[0].cancel(e);
}
},
_resback: function (res) {
/***
The primitive that means either callback or errback
***/
this.fired = ((res instanceof Error) ? 1 : 0);
this.results[this.fired] = res;
if (this.paused === 0) {
this._fire();
}
},
_check: function () {
if (this.fired != -1) {
if (!this.silentlyCancelled) {
throw new MochiKit.Async.AlreadyCalledError(this);
}
this.silentlyCancelled = false;
return;
}
},
/** @id MochiKit.Async.Deferred.prototype.callback */
callback: function (res) {
this._check();
if (res instanceof MochiKit.Async.Deferred) {
throw new Error("Deferred instances can only be chained if they are the result of a callback");
}
this._resback(res);
},
/** @id MochiKit.Async.Deferred.prototype.errback */
errback: function (res) {
this._check();
var self = MochiKit.Async;
if (res instanceof self.Deferred) {
throw new Error("Deferred instances can only be chained if they are the result of a callback");
}
if (!(res instanceof Error)) {
res = new self.GenericError(res);
}
this._resback(res);
},
/** @id MochiKit.Async.Deferred.prototype.addBoth */
addBoth: function (fn) {
if (arguments.length > 1) {
fn = MochiKit.Base.partial.apply(null, arguments);
}
return this.addCallbacks(fn, fn);
},
/** @id MochiKit.Async.Deferred.prototype.addCallback */
addCallback: function (fn) {
if (arguments.length > 1) {
fn = MochiKit.Base.partial.apply(null, arguments);
}
return this.addCallbacks(fn, null);
},
/** @id MochiKit.Async.Deferred.prototype.addErrback */
addErrback: function (fn) {
if (arguments.length > 1) {
fn = MochiKit.Base.partial.apply(null, arguments);
}
return this.addCallbacks(null, fn);
},
/** @id MochiKit.Async.Deferred.prototype.addCallbacks */
addCallbacks: function (cb, eb) {
if (this.chained) {
throw new Error("Chained Deferreds can not be re-used");
}
if (this.finalized) {
throw new Error("Finalized Deferreds can not be re-used");
}
this.chain.push([cb, eb]);
if (this.fired >= 0) {
this._fire();
}
return this;
},
/** @id MochiKit.Async.Deferred.prototype.setFinalizer */
setFinalizer: function (fn) {
if (this.chained) {
throw new Error("Chained Deferreds can not be re-used");
}
if (this.finalized) {
throw new Error("Finalized Deferreds can not be re-used");
}
if (arguments.length > 1) {
fn = MochiKit.Base.partial.apply(null, arguments);
}
this._finalizer = fn;
if (this.fired >= 0) {
this._fire();
}
return this;
},
_fire: function () {
/***
Used internally to exhaust the callback sequence when a result
is available.
***/
var chain = this.chain;
var fired = this.fired;
var res = this.results[fired];
var self = this;
var cb = null;
while (chain.length > 0 && this.paused === 0) {
// Array
var pair = chain.shift();
var f = pair[fired];
if (f === null) {
continue;
}
try {
res = f(res);
fired = ((res instanceof Error) ? 1 : 0);
if (res instanceof MochiKit.Async.Deferred) {
cb = function (res) {
self.paused--;
self._resback(res);
};
this.paused++;
}
} catch (err) {
fired = 1;
if (!(err instanceof Error)) {
err = new MochiKit.Async.GenericError(err);
}
res = err;
}
}
this.fired = fired;
this.results[fired] = res;
if (this.chain.length == 0 && this.paused === 0 && this._finalizer) {
this.finalized = true;
this._finalizer(res);
}
if (cb && this.paused) {
// this is for "tail recursion" in case the dependent deferred
// is already fired
res.addBoth(cb);
res.chained = true;
}
}
};
MochiKit.Base.update(MochiKit.Async, {
/** @id MochiKit.Async.evalJSONRequest */
evalJSONRequest: function (req) {
return MochiKit.Base.evalJSON(req.responseText);
},
/** @id MochiKit.Async.succeed */
succeed: function (/* optional */result) {
var d = new MochiKit.Async.Deferred();
d.callback.apply(d, arguments);
return d;
},
/** @id MochiKit.Async.fail */
fail: function (/* optional */result) {
var d = new MochiKit.Async.Deferred();
d.errback.apply(d, arguments);
return d;
},
/** @id MochiKit.Async.getXMLHttpRequest */
getXMLHttpRequest: function () {
var self = arguments.callee;
if (!self.XMLHttpRequest) {
var tryThese = [
function () { return new XMLHttpRequest(); },
function () { return new ActiveXObject('Msxml2.XMLHTTP'); },
function () { return new ActiveXObject('Microsoft.XMLHTTP'); },
function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); },
function () {
throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest");
}
];
for (var i = 0; i < tryThese.length; i++) {
var func = tryThese[i];
try {
self.XMLHttpRequest = func;
return func();
} catch (e) {
// pass
}
}
}
return self.XMLHttpRequest();
},
_xhr_onreadystatechange: function (d) {
// MochiKit.Logging.logDebug('this.readyState', this.readyState);
var m = MochiKit.Base;
if (this.readyState == 4) {
// IE SUCKS
try {
this.onreadystatechange = null;
} catch (e) {
try {
this.onreadystatechange = m.noop;
} catch (e) {
}
}
var status = null;
try {
status = this.status;
if (!status && (this.response || m.isNotEmpty(this.responseText))) {
// 0 or undefined seems to mean cached or local
status = 304;
}
} catch (e) {
// pass
// MochiKit.Logging.logDebug('error getting status?', repr(items(e)));
}
// 200 is OK, 201 is CREATED, 204 is NO CONTENT
// 304 is NOT MODIFIED, 1223 is apparently a bug in IE
if (status == 200 || status == 201 || status == 204 ||
status == 304 || status == 1223) {
d.callback(this);
} else {
var err = new MochiKit.Async.XMLHttpRequestError(this, "Request failed");
if (err.number) {
// XXX: This seems to happen on page change
d.errback(err);
} else {
// XXX: this seems to happen when the server is unreachable
d.errback(err);
}
}
}
},
_xhr_canceller: function (req) {
// IE SUCKS
try {
req.onreadystatechange = null;
} catch (e) {
try {
req.onreadystatechange = MochiKit.Base.noop;
} catch (e) {
}
}
req.abort();
},
/** @id MochiKit.Async.sendXMLHttpRequest */
sendXMLHttpRequest: function (req, /* optional */ sendContent) {
if (typeof(sendContent) == "undefined" || sendContent === null) {
sendContent = "";
}
var m = MochiKit.Base;
var self = MochiKit.Async;
var d = new self.Deferred(m.partial(self._xhr_canceller, req));
try {
req.onreadystatechange = m.bind(self._xhr_onreadystatechange,
req, d);
req.send(sendContent);
} catch (e) {
try {
req.onreadystatechange = null;
} catch (ignore) {
// pass
}
d.errback(e);
}
return d;
},
/** @id MochiKit.Async.doXHR */
doXHR: function (url, opts) {
/*
Work around a Firefox bug by dealing with XHR during
the next event loop iteration. Maybe it's this one:
https://bugzilla.mozilla.org/show_bug.cgi?id=249843
*/
var self = MochiKit.Async;
return self.callLater(0, self._doXHR, url, opts);
},
_doXHR: function (url, opts) {
var m = MochiKit.Base;
opts = m.update({
method: 'GET',
sendContent: ''
/*
queryString: undefined,
username: undefined,
password: undefined,
headers: undefined,
mimeType: undefined,
responseType: undefined,
withCredentials: undefined
*/
}, opts);
var self = MochiKit.Async;
var req = self.getXMLHttpRequest();
if (opts.queryString) {
var qs = m.queryString(opts.queryString);
if (qs) {
url += "?" + qs;
}
}
// Safari will send undefined:undefined, so we have to check.
// We can't use apply, since the function is native.
if ('username' in opts) {
req.open(opts.method, url, true, opts.username, opts.password);
} else {
req.open(opts.method, url, true);
}
if (req.overrideMimeType && opts.mimeType) {
req.overrideMimeType(opts.mimeType);
}
req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
if (opts.headers) {
var headers = opts.headers;
if (!m.isArrayLike(headers)) {
headers = m.items(headers);
}
for (var i = 0; i < headers.length; i++) {
var header = headers[i];
var name = header[0];
var value = header[1];
req.setRequestHeader(name, value);
}
}
if ("responseType" in opts && "responseType" in req) {
req.responseType = opts.responseType;
}
if (opts.withCredentials) {
req.withCredentials = 'true';
}
return self.sendXMLHttpRequest(req, opts.sendContent);
},
_buildURL: function (url/*, ...*/) {
if (arguments.length > 1) {
var m = MochiKit.Base;
var qs = m.queryString.apply(null, m.extend(null, arguments, 1));
if (qs) {
return url + "?" + qs;
}
}
return url;
},
/** @id MochiKit.Async.doSimpleXMLHttpRequest */
doSimpleXMLHttpRequest: function (url/*, ...*/) {
var self = MochiKit.Async;
url = self._buildURL.apply(self, arguments);
return self.doXHR(url);
},
/** @id MochiKit.Async.loadJSONDoc */
loadJSONDoc: function (url/*, ...*/) {
var self = MochiKit.Async;
url = self._buildURL.apply(self, arguments);
var d = self.doXHR(url, {
'mimeType': 'text/plain',
'headers': [['Accept', 'application/json']]
});
d = d.addCallback(self.evalJSONRequest);
return d;
},
/** @id MochiKit.Async.loadScript */
loadScript: function (url) {
var d = new MochiKit.Async.Deferred();
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
script.onload = function () {
script.onload = null;
script.onerror = null;
script.onreadystatechange = null;
script = null;
d.callback();
};
script.onerror = function (msg) {
script.onload = null;
script.onerror = null;
script.onreadystatechange = null;
script = null;
msg = "Failed to load script at " + url + ": " + msg;
d.errback(new URIError(msg, url));
}
script.onreadystatechange = function () {
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onload();
} else {
// IE doesn't bother to report errors...
MochiKit.Async.callLater(10, script.onerror, "Script loading timed out")
}
};
document.getElementsByTagName("head")[0].appendChild(script);
return d;
},
/** @id MochiKit.Async.wait */
wait: function (seconds, /* optional */value) {
var d = new MochiKit.Async.Deferred();
var cb = MochiKit.Base.bind("callback", d, value);
var timeout = setTimeout(cb, Math.floor(seconds * 1000));
d.canceller = function () {
try {
clearTimeout(timeout);
} catch (e) {
// pass
}
};
return d;
},
/** @id MochiKit.Async.callLater */
callLater: function (seconds, func) {
var m = MochiKit.Base;
var pfunc = m.partial.apply(m, m.extend(null, arguments, 1));
return MochiKit.Async.wait(seconds).addCallback(
function (res) { return pfunc(); }
);
}
});
/** @id MochiKit.Async.DeferredLock */
MochiKit.Async.DeferredLock = function () {
this.waiting = [];
this.locked = false;
this.id = this._nextId();
};
MochiKit.Async.DeferredLock.prototype = {
__class__: MochiKit.Async.DeferredLock,
/** @id MochiKit.Async.DeferredLock.prototype.acquire */
acquire: function () {
var d = new MochiKit.Async.Deferred();
if (this.locked) {
this.waiting.push(d);
} else {
this.locked = true;
d.callback(this);
}
return d;
},
/** @id MochiKit.Async.DeferredLock.prototype.release */
release: function () {
if (!this.locked) {
throw TypeError("Tried to release an unlocked DeferredLock");
}
this.locked = false;
if (this.waiting.length > 0) {
this.locked = true;
this.waiting.shift().callback(this);
}
},
_nextId: MochiKit.Base.counter(),
repr: function () {
var state;
if (this.locked) {
state = 'locked, ' + this.waiting.length + ' waiting';
} else {
state = 'unlocked';
}
return 'DeferredLock(' + this.id + ', ' + state + ')';
},
toString: MochiKit.Base.forwardCall("repr")
};
/** @id MochiKit.Async.DeferredList */
MochiKit.Async.DeferredList = function (list, /* optional */fireOnOneCallback, fireOnOneErrback, consumeErrors, canceller) {
// call parent constructor
MochiKit.Async.Deferred.apply(this, [canceller]);
this.list = list;
var resultList = [];
this.resultList = resultList;
this.finishedCount = 0;
this.fireOnOneCallback = fireOnOneCallback;
this.fireOnOneErrback = fireOnOneErrback;
this.consumeErrors = consumeErrors;
var cb = MochiKit.Base.bind(this._cbDeferred, this);
for (var i = 0; i < list.length; i++) {
var d = list[i];
resultList.push(undefined);
d.addCallback(cb, i, true);
d.addErrback(cb, i, false);
}
if (list.length === 0 && !fireOnOneCallback) {
this.callback(this.resultList);
}
};
MochiKit.Async.DeferredList.prototype = new MochiKit.Async.Deferred();
MochiKit.Async.DeferredList.prototype.constructor = MochiKit.Async.DeferredList;
MochiKit.Async.DeferredList.prototype._cbDeferred = function (index, succeeded, result) {
this.resultList[index] = [succeeded, result];
this.finishedCount += 1;
if (this.fired == -1) {
if (succeeded && this.fireOnOneCallback) {
this.callback([index, result]);
} else if (!succeeded && this.fireOnOneErrback) {
this.errback(result);
} else if (this.finishedCount == this.list.length) {
this.callback(this.resultList);
}
}
if (!succeeded && this.consumeErrors) {
result = null;
}
return result;
};
/** @id MochiKit.Async.gatherResults */
MochiKit.Async.gatherResults = function (deferredList) {
var d = new MochiKit.Async.DeferredList(deferredList, false, true, false);
d.addCallback(function (results) {
var ret = [];
for (var i = 0; i < results.length; i++) {
ret.push(results[i][1]);
}
return ret;
});
return d;
};
/** @id MochiKit.Async.maybeDeferred */
MochiKit.Async.maybeDeferred = function (func) {
var self = MochiKit.Async;
var result;
try {
var r = func.apply(null, MochiKit.Base.extend([], arguments, 1));
if (r instanceof self.Deferred) {
result = r;
} else if (r instanceof Error) {
result = self.fail(r);
} else {
result = self.succeed(r);
}
} catch (e) {
result = self.fail(e);
}
return result;
};
MochiKit.Async.__new__ = function () {
var m = MochiKit.Base;
var ne = m.partial(m._newNamedError, this);
ne("AlreadyCalledError",
/** @id MochiKit.Async.AlreadyCalledError */
function (deferred) {
/***
Raised by the Deferred if callback or errback happens
after it was already fired.
***/
this.deferred = deferred;
}
);
ne("CancelledError",
/** @id MochiKit.Async.CancelledError */
function (deferred) {
/***
Raised by the Deferred cancellation mechanism.
***/
this.deferred = deferred;
}
);
ne("BrowserComplianceError",
/** @id MochiKit.Async.BrowserComplianceError */
function (msg) {
/***
Raised when the JavaScript runtime is not capable of performing
the given function. Technically, this should really never be
raised because a non-conforming JavaScript runtime probably
isn't going to support exceptions in the first place.
***/
this.message = msg;
}
);
ne("GenericError",
/** @id MochiKit.Async.GenericError */
function (msg) {
this.message = msg;
}
);
ne("XMLHttpRequestError",
/** @id MochiKit.Async.XMLHttpRequestError */
function (req, msg) {
/***
Raised when an XMLHttpRequest does not complete for any reason.
***/
this.req = req;
this.message = msg;
try {
// Strange but true that this can raise in some cases.
this.number = req.status;
} catch (e) {
// pass
}
}
);
m.nameFunctions(this);
};
MochiKit.Async.__new__();
MochiKit.Base._exportSymbols(this, MochiKit.Async);