--- a/dom/base/DOMCursor.h
+++ b/dom/base/DOMCursor.h
@@ -38,16 +38,21 @@ public:
void Reset();
void FireDone();
protected:
~DOMCursor() {}
private:
DOMCursor() MOZ_DELETE;
+ // Calling Then() on DOMCursor is a mistake, since the DOMCursor object
+ // should not have a .then() method from JS' point of view.
+ already_AddRefed<mozilla::dom::Promise>
+ Then(JSContext* aCx, AnyCallback* aResolveCallback,
+ AnyCallback* aRejectCallback, ErrorResult& aRv) MOZ_DELETE;
nsCOMPtr<nsICursorContinueCallback> mCallback;
bool mFinished;
};
} // namespace dom
} // namespace mozilla
--- a/dom/base/DOMRequest.cpp
+++ b/dom/base/DOMRequest.cpp
@@ -5,41 +5,54 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DOMRequest.h"
#include "DOMError.h"
#include "nsThreadUtils.h"
#include "DOMCursor.h"
#include "nsIDOMEvent.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ScriptSettings.h"
+using mozilla::dom::AnyCallback;
using mozilla::dom::DOMError;
using mozilla::dom::DOMRequest;
using mozilla::dom::DOMRequestService;
using mozilla::dom::DOMCursor;
+using mozilla::dom::Promise;
using mozilla::AutoSafeJSContext;
DOMRequest::DOMRequest(nsPIDOMWindow* aWindow)
: DOMEventTargetHelper(aWindow->IsInnerWindow() ?
aWindow : aWindow->GetCurrentInnerWindow())
, mResult(JSVAL_VOID)
, mDone(false)
{
}
+DOMRequest::~DOMRequest()
+{
+ mResult.setUndefined();
+ mozilla::DropJSObjects(this);
+}
+
NS_IMPL_CYCLE_COLLECTION_CLASS(DOMRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMRequest,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMRequest,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
tmp->mResult = JSVAL_VOID;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DOMRequest,
DOMEventTargetHelper)
// Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
// DOMEventTargetHelper does it for us.
NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mResult)
@@ -102,56 +115,72 @@ DOMRequest::FireSuccess(JS::Handle<JS::V
mDone = true;
if (aResult.isGCThing()) {
RootResultVal();
}
mResult = aResult;
FireEvent(NS_LITERAL_STRING("success"), false, false);
+
+ if (mPromise) {
+ mPromise->MaybeResolve(mResult);
+ }
}
void
DOMRequest::FireError(const nsAString& aError)
{
NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!");
NS_ASSERTION(!mError, "mError shouldn't have been set!");
NS_ASSERTION(mResult == JSVAL_VOID, "mResult shouldn't have been set!");
mDone = true;
mError = new DOMError(GetOwner(), aError);
FireEvent(NS_LITERAL_STRING("error"), true, true);
+
+ if (mPromise) {
+ mPromise->MaybeRejectBrokenly(mError);
+ }
}
void
DOMRequest::FireError(nsresult aError)
{
NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!");
NS_ASSERTION(!mError, "mError shouldn't have been set!");
NS_ASSERTION(mResult == JSVAL_VOID, "mResult shouldn't have been set!");
mDone = true;
mError = new DOMError(GetOwner(), aError);
FireEvent(NS_LITERAL_STRING("error"), true, true);
+
+ if (mPromise) {
+ mPromise->MaybeRejectBrokenly(mError);
+ }
}
void
DOMRequest::FireDetailedError(DOMError* aError)
{
NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!");
NS_ASSERTION(!mError, "mError shouldn't have been set!");
NS_ASSERTION(mResult == JSVAL_VOID, "mResult shouldn't have been set!");
NS_ASSERTION(aError, "No detailed error provided");
mDone = true;
mError = aError;
FireEvent(NS_LITERAL_STRING("error"), true, true);
+
+ if (mPromise) {
+ mPromise->MaybeRejectBrokenly(mError);
+ }
}
void
DOMRequest::FireEvent(const nsAString& aType, bool aBubble, bool aCancelable)
{
if (NS_FAILED(CheckInnerWindowCorrectness())) {
return;
}
@@ -170,16 +199,41 @@ DOMRequest::FireEvent(const nsAString& a
}
void
DOMRequest::RootResultVal()
{
mozilla::HoldJSObjects(this);
}
+already_AddRefed<Promise>
+DOMRequest::Then(JSContext* aCx, AnyCallback* aResolveCallback,
+ AnyCallback* aRejectCallback, mozilla::ErrorResult& aRv)
+{
+ if (!mPromise) {
+ mPromise = Promise::Create(DOMEventTargetHelper::GetParentObject(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ if (mDone) {
+ // Since we create mPromise lazily, it's possible that the DOMRequest object
+ // has already fired its success/error event. In that case we should
+ // manually resolve/reject mPromise here. mPromise will take care of
+ // calling the callbacks on |promise| as needed.
+ if (mError) {
+ mPromise->MaybeRejectBrokenly(mError);
+ } else {
+ mPromise->MaybeResolve(mResult);
+ }
+ }
+ }
+
+ return mPromise->Then(aCx, aResolveCallback, aRejectCallback, aRv);
+}
+
NS_IMPL_ISUPPORTS(DOMRequestService, nsIDOMRequestService)
NS_IMETHODIMP
DOMRequestService::CreateRequest(nsIDOMWindow* aWindow,
nsIDOMDOMRequest** aRequest)
{
nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(aWindow));
NS_ENSURE_STATE(win);
@@ -248,17 +302,17 @@ public:
// Due to the fact that initialization can fail during shutdown (since we
// can't fetch a js context), set up an initiatization function to make sure
// we can return the failure appropriately
static nsresult
Dispatch(DOMRequest* aRequest,
const JS::Value& aResult)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
- AutoSafeJSContext cx;
+ mozilla::ThreadsafeAutoSafeJSContext cx;
nsRefPtr<FireSuccessAsyncTask> asyncTask = new FireSuccessAsyncTask(cx, aRequest, aResult);
if (NS_FAILED(NS_DispatchToMainThread(asyncTask))) {
NS_WARNING("Failed to dispatch to main thread!");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
--- a/dom/base/DOMRequest.h
+++ b/dom/base/DOMRequest.h
@@ -11,24 +11,31 @@
#include "mozilla/Attributes.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/DOMError.h"
#include "mozilla/dom/DOMRequestBinding.h"
#include "nsCOMPtr.h"
namespace mozilla {
+
+class ErrorResult;
+
namespace dom {
+class AnyCallback;
+class Promise;
+
class DOMRequest : public DOMEventTargetHelper,
public nsIDOMDOMRequest
{
protected:
JS::Heap<JS::Value> mResult;
nsRefPtr<DOMError> mError;
+ nsRefPtr<Promise> mPromise;
bool mDone;
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIDOMDOMREQUEST
NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(DOMRequest,
@@ -62,30 +69,29 @@ public:
NS_ASSERTION(mDone || !mError,
"Error should be null when pending");
return mError;
}
IMPL_EVENT_HANDLER(success)
IMPL_EVENT_HANDLER(error)
+ already_AddRefed<mozilla::dom::Promise>
+ Then(JSContext* aCx, AnyCallback* aResolveCallback,
+ AnyCallback* aRejectCallback, mozilla::ErrorResult& aRv);
void FireSuccess(JS::Handle<JS::Value> aResult);
void FireError(const nsAString& aError);
void FireError(nsresult aError);
void FireDetailedError(DOMError* aError);
explicit DOMRequest(nsPIDOMWindow* aWindow);
protected:
- virtual ~DOMRequest()
- {
- mResult = JSVAL_VOID;
- mozilla::DropJSObjects(this);
- }
+ virtual ~DOMRequest();
void FireEvent(const nsAString& aType, bool aBubble, bool aCancelable);
void RootResultVal();
};
class DOMRequestService MOZ_FINAL : public nsIDOMRequestService
{
--- a/dom/base/test/test_domcursor.html
+++ b/dom/base/test/test_domcursor.html
@@ -46,16 +46,17 @@ var tests = [
});
ok("result" in req, "cursor has result");
ok("error" in req, "cursor has error");
ok("onsuccess" in req, "cursor has onsuccess");
ok("onerror" in req, "cursor has onerror");
ok("readyState" in req, "cursor has readyState");
ok("done" in req, "cursor has finished");
ok("continue" in req, "cursor has continue");
+ ok(!("then" in req), "cursor should not have a then method");
is(req.readyState, "pending", "readyState is pending");
is(req.result, undefined, "result is undefined");
is(req.onsuccess, null, "onsuccess is null");
is(req.onerror, null, "onerror is null");
next();
},
function() {
--- a/dom/base/test/test_domrequest.html
+++ b/dom/base/test/test_domrequest.html
@@ -12,65 +12,218 @@
</div>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
"use strict";
var reqserv = SpecialPowers.getDOMRequestService();
ok("createRequest" in reqserv, "appears to be a service");
-// create a request
-var req = reqserv.createRequest(window);
-ok("result" in req, "request has result");
-ok("error" in req, "request has error");
-ok("onsuccess" in req, "request has onsuccess");
-ok("onerror" in req, "request has onerror");
-ok("readyState" in req, "request has readyState");
+function testBasics() {
+ // create a request
+ var req = reqserv.createRequest(window);
+ ok("result" in req, "request has result");
+ ok("error" in req, "request has error");
+ ok("onsuccess" in req, "request has onsuccess");
+ ok("onerror" in req, "request has onerror");
+ ok("readyState" in req, "request has readyState");
+ ok("then" in req, "request has then");
+
+ is(req.readyState, "pending", "readyState is pending");
+ is(req.result, undefined, "result is undefined");
+ is(req.onsuccess, null, "onsuccess is null");
+ is(req.onerror, null, "onerror is null");
+
+ runTest();
+}
-is(req.readyState, "pending", "readyState is pending");
-is(req.result, undefined, "result is undefined");
-is(req.onsuccess, null, "onsuccess is null");
-is(req.onerror, null, "onerror is null");
+function testSuccess() {
+ // fire success
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onsuccess = function(e) {
+ ev = e;
+ }
+ var result = null;
+ var promise = req.then(function(r) {
+ is(r, "my result", "correct result when resolving the promise");
+ result = r;
+ runTest();
+ }, function(e) {
+ ok(false, "promise should not be rejected");
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ reqserv.fireSuccess(req, "my result");
+ ok(ev, "got success event");
+ is(ev.type, "success", "correct type during success");
+ is(ev.target, req, "correct target during success");
+ is(req.readyState, "done", "correct readyState after success");
+ is(req.error, null, "correct error after success");
+ is(req.result, "my result", "correct result after success");
+ is(result, null, "Promise should not be resolved synchronously");
+}
-// fire success
-var ev = null;
-req.onsuccess = function(e) {
- ev = e;
+function testError() {
+ // fire error
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onerror = function(e) {
+ ev = e;
+ }
+ var error = null;
+ var promise = req.then(function(r) {
+ ok(false, "promise should not be resolved");
+ runTest();
+ }, function(e) {
+ ok(e instanceof DOMError, "got error rejection");
+ ok(e === req.error, "got correct error when rejecting the promise");
+ error = e;
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ reqserv.fireError(req, "OhMyError");
+ ok(ev, "got error event");
+ is(ev.type, "error", "correct type during error");
+ is(ev.target, req, "correct target during error");
+ is(req.readyState, "done", "correct readyState after error");
+ is(req.error.name, "OhMyError", "correct error after error");
+ is(req.result, undefined, "correct result after error");
+ is(error, null, "Promise should not be rejected synchronously");
}
-reqserv.fireSuccess(req, "my result");
-ok(ev, "got success event");
-is(ev.type, "success", "correct type during success");
-is(ev.target, req, "correct target during success");
-is(req.readyState, "done", "correct readyState after success");
-is(req.error, null, "correct error after success");
-is(req.result, "my result", "correct result after success");
+
+function testDetailedError() {
+ // fire detailed error
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onerror = function(e) {
+ ev = e;
+ };
+ var error = null;
+ var promise = req.then(function(r) {
+ ok(false, "promise should not be resolved");
+ runTest();
+ }, function(e) {
+ ok(e instanceof DOMError, "got error rejection");
+ ok(e === req.error, "got correct error when rejecting the promise");
+ error = e;
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ reqserv.fireDetailedError(req, new DOMError("detailedError"));
+ ok(ev, "got error event");
+ is(ev.type, "error", "correct type during error");
+ is(ev.target, req, "correct target during error");
+ is(req.readyState, "done", "correct readyState after error");
+ is(req.error.name, "detailedError", "correct error after error");
+ is(req.result, undefined, "correct result after error");
+ is(error, null, "Promise should not be rejected synchronously");
+}
-// fire error
-req = reqserv.createRequest(window);
-ev = null;
-req.onerror = function(e) {
- ev = e;
+function testThenAfterSuccess() {
+ // fire success
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onsuccess = function(e) {
+ ev = e;
+ }
+ reqserv.fireSuccess(req, "my result");
+ ok(ev, "got success event");
+ is(ev.type, "success", "correct type during success");
+ is(ev.target, req, "correct target during success");
+ is(req.readyState, "done", "correct readyState after success");
+ is(req.error, null, "correct error after success");
+ is(req.result, "my result", "correct result after success");
+ var result = null;
+ var promise = req.then(function(r) {
+ is(r, "my result", "correct result when resolving the promise");
+ result = r;
+ runTest();
+ }, function(e) {
+ ok(false, "promise should not be rejected");
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ is(result, null, "Promise should not be resolved synchronously");
}
-reqserv.fireError(req, "OhMyError");
-ok(ev, "got error event");
-is(ev.type, "error", "correct type during error");
-is(ev.target, req, "correct target during error");
-is(req.readyState, "done", "correct readyState after error");
-is(req.error.name, "OhMyError", "correct error after error");
-is(req.result, undefined, "correct result after error");
-// fire detailed error
-req = reqserv.createRequest(window);
-ev = null;
-req.onerror = function(e) {
- ev = e;
-};
-reqserv.fireDetailedError(req, new DOMError("detailedError"));
-ok(ev, "got error event");
-is(ev.type, "error", "correct type during error");
-is(ev.target, req, "correct target during error");
-is(req.readyState, "done", "correct readyState after error");
-is(req.error.name, "detailedError", "correct error after error");
-is(req.result, undefined, "correct result after error");
+function testThenAfterError() {
+ // fire error
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onerror = function(e) {
+ ev = e;
+ }
+ reqserv.fireError(req, "OhMyError");
+ ok(ev, "got error event");
+ is(ev.type, "error", "correct type during error");
+ is(ev.target, req, "correct target during error");
+ is(req.readyState, "done", "correct readyState after error");
+ is(req.error.name, "OhMyError", "correct error after error");
+ is(req.result, undefined, "correct result after error");
+ var error = null;
+ var promise = req.then(function(r) {
+ ok(false, "promise should not be resolved");
+ runTest();
+ }, function(e) {
+ ok(e instanceof DOMError, "got error rejection");
+ ok(e === req.error, "got correct error when rejecting the promise");
+ error = e;
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ is(error, null, "Promise should not be rejected synchronously");
+}
+
+function testDetailedError() {
+ // fire detailed error
+ var req = reqserv.createRequest(window);
+ var ev = null;
+ req.onerror = function(e) {
+ ev = e;
+ };
+ var error = null;
+ var promise = req.then(function(r) {
+ ok(false, "promise should not be resolved");
+ runTest();
+ }, function(e) {
+ ok(e instanceof DOMError, "got error rejection");
+ ok(e === req.error, "got correct error when rejecting the promise");
+ error = e;
+ runTest();
+ });
+ ok(promise instanceof Promise, "then() should return a Promise");
+ reqserv.fireDetailedError(req, new DOMError("detailedError"));
+ ok(ev, "got error event");
+ is(ev.type, "error", "correct type during error");
+ is(ev.target, req, "correct target during error");
+ is(req.readyState, "done", "correct readyState after error");
+ is(req.error.name, "detailedError", "correct error after error");
+ is(req.result, undefined, "correct result after error");
+ is(error, null, "Promise should not be rejected synchronously");
+}
+
+var tests = [
+ testBasics,
+ testSuccess,
+ testError,
+ testDetailedError,
+ testThenAfterSuccess,
+ testThenAfterError,
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
</script>
</pre>
</body>
</html>
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -347,16 +347,20 @@ DOMInterfaces = {
'DOMRectList': {
'headerFile': 'mozilla/dom/DOMRect.h',
},
'DOMRectReadOnly': {
'headerFile': 'mozilla/dom/DOMRect.h',
},
+'DOMRequest': {
+ 'implicitJSContext': [ 'then' ],
+},
+
'DOMSettableTokenList': {
'nativeType': 'nsDOMSettableTokenList',
},
'DOMStringMap': {
'nativeType': 'nsDOMStringMap'
},
--- a/dom/bindings/ToJSValue.h
+++ b/dom/bindings/ToJSValue.h
@@ -231,16 +231,34 @@ ToJSValue(JSContext* aCx,
inline bool
ToJSValue(JSContext* aCx, JS::Handle<JS::Value> aArgument,
JS::MutableHandle<JS::Value> aValue)
{
aValue.set(aArgument);
return MaybeWrapValue(aCx, aValue);
}
+// Accept existing JS values on the Heap (which may not be same-compartment with us
+inline bool
+ToJSValue(JSContext* aCx, const JS::Heap<JS::Value>& aArgument,
+ JS::MutableHandle<JS::Value> aValue)
+{
+ aValue.set(aArgument);
+ return MaybeWrapValue(aCx, aValue);
+}
+
+// Accept existing rooted JS values (which may not be same-compartment with us
+inline bool
+ToJSValue(JSContext* aCx, const JS::Rooted<JS::Value>& aArgument,
+ JS::MutableHandle<JS::Value> aValue)
+{
+ aValue.set(aArgument);
+ return MaybeWrapValue(aCx, aValue);
+}
+
// Accept nsresult, for use in rejections, and create an XPCOM
// exception object representing that nsresult.
bool
ToJSValue(JSContext* aCx,
nsresult aArgument,
JS::MutableHandle<JS::Value> aValue);
// Accept pointers to other things we accept
--- a/dom/telephony/test/marionette/head.js
+++ b/dom/telephony/test/marionette/head.js
@@ -1311,25 +1311,16 @@ function startDSDSTest(test) {
finish();
}
}
function sendMMI(aMmi) {
let deferred = Promise.defer();
telephony.dial(aMmi)
- .then(request => {
- ok(request instanceof DOMRequest,
- "request is instanceof " + request.constructor);
-
- request.addEventListener("success", function(event) {
- deferred.resolve(request.result);
- });
-
- request.addEventListener("error", function(event) {
- deferred.reject(request.error);
- });
+ .then(result => {
+ deferred.resolve(result);
}, cause => {
deferred.reject(cause);
});
return deferred.promise;
}
--- a/dom/webidl/DOMCursor.webidl
+++ b/dom/webidl/DOMCursor.webidl
@@ -1,10 +1,12 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-interface DOMCursor : DOMRequest {
+interface DOMCursor : EventTarget {
readonly attribute boolean done;
[Throws]
void continue();
};
+
+DOMCursor implements DOMRequestShared;
--- a/dom/webidl/DOMRequest.webidl
+++ b/dom/webidl/DOMRequest.webidl
@@ -1,16 +1,27 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
enum DOMRequestReadyState { "pending", "done" };
-interface DOMRequest : EventTarget {
+[NoInterfaceObject]
+interface DOMRequestShared {
readonly attribute DOMRequestReadyState readyState;
readonly attribute any result;
readonly attribute DOMError? error;
attribute EventHandler onsuccess;
attribute EventHandler onerror;
};
+
+interface DOMRequest : EventTarget {
+ // The [TreatNonCallableAsNull] annotation is required since then() should do
+ // nothing instead of throwing errors when non-callable arguments are passed.
+ [NewObject, Throws]
+ Promise<any> then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null,
+ [TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
+};
+
+DOMRequest implements DOMRequestShared;