Bug 783420 - Stop using devtools' Promise.jsm, and start using toolkit/addon-sdk/promise/core.js; r=past,jwalker

This commit is contained in:
Nick Fitzgerald 2013-01-08 14:42:00 +02:00
parent 8776b9208e
commit 970b580bdf
6 changed files with 27 additions and 744 deletions

View File

@ -15,7 +15,6 @@ XPCSHELL_TESTS = unit
MOCHITEST_BROWSER_FILES = \
browser_browser_basic.js \
browser_promise_basic.js \
browser_require_basic.js \
browser_templater_basic.js \
browser_toolbar_basic.js \

View File

@ -1,305 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that our Promise implementation works properly
let tempScope = {};
Cu.import("resource://gre/modules/devtools/_Promise.jsm", tempScope);
let Promise = tempScope.Promise;
function test() {
addTab("about:blank", function() {
info("Starting Promise Tests");
testBasic();
});
}
var postResolution;
function testBasic() {
postResolution = new Promise();
ok(postResolution.isPromise, "We have a promise");
ok(!postResolution.isComplete(), "Promise is initially incomplete");
ok(!postResolution.isResolved(), "Promise is initially unresolved");
ok(!postResolution.isRejected(), "Promise is initially unrejected");
// Test resolve() *after* then() in the same context
var reply = postResolution.then(testPostResolution, fail)
.resolve("postResolution");
is(reply, postResolution, "return this; working ok");
}
var preResolution;
function testPostResolution(data) {
is(data, "postResolution", "data is postResolution");
ok(postResolution.isComplete(), "postResolution Promise is complete");
ok(postResolution.isResolved(), "postResolution Promise is resolved");
ok(!postResolution.isRejected(), "postResolution Promise is unrejected");
try {
info("Expected double resolve error");
postResolution.resolve("double resolve");
ok(false, "double resolve");
}
catch (ex) {
// Expected
}
// Test resolve() *before* then() in the same context
preResolution = new Promise();
var reply = preResolution.resolve("preResolution")
.then(testPreResolution, fail);
is(reply, preResolution, "return this; working ok");
}
var laterResolution;
function testPreResolution(data) {
is(data, "preResolution", "data is preResolution");
ok(preResolution.isComplete(), "preResolution Promise is complete");
ok(preResolution.isResolved(), "preResolution Promise is resolved");
ok(!preResolution.isRejected(), "preResolution Promise is unrejected");
// Test resolve() *after* then() in a later context
laterResolution = new Promise();
laterResolution.then(testLaterResolution, fail);
executeSoon(function() {
laterResolution.resolve("laterResolution");
});
}
var laterRejection;
function testLaterResolution(data) {
is(data, "laterResolution", "data is laterResolution");
ok(laterResolution.isComplete(), "laterResolution Promise is complete");
ok(laterResolution.isResolved(), "laterResolution Promise is resolved");
ok(!laterResolution.isRejected(), "laterResolution Promise is unrejected");
// Test reject() *after* then() in a later context
laterRejection = new Promise().then(fail, testLaterRejection);
executeSoon(function() {
laterRejection.reject("laterRejection");
});
}
function testLaterRejection(data) {
is(data, "laterRejection", "data is laterRejection");
ok(laterRejection.isComplete(), "laterRejection Promise is complete");
ok(!laterRejection.isResolved(), "laterRejection Promise is unresolved");
ok(laterRejection.isRejected(), "laterRejection Promise is rejected");
// Test chaining
var orig = new Promise();
orig.chainPromise(function(data) {
is(data, "origData", "data is origData");
return data.replace(/orig/, "new");
}).then(function(data) {
is(data, "newData", "data is newData");
testChain();
});
orig.resolve("origData");
}
var member1;
var member2;
var member3;
var laterGroup;
function testChain() {
// Test an empty group
var empty1 = Promise.group();
ok(empty1.isComplete(), "empty1 Promise is complete");
ok(empty1.isResolved(), "empty1 Promise is resolved");
ok(!empty1.isRejected(), "empty1 Promise is unrejected");
// Test a group with no members
var empty2 = Promise.group([]);
ok(empty2.isComplete(), "empty2 Promise is complete");
ok(empty2.isResolved(), "empty2 Promise is resolved");
ok(!empty2.isRejected(), "empty2 Promise is unrejected");
// Test grouping using resolve() in a later context
member1 = new Promise();
member2 = new Promise();
member3 = new Promise();
laterGroup = Promise.group(member1, member2, member3);
laterGroup.then(testLaterGroup, fail);
member1.then(function(data) {
is(data, "member1", "member1 is member1");
executeSoon(function() {
member2.resolve("member2");
});
}, fail);
member2.then(function(data) {
is(data, "member2", "member2 is member2");
executeSoon(function() {
member3.resolve("member3");
});
}, fail);
member3.then(function(data) {
is(data, "member3", "member3 is member3");
// The group should now fire
}, fail);
executeSoon(function() {
member1.resolve("member1");
});
}
var tidyGroup;
function testLaterGroup(data) {
is(data[0], "member1", "member1 is member1");
is(data[1], "member2", "member2 is member2");
is(data[2], "member3", "member3 is member3");
is(data.length, 3, "data.length is right");
ok(laterGroup.isComplete(), "laterGroup Promise is complete");
ok(laterGroup.isResolved(), "laterGroup Promise is resolved");
ok(!laterGroup.isRejected(), "laterGroup Promise is unrejected");
// Test grouping resolve() *before* then() in the same context
tidyGroup = Promise.group([
postResolution, preResolution, laterResolution,
member1, member2, member3, laterGroup
]);
tidyGroup.then(testTidyGroup, fail);
}
var failGroup;
function testTidyGroup(data) {
is(data[0], "postResolution", "postResolution is postResolution");
is(data[1], "preResolution", "preResolution is preResolution");
is(data[2], "laterResolution", "laterResolution is laterResolution");
is(data[3], "member1", "member1 is member1");
is(data[6][1], "member2", "laterGroup is laterGroup");
is(data.length, 7, "data.length is right");
ok(tidyGroup.isComplete(), "tidyGroup Promise is complete");
ok(tidyGroup.isResolved(), "tidyGroup Promise is resolved");
ok(!tidyGroup.isRejected(), "tidyGroup Promise is unrejected");
// Test grouping resolve() *before* then() in the same context
failGroup = Promise.group(postResolution, laterRejection);
failGroup.then(fail, testFailGroup);
}
function testFailGroup(data) {
is(data, "laterRejection", "laterRejection is laterRejection");
postResolution = undefined;
preResolution = undefined;
laterResolution = undefined;
member1 = undefined;
member2 = undefined;
member3 = undefined;
laterGroup = undefined;
laterRejection = undefined;
testTrap();
}
function testTrap() {
var p = new Promise();
var message = "Expected exception";
p.chainPromise(
function() {
throw new Error(message);
}).trap(
function(aError) {
is(aError instanceof Error, true, "trap received exception");
is(aError.message, message, "trap received correct exception");
return 1;
}).chainPromise(
function(aResult) {
is(aResult, 1, "trap restored correct result");
testAlways();
});
p.resolve();
}
function testAlways() {
var shouldbeTrue1 = false;
var shouldbeTrue2 = false;
var p = new Promise();
p.chainPromise(
function() {
throw new Error();
}
).chainPromise(// Promise rejected, should not be executed
function() {
ok(false, "This should not be executed");
}
).always(
function(x) {
shouldbeTrue1 = true;
return "random value";
}
).trap(
function(arg) {
ok((arg instanceof Error), "The random value should be ignored");
return 1;// We should still have this result later
}
).trap(
function() {
ok(false, "This should not be executed 2");
}
).always(
function() {
shouldbeTrue2 = true;
}
).then(
function(aResult){
ok(shouldbeTrue1, "First always must be executed");
ok(shouldbeTrue2, "Second always must be executed");
is(aResult, 1, "Result should be unaffected by always");
testComplete();
}
);
p.resolve();
}
function fail() {
gBrowser.removeCurrentTab();
info("Failed Promise Tests");
ok(false, "fail called");
finish();
}
/**
* We wish to launch all tests with several configurations (at the moment,
* non-debug and debug mode).
*
* If 0, we have not completed any test yet.
* If 1, we have completed the tests in non-debug mode.
* If 2, we have also completed the tests in debug mode.
*/
var configurationTestComplete = 0;
function testComplete() {
switch (configurationTestComplete) {
case 0:
info("Finished run in non-debug mode");
configurationTestComplete = 1;
Promise.Debug.setDebug(true);
window.setTimeout(testBasic, 0);
return;
case 1:
info("Finished run in debug mode");
configurationTestComplete = 2;
Promise.Debug.setDebug(false);
window.setTimeout(finished, 0);
return;
default:
ok(false, "Internal error in testComplete "+configurationTestComplete);
return;
}
}
function finished() {
gBrowser.removeCurrentTab();
info("Finishing Promise Tests");
finish();
}

View File

@ -11,7 +11,7 @@
var imports = {};
Cu.import("resource:///modules/devtools/Templater.jsm", imports);
Cu.import("resource://gre/modules/devtools/_Promise.jsm", imports);
Cu.import("resource://gre/modules/commonjs/promise/core.js", imports);
function test() {
addTab("http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html", function() {
@ -278,9 +278,9 @@ var tests = [
];
function delayReply(data) {
var p = new imports.Promise();
var d = imports.Promise.defer();
executeSoon(function() {
p.resolve(data);
d.resolve(data);
});
return p;
return d.promise;
}

View File

@ -1,409 +0,0 @@
/*
* Copyright 2009-2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE.txt or:
* http://opensource.org/licenses/BSD-3-Clause
*/
this.EXPORTED_SYMBOLS = [ "Promise" ];
/**
* Create an unfulfilled promise
*
* @param {*=} aTrace A debugging value
*
* @constructor
*/
this.Promise = function Promise(aTrace) {
this._status = Promise.PENDING;
this._value = undefined;
this._onSuccessHandlers = [];
this._onErrorHandlers = [];
this._trace = aTrace;
// Debugging help
if (Promise.Debug._debug) {
this._id = Promise.Debug._nextId++;
Promise.Debug._outstanding[this._id] = this;
}
}
/**
* Debugging options and tools.
*/
Promise.Debug = {
/**
* Set current debugging mode.
*
* @param {boolean} value If |true|, maintain _nextId, _outstanding, _recent.
* Otherwise, cleanup debugging data.
*/
setDebug: function(value) {
Promise.Debug._debug = value;
if (!value) {
Promise.Debug._outstanding = [];
Promise.Debug._recent = [];
}
},
_debug: false,
/**
* We give promises and ID so we can track which are outstanding.
*/
_nextId: 0,
/**
* Outstanding promises. Handy for debugging (only).
*/
_outstanding: [],
/**
* Recently resolved promises. Also for debugging only.
*/
_recent: []
};
/**
* A promise can be in one of 2 states.
* The ERROR and SUCCESS states are terminal, the PENDING state is the only
* start state.
*/
Promise.ERROR = -1;
Promise.PENDING = 0;
Promise.SUCCESS = 1;
/**
* Yeay for RTTI
*/
Promise.prototype.isPromise = true;
/**
* Have we either been resolve()ed or reject()ed?
*/
Promise.prototype.isComplete = function() {
return this._status != Promise.PENDING;
};
/**
* Have we resolve()ed?
*/
Promise.prototype.isResolved = function() {
return this._status == Promise.SUCCESS;
};
/**
* Have we reject()ed?
*/
Promise.prototype.isRejected = function() {
return this._status == Promise.ERROR;
};
/**
* Take the specified action of fulfillment of a promise, and (optionally)
* a different action on promise rejection
*/
Promise.prototype.then = function(onSuccess, onError) {
if (typeof onSuccess === 'function') {
if (this._status === Promise.SUCCESS) {
onSuccess.call(null, this._value);
}
else if (this._status === Promise.PENDING) {
this._onSuccessHandlers.push(onSuccess);
}
}
if (typeof onError === 'function') {
if (this._status === Promise.ERROR) {
onError.call(null, this._value);
}
else if (this._status === Promise.PENDING) {
this._onErrorHandlers.push(onError);
}
}
return this;
};
/**
* Like then() except that rather than returning <tt>this</tt> we return
* a promise which resolves when the original promise resolves
*/
Promise.prototype.chainPromise = function(onSuccess) {
var chain = new Promise();
chain._chainedFrom = this;
this.then(function(data) {
try {
chain.resolve(onSuccess(data));
}
catch (ex) {
chain.reject(ex);
}
}, function(ex) {
chain.reject(ex);
});
return chain;
};
/**
* Supply the fulfillment of a promise
*/
Promise.prototype.resolve = function(data) {
return this._complete(this._onSuccessHandlers,
Promise.SUCCESS, data, 'resolve');
};
/**
* Renege on a promise
*/
Promise.prototype.reject = function(data) {
return this._complete(this._onErrorHandlers, Promise.ERROR, data, 'reject');
};
/**
* Internal method to be called on resolve() or reject()
* @private
*/
Promise.prototype._complete = function(list, status, data, name) {
// Complain if we've already been completed
if (this._status != Promise.PENDING) {
Promise._error("Promise complete.", "Attempted ", name, "() with ", data);
Promise._error("Previous status: ", this._status, ", value =", this._value);
throw new Error('Promise already complete');
}
if (list.length == 0 && status == Promise.ERROR) {
var frame;
var text;
//Complain if a rejection is ignored
//(this is the equivalent of an empty catch-all clause)
Promise._error("Promise rejection ignored and silently dropped", data);
if (data.stack) {// This looks like an exception. Try harder to display it
if (data.fileName && data.lineNumber) {
Promise._error("Error originating at", data.fileName,
", line", data.lineNumber );
}
try {
for (frame = data.stack; frame; frame = frame.caller) {
text += frame + "\n";
}
Promise._error("Attempting to extract exception stack", text);
} catch (x) {
Promise._error("Could not extract exception stack.");
}
} else {
Promise._error("Exception stack not available.");
}
if (Components && Components.stack) {
try {
text = "";
for (frame = Components.stack; frame; frame = frame.caller) {
text += frame + "\n";
}
Promise._error("Attempting to extract current stack", text);
} catch (x) {
Promise._error("Could not extract current stack.");
}
} else {
Promise._error("Current stack not available.");
}
}
this._status = status;
this._value = data;
// Call all the handlers, and then delete them
list.forEach(function(handler) {
handler.call(null, this._value);
}, this);
delete this._onSuccessHandlers;
delete this._onErrorHandlers;
// Remove the given {promise} from the _outstanding list, and add it to the
// _recent list, pruning more than 20 recent promises from that list
delete Promise.Debug._outstanding[this._id];
// The original code includes this very useful debugging aid, however there
// is concern that it will create a memory leak, so we leave it out here.
/*
Promise._recent.push(this);
while (Promise._recent.length > 20) {
Promise._recent.shift();
}
*/
return this;
};
/**
* Log an error on the most appropriate channel.
*
* If the console is available, this method uses |console.warn|. Otherwise,
* this method falls back to |dump|.
*
* @param {...*} items Items to log.
*/
Promise._error = null;
if (typeof console != "undefined" && console.warn) {
Promise._error = function() {
var args = Array.prototype.slice.call(arguments);
args.unshift("Promise");
console.warn.call(console, args);
};
} else {
Promise._error = function() {
var i;
var len = arguments.length;
dump("Promise: ");
for (i = 0; i < len; ++i) {
dump(arguments[i]+" ");
}
dump("\n");
};
}
/**
* Takes an array of promises and returns a promise that that is fulfilled once
* all the promises in the array are fulfilled
* @param promiseList The array of promises
* @return the promise that is fulfilled when all the array is fulfilled
*/
Promise.group = function(promiseList) {
if (!Array.isArray(promiseList)) {
promiseList = Array.prototype.slice.call(arguments);
}
// If the original array has nothing in it, return now to avoid waiting
if (promiseList.length === 0) {
return new Promise().resolve([]);
}
var groupPromise = new Promise();
var results = [];
var fulfilled = 0;
var onSuccessFactory = function(index) {
return function(data) {
results[index] = data;
fulfilled++;
// If the group has already failed, silently drop extra results
if (groupPromise._status !== Promise.ERROR) {
if (fulfilled === promiseList.length) {
groupPromise.resolve(results);
}
}
};
};
promiseList.forEach(function(promise, index) {
var onSuccess = onSuccessFactory(index);
var onError = groupPromise.reject.bind(groupPromise);
promise.then(onSuccess, onError);
});
return groupPromise;
};
/**
* Trap errors.
*
* This function serves as an asynchronous counterpart to |catch|.
*
* Example:
* myPromise.chainPromise(a) //May reject
* .chainPromise(b) //May reject
* .chainPromise(c) //May reject
* .trap(d) //Catch any rejection from a, b or c
* .chainPromise(e) //If either a, b and c or
* //d has resolved, execute
*
* Scenario 1:
* If a, b, c resolve, e is executed as if d had not been added.
*
* Scenario 2:
* If a, b or c rejects, d is executed. If d resolves, we proceed
* with e as if nothing had happened. Otherwise, we proceed with
* the rejection of d.
*
* @param {Function} aTrap Called if |this| promise is rejected,
* with one argument: the rejection.
* @return {Promise} A new promise. This promise resolves if all
* previous promises have resolved or if |aTrap| succeeds.
*/
Promise.prototype.trap = function(aTrap) {
var promise = new Promise();
var resolve = Promise.prototype.resolve.bind(promise);
var reject = function(aRejection) {
try {
//Attempt to handle issue
var result = aTrap.call(aTrap, aRejection);
promise.resolve(result);
} catch (x) {
promise.reject(x);
}
};
this.then(resolve, reject);
return promise;
};
/**
* Execute regardless of errors.
*
* This function serves as an asynchronous counterpart to |finally|.
*
* Example:
* myPromise.chainPromise(a) //May reject
* .chainPromise(b) //May reject
* .chainPromise(c) //May reject
* .always(d) //Executed regardless
* .chainPromise(e)
*
* Whether |a|, |b| or |c| resolve or reject, |d| is executed.
*
* @param {Function} aTrap Called regardless of whether |this|
* succeeds or fails.
* @return {Promise} A new promise. This promise holds the same
* resolution/rejection as |this|.
*/
Promise.prototype.always = function(aTrap) {
var promise = new Promise();
var resolve = function(result) {
try {
aTrap.call(aTrap);
promise.resolve(result);
} catch (x) {
promise.reject(x);
}
};
var reject = function(result) {
try {
aTrap.call(aTrap);
promise.reject(result);
} catch (x) {
promise.reject(result);
}
};
this.then(resolve, reject);
return promise;
};
Promise.prototype.toString = function() {
var status;
switch (this._status) {
case Promise.PENDING:
status = "pending";
break;
case Promise.SUCCESS:
status = "resolved";
break;
case Promise.ERROR:
status = "rejected";
break;
default:
status = "invalid status: "+this._status;
}
return "[Promise " + this._id + " (" + status + ")]";
};

View File

@ -1346,27 +1346,23 @@ SourceActor.prototype = {
* Handler for the "source" packet.
*/
onSource: function SA_onSource(aRequest) {
this
return this
._loadSource()
.chainPromise(function(aSource) {
.then(function(aSource) {
return this._threadActor.createValueGrip(
aSource, this.threadActor.threadLifetimePool);
}.bind(this))
.chainPromise(function (aSourceGrip) {
.then(function (aSourceGrip) {
return {
from: this.actorID,
source: aSourceGrip
};
}.bind(this))
.trap(function (aError) {
}.bind(this), function (aError) {
return {
"from": this.actorID,
"error": "loadSourceError",
"message": "Could not load the source for " + this._script.url + "."
};
}.bind(this))
.chainPromise(function (aPacket) {
this.conn.send(aPacket);
}.bind(this));
},
@ -1404,7 +1400,7 @@ SourceActor.prototype = {
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
*/
_loadSource: function SA__loadSource() {
let promise = new Promise();
let deferred = defer();
let url = this._script.url;
let scheme;
try {
@ -1424,16 +1420,16 @@ SourceActor.prototype = {
try {
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus) {
if (!Components.isSuccessCode(aStatus)) {
promise.reject(new Error("Request failed"));
deferred.reject(new Error("Request failed"));
return;
}
let source = NetUtil.readInputStreamToString(aStream, aStream.available());
promise.resolve(this._convertToUnicode(source));
deferred.resolve(this._convertToUnicode(source));
aStream.close();
}.bind(this));
} catch (ex) {
promise.reject(new Error("Request failed"));
deferred.reject(new Error("Request failed"));
}
break;
@ -1451,7 +1447,7 @@ SourceActor.prototype = {
let streamListener = {
onStartRequest: function(aRequest, aContext, aStatusCode) {
if (!Components.isSuccessCode(aStatusCode)) {
promise.reject("Request failed");
deferred.reject("Request failed");
}
},
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
@ -1459,12 +1455,12 @@ SourceActor.prototype = {
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
if (!Components.isSuccessCode(aStatusCode)) {
promise.reject("Request failed");
deferred.reject("Request failed");
return;
}
promise.resolve(this._convertToUnicode(chunks.join(""),
channel.contentCharset));
deferred.resolve(this._convertToUnicode(chunks.join(""),
channel.contentCharset));
}.bind(this)
};
@ -1473,7 +1469,7 @@ SourceActor.prototype = {
break;
}
return promise;
return deferred.promise;
}
};

View File

@ -24,7 +24,8 @@ let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Cu.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(this);
Cu.import("resource://gre/modules/devtools/_Promise.jsm");
Cu.import("resource://gre/modules/commonjs/promise/core.js");
const { defer, resolve, reject } = Promise;
function dumpn(str) {
if (wantLogging) {
@ -664,16 +665,17 @@ DebuggerServerConnection.prototype = {
}
if (!ret) {
// XXX: The actor wasn't ready to reply yet, don't process new
// requests until it does.
// This should become an error once we've converted every user
// of this to promises in bug 794078.
return;
}
if (!ret.from) {
ret.from = aPacket.to;
}
this.transport.send(ret);
resolve(ret).then(function(returnPacket) {
if (!returnPacket.from) {
returnPacket.from = aPacket.to;
}
this.transport.send(returnPacket);
}.bind(this));
},
/**