Bug 1035060 - Implement AbortablePromise. r=bz
authorYuan Xulei <xyuan@mozilla.com>
Fri, 12 Sep 2014 10:18:49 +0800
changeset 205040 25e20dd15ec50c06c53f1d2b6fc893da2017b959
parent 205039 a925804b5cb262fc0a8c36aca357adb02cf3cdb3
child 205041 a3989e17310c9cd48634cf625841538f50b8d6df
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbz
bugs1035060
milestone35.0a1
Bug 1035060 - Implement AbortablePromise. r=bz
dom/bindings/Bindings.conf
dom/promise/AbortablePromise.cpp
dom/promise/AbortablePromise.h
dom/promise/Promise.cpp
dom/promise/Promise.h
dom/promise/PromiseCallback.cpp
dom/promise/PromiseNativeAbortCallback.h
dom/promise/moz.build
dom/promise/tests/mochitest.ini
dom/promise/tests/test_abortable_promise.html
dom/tests/mochitest/general/test_interfaces.html
dom/webidl/AbortablePromise.webidl
dom/webidl/moz.build
modules/libpref/init/all.js
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -86,16 +86,20 @@
 # entry, just make it not a list.
 
 DOMInterfaces = {
 
 'MozActivity': {
     'nativeType': 'mozilla::dom::Activity',
 },
 
+'MozAbortablePromise': {
+    'nativeType': 'mozilla::dom::AbortablePromise',
+},
+
 'AbstractWorker': {
     'concrete': False
 },
 
 'ArchiveReader': {
     'nativeType': 'mozilla::dom::archivereader::ArchiveReader',
 },
 
new file mode 100644
--- /dev/null
+++ b/dom/promise/AbortablePromise.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/dom/AbortablePromise.h"
+
+#include "mozilla/dom/AbortablePromiseBinding.h"
+#include "mozilla/dom/PromiseNativeAbortCallback.h"
+#include "mozilla/ErrorResult.h"
+#include "nsThreadUtils.h"
+#include "PromiseCallback.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeAbortCallback)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeAbortCallback)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeAbortCallback)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_0(PromiseNativeAbortCallback)
+
+NS_IMPL_ADDREF_INHERITED(AbortablePromise, Promise)
+NS_IMPL_RELEASE_INHERITED(AbortablePromise, Promise)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AbortablePromise)
+NS_INTERFACE_MAP_END_INHERITING(Promise)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(AbortablePromise, Promise, mAbortCallback)
+
+AbortablePromise::AbortablePromise(nsIGlobalObject* aGlobal,
+                                   PromiseNativeAbortCallback& aAbortCallback)
+  : Promise(aGlobal)
+  , mAbortCallback(&aAbortCallback)
+{
+}
+
+AbortablePromise::AbortablePromise(nsIGlobalObject* aGlobal)
+  : Promise(aGlobal)
+{
+}
+
+AbortablePromise::~AbortablePromise()
+{
+}
+
+/* static */ already_AddRefed<AbortablePromise>
+AbortablePromise::Create(nsIGlobalObject* aGlobal,
+                         PromiseNativeAbortCallback& aAbortCallback,
+                         ErrorResult& aRv)
+{
+  nsRefPtr<AbortablePromise> p = new AbortablePromise(aGlobal, aAbortCallback);
+  p->CreateWrapper(aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+  return p.forget();
+}
+
+JSObject*
+AbortablePromise::WrapObject(JSContext* aCx)
+{
+  return MozAbortablePromiseBinding::Wrap(aCx, this);
+}
+
+/* static */ already_AddRefed<AbortablePromise>
+AbortablePromise::Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
+                              AbortCallback& aAbortCallback, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global;
+  global = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!global) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsRefPtr<AbortablePromise> promise = new AbortablePromise(global);
+  promise->CreateWrapper(aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->CallInitFunction(aGlobal, aInit, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->mAbortCallback = &aAbortCallback;
+
+  return promise.forget();
+}
+
+void
+AbortablePromise::Abort()
+{
+  if (IsPending()) {
+    return;
+  }
+  MaybeReject(NS_ERROR_ABORT);
+
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableMethod(this, &AbortablePromise::DoAbort);
+  Promise::DispatchToMainOrWorkerThread(runnable);
+}
+
+void
+AbortablePromise::DoAbort()
+{
+  if (mAbortCallback.HasWebIDLCallback()) {
+    ErrorResult rv;
+    mAbortCallback.GetWebIDLCallback()->Call(rv);
+    return;
+  }
+  mAbortCallback.GetXPCOMCallback()->Call();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/promise/AbortablePromise.h
@@ -0,0 +1,66 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_AbortablePromise_h__
+#define mozilla_dom_AbortablePromise_h__
+
+#include "js/TypeDecls.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/CallbackObject.h"
+
+namespace mozilla {
+namespace dom {
+
+class AbortCallback;
+class PromiseNativeAbortCallback;
+
+class AbortablePromise
+  : public Promise
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AbortablePromise, Promise)
+
+public:
+  // It is the same as Promise::Create except that this takes an extra
+  // aAbortCallback parameter to set the abort callback handler.
+  static already_AddRefed<AbortablePromise>
+  Create(nsIGlobalObject* aGlobal, PromiseNativeAbortCallback& aAbortCallback,
+         ErrorResult& aRv);
+
+protected:
+  // Constructor used to create native AbortablePromise with C++.
+  AbortablePromise(nsIGlobalObject* aGlobal,
+                   PromiseNativeAbortCallback& aAbortCallback);
+
+  // Constructor used to create AbortablePromise for JavaScript. It should be
+  // called by the static AbortablePromise::Constructor.
+  AbortablePromise(nsIGlobalObject* aGlobal);
+
+  virtual ~AbortablePromise();
+
+public:
+  virtual JSObject*
+  WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  static already_AddRefed<AbortablePromise>
+  Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
+              AbortCallback& aAbortCallback, ErrorResult& aRv);
+
+  void Abort();
+
+private:
+  void DoAbort();
+
+  // The callback functions to abort the promise.
+  CallbackObjectHolder<AbortCallback,
+                       PromiseNativeAbortCallback> mAbortCallback;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_AbortablePromise_h__
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -320,36 +320,43 @@ JSObject*
 Promise::WrapObject(JSContext* aCx)
 {
   return PromiseBinding::Wrap(aCx, this);
 }
 
 already_AddRefed<Promise>
 Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv)
 {
+  nsRefPtr<Promise> p = new Promise(aGlobal);
+  p->CreateWrapper(aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+  return p.forget();
+}
+
+void
+Promise::CreateWrapper(ErrorResult& aRv)
+{
   AutoJSAPI jsapi;
-  if (!jsapi.Init(aGlobal)) {
+  if (!jsapi.Init(mGlobal)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
+    return;
   }
   JSContext* cx = jsapi.cx();
 
-  nsRefPtr<Promise> p = new Promise(aGlobal);
-
   JS::Rooted<JS::Value> ignored(cx);
-  if (!WrapNewBindingObject(cx, p, &ignored)) {
+  if (!WrapNewBindingObject(cx, this, &ignored)) {
     JS_ClearPendingException(cx);
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-    return nullptr;
+    return;
   }
 
   // Need the .get() bit here to get template deduction working right
-  dom::PreserveWrapper(p.get());
-
-  return p.forget();
+  dom::PreserveWrapper(this);
 }
 
 void
 Promise::MaybeResolve(JSContext* aCx,
                       JS::Handle<JS::Value> aValue)
 {
   MaybeResolveInternal(aCx, aValue);
 }
@@ -478,64 +485,74 @@ Promise::CreateThenableFunction(JSContex
 
   return obj;
 }
 
 /* static */ already_AddRefed<Promise>
 Promise::Constructor(const GlobalObject& aGlobal,
                      PromiseInit& aInit, ErrorResult& aRv)
 {
-  JSContext* cx = aGlobal.Context();
-
   nsCOMPtr<nsIGlobalObject> global;
   global = do_QueryInterface(aGlobal.GetAsSupports());
   if (!global) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Create(global, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
+  promise->CallInitFunction(aGlobal, aInit, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  return promise.forget();
+}
+
+void
+Promise::CallInitFunction(const GlobalObject& aGlobal,
+                          PromiseInit& aInit, ErrorResult& aRv)
+{
+  JSContext* cx = aGlobal.Context();
+
   JS::Rooted<JSObject*> resolveFunc(cx,
-                                    CreateFunction(cx, aGlobal.Get(), promise,
+                                    CreateFunction(cx, aGlobal.Get(), this,
                                                    PromiseCallback::Resolve));
   if (!resolveFunc) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
+    return;
   }
 
   JS::Rooted<JSObject*> rejectFunc(cx,
-                                   CreateFunction(cx, aGlobal.Get(), promise,
+                                   CreateFunction(cx, aGlobal.Get(), this,
                                                   PromiseCallback::Reject));
   if (!rejectFunc) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
+    return;
   }
 
   aInit.Call(resolveFunc, rejectFunc, aRv, CallbackObject::eRethrowExceptions);
   aRv.WouldReportJSException();
 
   if (aRv.IsJSException()) {
     JS::Rooted<JS::Value> value(cx);
     aRv.StealJSException(cx, &value);
 
     // we want the same behavior as this JS implementation:
     // function Promise(arg) { try { arg(a, b); } catch (e) { this.reject(e); }}
     if (!JS_WrapValue(cx, &value)) {
       aRv.Throw(NS_ERROR_UNEXPECTED);
-      return nullptr;
+      return;
     }
 
-    promise->MaybeRejectInternal(cx, value);
+    MaybeRejectInternal(cx, value);
   }
-
-  return promise.forget();
 }
 
 /* static */ already_AddRefed<Promise>
 Promise::Resolve(const GlobalObject& aGlobal,
                  JS::Handle<JS::Value> aValue, ErrorResult& aRv)
 {
   // If a Promise was passed, just return it.
   if (aValue.isObject()) {
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -45,33 +45,31 @@ public:
   {
     MOZ_ASSERT(mPromise);
   }
 
   virtual bool
   Notify(JSContext* aCx, workers::Status aStatus) MOZ_OVERRIDE;
 };
 
-class Promise MOZ_FINAL : public nsISupports,
-                          public nsWrapperCache,
-                          public SupportsWeakPtr<Promise>
+class Promise : public nsISupports,
+                public nsWrapperCache,
+                public SupportsWeakPtr<Promise>
 {
   friend class NativePromiseCallback;
   friend class PromiseResolverTask;
   friend class PromiseTask;
   friend class PromiseReportRejectFeature;
   friend class PromiseWorkerProxy;
   friend class PromiseWorkerProxyRunnable;
   friend class RejectPromiseCallback;
   friend class ResolvePromiseCallback;
   friend class ThenableResolverTask;
   friend class WrapperPromiseCallback;
 
-  ~Promise();
-
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Promise)
   MOZ_DECLARE_REFCOUNTED_TYPENAME(Promise)
 
   // Promise creation tries to create a JS reflector for the Promise, so is
   // fallible.  Furthermore, we don't want to do JS-wrapping on a 0-refcount
   // object, so we addref before doing that and return the addrefed pointer
@@ -154,21 +152,42 @@ public:
       const Sequence<JS::Value>& aIterable, ErrorResult& aRv);
 
   static already_AddRefed<Promise>
   Race(const GlobalObject& aGlobal,
        const Sequence<JS::Value>& aIterable, ErrorResult& aRv);
 
   void AppendNativeHandler(PromiseNativeHandler* aRunnable);
 
-private:
+protected:
   // Do NOT call this unless you're Promise::Create.  I wish we could enforce
   // that from inside this class too, somehow.
   explicit Promise(nsIGlobalObject* aGlobal);
 
+  virtual ~Promise();
+
+  // Queue an async task to current main or worker thread.
+  static void
+  DispatchToMainOrWorkerThread(nsIRunnable* aRunnable);
+
+  // Do JS-wrapping after Promise creation.
+  void CreateWrapper(ErrorResult& aRv);
+
+  // Create the JS resolving functions of resolve() and reject(). And provide
+  // references to the two functions by calling PromiseInit passed from Promise
+  // constructor.
+  void CallInitFunction(const GlobalObject& aGlobal, PromiseInit& aInit,
+                        ErrorResult& aRv);
+
+  bool IsPending()
+  {
+    return mResolvePending;
+  }
+
+private:
   friend class PromiseDebugging;
 
   enum PromiseState {
     Pending,
     Resolved,
     Rejected
   };
 
@@ -184,20 +203,16 @@ private:
     mState = aState;
   }
 
   void SetResult(JS::Handle<JS::Value> aValue)
   {
     mResult = aValue;
   }
 
-  // Queue an async task to current main or worker thread.
-  static void
-  DispatchToMainOrWorkerThread(nsIRunnable* aRunnable);
-
   // This method processes promise's resolve/reject callbacks with promise's
   // result. It's executed when the resolver.resolve() or resolver.reject() is
   // called or when the promise already has a result and new callbacks are
   // appended by then(), catch() or done().
   void RunTask();
 
   void RunResolveTask(JS::Handle<JS::Value> aValue,
                       Promise::PromiseState aState,
--- a/dom/promise/PromiseCallback.cpp
+++ b/dom/promise/PromiseCallback.cpp
@@ -15,23 +15,17 @@ namespace dom {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseCallback)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseCallback)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseCallback)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseCallback)
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseCallback)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseCallback)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_0(PromiseCallback)
 
 PromiseCallback::PromiseCallback()
 {
 }
 
 PromiseCallback::~PromiseCallback()
 {
 }
new file mode 100644
--- /dev/null
+++ b/dom/promise/PromiseNativeAbortCallback.h
@@ -0,0 +1,36 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_PromiseNativeAbortCallback_h
+#define mozilla_dom_PromiseNativeAbortCallback_h
+
+#include "nsISupports.h"
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * PromiseNativeAbortCallback allows C++ to react to an AbortablePromise being
+ * aborted.
+ */
+class PromiseNativeAbortCallback : public nsISupports
+{
+protected:
+  virtual ~PromiseNativeAbortCallback()
+  { }
+
+public:
+  // Implemented in AbortablePromise.cpp.
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeAbortCallback)
+
+  virtual void Call() = 0;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PromiseNativeAbortCallback_h
--- a/dom/promise/moz.build
+++ b/dom/promise/moz.build
@@ -1,22 +1,25 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXPORTS.mozilla.dom += [
+    'AbortablePromise.h',
     'Promise.h',
     'PromiseDebugging.h',
+    'PromiseNativeAbortCallback.h',
     'PromiseNativeHandler.h',
     'PromiseWorkerProxy.h',
 ]
 
 UNIFIED_SOURCES += [
+    'AbortablePromise.cpp',
     'Promise.cpp',
     'PromiseCallback.cpp',
     'PromiseDebugging.cpp'
 ]
 
 FAIL_ON_WARNINGS = True
 
 LOCAL_INCLUDES += [
--- a/dom/promise/tests/mochitest.ini
+++ b/dom/promise/tests/mochitest.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 
 [test_bug883683.html]
 [test_promise.html]
 [test_promise_utils.html]
 [test_resolve.html]
+[test_abortable_promise.html]
new file mode 100644
--- /dev/null
+++ b/dom/promise/tests/test_abortable_promise.html
@@ -0,0 +1,115 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Promise.resolve(anything) Test</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.abortablepromise.enabled", true]]},
+                          runTest);
+var gTests = [testPending, testResolved, testRejected];
+
+function runTest() {
+   if (gTests.length == 0) {
+     SimpleTest.finish();
+     return;
+   }
+   new Promise(gTests.shift()).then(runTest, SimpleTest.finish);
+}
+
+// Aborting pending promise should first reject the promise and then call the
+// abortable callback.
+// The test succeeds once both the rejection handler and the abort handler have
+// been called.
+function testPending(succeed, fail) {
+  var rejected = false;
+  var aborted = false;
+
+  var p = new MozAbortablePromise(function(resolve, reject) {
+    // Wait for a while so that the promise can be aborted before resolved.
+    SimpleTest.executeSoon(function() {
+      resolve(0);
+    });
+  }, function abortable() {
+    aborted = true;
+    ok(true, "Promise aborted.");
+    if (rejected) {
+      succeed();
+    }
+  });
+
+  p.then(function() {
+    ok(false, "Failed to abort pending promise.");
+    fail();
+  }, function(what) {
+    rejected = true;
+    var isAbortException = (what && what.name) == "NS_ERROR_ABORT";
+    ok(isAbortException, "Should have NS_ERROR_ABORT exception");
+    if (!isAbortException) {
+      fail();
+    }
+    if (aborted) {
+      succeed();
+    }
+  });
+
+  // Abort the promise on creation.
+  p.abort();
+}
+
+// Do nothing when aborting resolved promise.
+function testResolved(succeed, fail) {
+  var p = new MozAbortablePromise(function(resolve, reject) {
+    resolve();
+  }, function abortable() {
+    ok(false, "Should not abort a resolved promise.");
+    fail();
+  });
+  p.then(function() {
+    ok(true, "Promise resolved.");
+    // Wait for a while to ensure abort handle won't be called.
+    setTimeout(succeed, 1000);
+  }, function(what) {
+    ok(false, "Failed to resolve promise: " + what);
+    fail();
+  });
+  p.abort();
+}
+
+// Do nothing when aborting rejected promise.
+function testRejected(succeed, fail) {
+  var p = new MozAbortablePromise(function(resolve, reject) {
+    reject(0);
+  }, function abortable() {
+    ok(false, "Should not abort a rejected promise.");
+    fail();
+  });
+
+  p.then(function() {
+    ok(false, "Failed to reject promise.");
+    fail();
+  }, function(what) {
+    is(what, 0, "promise rejected: " + what);
+    // Wait for a while to ensure abort handle won't be called.
+    setTimeout(succeed, 1000);
+  });
+  p.abort();
+}
+// -->
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -114,16 +114,18 @@ var legacyMozPrefixedInterfaces =
   ];
 // IMPORTANT: Do not change the list above without review from a DOM peer,
 //            except to remove items from it!
 
 // IMPORTANT: Do not change the list below without review from a DOM peer!
 var interfaceNamesInGlobalScope =
   [
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "MozAbortablePromise", pref: "dom.abortablepromise.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AlarmsManager", pref: "dom.mozAlarms.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AnalyserNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AnimationEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Animation", pref: "dom.animations-api.core.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/AbortablePromise.webidl
@@ -0,0 +1,19 @@
+/* -*- 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/.
+ */
+
+callback AbortCallback = void ();
+
+[Constructor(PromiseInit init, AbortCallback abortCallback),
+ Pref="dom.abortablepromise.enabled"]
+interface MozAbortablePromise : _Promise {
+  /*
+   * Aborts the promise.
+   * If the promise has not been resolved or rejected, it should be rejected
+   * with an Exception of type abort and then AbortCallback is called
+   * asynchronously. Otherwise, nothing should be done.
+   */
+  void abort();
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -11,16 +11,17 @@ GENERATED_WEBIDL_FILES = [
 PREPROCESSED_WEBIDL_FILES = [
     'Crypto.webidl',
     'HTMLMediaElement.webidl',
     'Navigator.webidl',
     'Window.webidl',
 ]
 
 WEBIDL_FILES = [
+    'AbortablePromise.webidl',
     'AbstractWorker.webidl',
     'ActivityRequestHandler.webidl',
     'AlarmsManager.webidl',
     'AnalyserNode.webidl',
     'Animatable.webidl',
     'Animation.webidl',
     'AnimationEffect.webidl',
     'AnimationEvent.webidl',
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -102,16 +102,19 @@ pref("offline-apps.quota.warn",        5
 
 // zlib compression level used for cache compression:
 // 0 => disable compression
 // 1 => best speed
 // 9 => best compression
 // cache compression turned off for now - see bug #715198
 pref("browser.cache.compression_level", 0);
 
+// Whether or not MozAbortablePromise is enabled.
+pref("dom.abortablepromise.enabled", false);
+
 // Whether or not testing features are enabled.
 pref("dom.quotaManager.testing", false);
 
 // Whether or not indexedDB is enabled.
 pref("dom.indexedDB.enabled", true);
 // Space to allow indexedDB databases before prompting (in MB).
 pref("dom.indexedDB.warningQuota", 50);
 // Whether or not indexedDB experimental features are enabled.