Bug 839838 - Implement DOMRequest.then; r=sicking
☠☠ backed out by 16a8a5c8b96a ☠ ☠
authorEhsan Akhgari <ehsan@mozilla.com>
Sat, 11 Oct 2014 09:46:01 -0400
changeset 233186 3a89e23a25f07bf29ad5da35dcb04edc772d481b
parent 233185 99531e7a02af22d0bcbf950762ba577fc1b6b711
child 233187 dc10fcd30554c85d2a720a7c5d135ea3ff788bb2
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs839838
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 839838 - Implement DOMRequest.then; r=sicking This is implemented by creating a Promise object internally and forwarding the .then() call to it. Any further callbacks passed to future .then() calls will be added as callbacks on the same internal promise object. We also take care of resolving or rejecting the promise if the success/error event of the DOMRequest object has been fired before .then() is called.
dom/base/DOMCursor.h
dom/base/DOMRequest.cpp
dom/base/DOMRequest.h
dom/base/test/test_domcursor.html
dom/base/test/test_domrequest.html
dom/bindings/Bindings.conf
dom/bindings/ToJSValue.h
dom/telephony/test/marionette/head.js
dom/webidl/DOMCursor.webidl
dom/webidl/DOMRequest.webidl
js/public/RootingAPI.h
--- 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, 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, 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;
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -215,17 +215,17 @@ class Heap : public js::HeapBase<T>
     explicit Heap(T p) { init(p); }
 
     /*
      * For Heap, move semantics are equivalent to copy semantics. In C++, a
      * copy constructor taking const-ref is the way to get a single function
      * that will be used for both lvalue and rvalue copies, so we can simply
      * omit the rvalue variant.
      */
-    explicit Heap(const Heap<T> &p) { init(p.ptr); }
+    Heap(const Heap<T> &p) { init(p.ptr); }
 
     ~Heap() {
         if (js::GCMethods<T>::needsPostBarrier(ptr))
             relocate();
     }
 
     bool operator==(const Heap<T> &other) { return ptr == other.ptr; }
     bool operator!=(const Heap<T> &other) { return ptr != other.ptr; }