Bug 856410 - Implement futures - Part 2: Future.then() and Future.catch(). r=mounir, r=bz, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 11 Jun 2013 21:41:22 -0400
changeset 146290 562683b157e0d8573035c87ab1b051e32457ec0b
parent 146289 d116952bb74e0f1e858c0b6ffa2a24fb21d2a7bc
child 146291 ac3e3a1bc8d2c88d58e98c8889abb60180a693a4
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmounir, bz, smaug
bugs856410
milestone24.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 856410 - Implement futures - Part 2: Future.then() and Future.catch(). r=mounir, r=bz, r=smaug
dom/future/Future.cpp
dom/future/Future.h
dom/future/FutureCallback.cpp
dom/future/FutureCallback.h
dom/future/FutureResolver.h
dom/future/moz.build
dom/future/tests/test_future.html
dom/webidl/Future.webidl
js/xpconnect/src/nsCxPusher.cpp
js/xpconnect/src/nsCxPusher.h
--- a/dom/future/Future.cpp
+++ b/dom/future/Future.cpp
@@ -2,16 +2,17 @@
 /* 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/Future.h"
 #include "mozilla/dom/FutureBinding.h"
 #include "mozilla/dom/FutureResolver.h"
+#include "FutureCallback.h"
 #include "nsContentUtils.h"
 #include "nsPIDOMWindow.h"
 
 namespace mozilla {
 namespace dom {
 
 // FutureTask
 
@@ -78,16 +79,18 @@ Future::Future(nsPIDOMWindow* aWindow)
   : mWindow(aWindow)
   , mResult(JS::UndefinedValue())
   , mState(Pending)
   , mTaskPending(false)
 {
   MOZ_COUNT_CTOR(Future);
   NS_HOLD_JS_OBJECTS(this, Future);
   SetIsDOMBinding();
+
+  mResolver = new FutureResolver(this);
 }
 
 Future::~Future()
 {
   mResult = JSVAL_VOID;
   NS_DROP_JS_OBJECTS(this, Future);
   MOZ_COUNT_DTOR(Future);
 }
@@ -104,68 +107,111 @@ Future::Constructor(const GlobalObject& 
 {
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.Get());
   if (!window) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<Future> future = new Future(window);
-  future->mResolver = new FutureResolver(future);
 
   aInit.Call(future, *future->mResolver, aRv,
              CallbackObject::eRethrowExceptions);
   aRv.WouldReportJSException();
 
   if (aRv.IsJSException()) {
     Optional<JS::Handle<JS::Value> > value(aCx);
     aRv.StealJSException(aCx, &value.Value());
     future->mResolver->Reject(aCx, value);
   }
 
   return future.forget();
 }
 
+already_AddRefed<Future>
+Future::Then(AnyCallback* aResolveCallback, AnyCallback* aRejectCallback)
+{
+  nsRefPtr<Future> future = new Future(GetParentObject());
+
+  nsRefPtr<FutureCallback> resolveCb =
+    FutureCallback::Factory(future->mResolver,
+                            aResolveCallback,
+                            FutureCallback::Resolve);
+
+  nsRefPtr<FutureCallback> rejectCb =
+    FutureCallback::Factory(future->mResolver,
+                            aRejectCallback,
+                            FutureCallback::Reject);
+
+  AppendCallbacks(resolveCb, rejectCb);
+
+  return future.forget();
+}
+
+already_AddRefed<Future>
+Future::Catch(AnyCallback* aRejectCallback)
+{
+  return Then(nullptr, aRejectCallback);
+}
+
 void
 Future::Done(AnyCallback* aResolveCallback, AnyCallback* aRejectCallback)
 {
-  AppendCallbacks(aResolveCallback, aRejectCallback);
+  if (!aResolveCallback && !aRejectCallback) {
+    return;
+  }
+
+  nsRefPtr<FutureCallback> resolveCb;
+  if (aResolveCallback) {
+    resolveCb = new SimpleWrapperFutureCallback(this, aResolveCallback);
+  }
+
+  nsRefPtr<FutureCallback> rejectCb;
+  if (aRejectCallback) {
+    rejectCb = new SimpleWrapperFutureCallback(this, aRejectCallback);
+  }
+
+  AppendCallbacks(resolveCb, rejectCb);
 }
 
 void
-Future::AppendCallbacks(AnyCallback* aResolveCallback,
-                        AnyCallback* aRejectCallback)
+Future::AppendCallbacks(FutureCallback* aResolveCallback,
+                        FutureCallback* aRejectCallback)
 {
-  mResolveCallbacks.AppendElement(aResolveCallback);
-  mRejectCallbacks.AppendElement(aRejectCallback);
+  if (aResolveCallback) {
+    mResolveCallbacks.AppendElement(aResolveCallback);
+  }
+
+  if (aRejectCallback) {
+    mRejectCallbacks.AppendElement(aRejectCallback);
+  }
 
   // If future's state is resolved, queue a task to process future's resolve
   // callbacks with future's result. If future's state is rejected, queue a task
   // to process future's reject callbacks with future's result.
   if (mState != Pending && !mTaskPending) {
     nsRefPtr<FutureTask> task = new FutureTask(this);
     NS_DispatchToCurrentThread(task);
     mTaskPending = true;
   }
 }
 
 void
 Future::RunTask()
 {
   MOZ_ASSERT(mState != Pending);
 
-  nsTArray<nsRefPtr<AnyCallback> > callbacks;
+  nsTArray<nsRefPtr<FutureCallback> > callbacks;
   callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
                                             : mRejectCallbacks);
   mResolveCallbacks.Clear();
   mRejectCallbacks.Clear();
 
   Optional<JS::Handle<JS::Value> > value(nsContentUtils::GetSafeJSContext(),
                                          mResult);
 
   for (uint32_t i = 0; i < callbacks.Length(); ++i) {
-    ErrorResult rv;
-    callbacks[i]->Call(value, rv);
+    callbacks[i]->Call(value);
   }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/future/Future.h
+++ b/dom/future/Future.h
@@ -17,16 +17,17 @@
 
 struct JSContext;
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class FutureInit;
+class FutureCallback;
 class AnyCallback;
 class FutureResolver;
 
 class Future MOZ_FINAL : public nsISupports,
                          public nsWrapperCache
 {
   friend class FutureTask;
   friend class FutureResolver;
@@ -48,16 +49,22 @@ public:
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   static already_AddRefed<Future>
   Constructor(const GlobalObject& aGlobal, JSContext* aCx, FutureInit& aInit,
               ErrorResult& aRv);
 
+  already_AddRefed<Future>
+  Then(AnyCallback* aResolveCallback, AnyCallback* aRejectCallback);
+
+  already_AddRefed<Future>
+  Catch(AnyCallback* aRejectCallback);
+
   void Done(AnyCallback* aResolveCallback, AnyCallback* aRejectCallback);
 
 private:
   enum FutureState {
     Pending,
     Resolved,
     Rejected
   };
@@ -75,25 +82,25 @@ private:
   }
 
   // This method processes future's resolve/reject callbacks with future's
   // result. It's executed when the resolver.resolve() or resolver.reject() is
   // called or when the future already has a result and new callbacks are
   // appended by then(), catch() or done().
   void RunTask();
 
-  void AppendCallbacks(AnyCallback* aResolveCallback,
-                       AnyCallback* aRejectCallback);
+  void AppendCallbacks(FutureCallback* aResolveCallback,
+                       FutureCallback* aRejectCallback);
 
   nsRefPtr<nsPIDOMWindow> mWindow;
 
   nsRefPtr<FutureResolver> mResolver;
 
-  nsTArray<nsRefPtr<AnyCallback> > mResolveCallbacks;
-  nsTArray<nsRefPtr<AnyCallback> > mRejectCallbacks;
+  nsTArray<nsRefPtr<FutureCallback> > mResolveCallbacks;
+  nsTArray<nsRefPtr<FutureCallback> > mRejectCallbacks;
 
   JS::Value mResult;
   FutureState mState;
   bool mTaskPending;
 };
 
 } // namespace dom
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/future/FutureCallback.cpp
@@ -0,0 +1,232 @@
+/* -*- 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 "FutureCallback.h"
+#include "mozilla/dom/Future.h"
+#include "mozilla/dom/FutureResolver.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FutureCallback)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FutureCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FutureCallback)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FutureCallback)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FutureCallback)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+FutureCallback::FutureCallback()
+{
+  MOZ_COUNT_CTOR(FutureCallback);
+}
+
+FutureCallback::~FutureCallback()
+{
+  MOZ_COUNT_DTOR(FutureCallback);
+}
+
+// ResolveFutureCallback
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_1(ResolveFutureCallback,
+                                     FutureCallback,
+                                     mResolver)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ResolveFutureCallback)
+NS_INTERFACE_MAP_END_INHERITING(FutureCallback)
+
+NS_IMPL_ADDREF_INHERITED(ResolveFutureCallback, FutureCallback)
+NS_IMPL_RELEASE_INHERITED(ResolveFutureCallback, FutureCallback)
+
+ResolveFutureCallback::ResolveFutureCallback(FutureResolver* aResolver)
+  : mResolver(aResolver)
+{
+  MOZ_ASSERT(aResolver);
+  MOZ_COUNT_CTOR(ResolveFutureCallback);
+}
+
+ResolveFutureCallback::~ResolveFutureCallback()
+{
+  MOZ_COUNT_DTOR(ResolveFutureCallback);
+}
+
+void
+ResolveFutureCallback::Call(const Optional<JS::Handle<JS::Value> >& aValue)
+{
+  // Run resolver's algorithm with value and the synchronous flag set.
+  AutoJSContext cx;
+  // FIXME Bug 878849
+  Maybe<JSAutoCompartment> ac;
+  if (aValue.WasPassed() && aValue.Value().isObject()) {
+    JS::Rooted<JSObject*> rooted(cx, &aValue.Value().toObject());
+    ac.construct(cx, rooted);
+  }
+
+  mResolver->Resolve(cx, aValue, FutureResolver::SyncTask);
+}
+
+// RejectFutureCallback
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_1(RejectFutureCallback,
+                                     FutureCallback,
+                                     mResolver)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(RejectFutureCallback)
+NS_INTERFACE_MAP_END_INHERITING(FutureCallback)
+
+NS_IMPL_ADDREF_INHERITED(RejectFutureCallback, FutureCallback)
+NS_IMPL_RELEASE_INHERITED(RejectFutureCallback, FutureCallback)
+
+RejectFutureCallback::RejectFutureCallback(FutureResolver* aResolver)
+  : mResolver(aResolver)
+{
+  MOZ_ASSERT(aResolver);
+  MOZ_COUNT_CTOR(RejectFutureCallback);
+}
+
+RejectFutureCallback::~RejectFutureCallback()
+{
+  MOZ_COUNT_DTOR(RejectFutureCallback);
+}
+
+void
+RejectFutureCallback::Call(const Optional<JS::Handle<JS::Value> >& aValue)
+{
+  // Run resolver's algorithm with value and the synchronous flag set.
+  AutoJSContext cx;
+  // FIXME Bug 878849
+  Maybe<JSAutoCompartment> ac;
+  if (aValue.WasPassed() && aValue.Value().isObject()) {
+    JS::Rooted<JSObject*> rooted(cx, &aValue.Value().toObject());
+    ac.construct(cx, rooted);
+  }
+
+  mResolver->Reject(cx, aValue, FutureResolver::SyncTask);
+}
+
+// WrapperFutureCallback
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_2(WrapperFutureCallback,
+                                     FutureCallback,
+                                     mNextResolver, mCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WrapperFutureCallback)
+NS_INTERFACE_MAP_END_INHERITING(FutureCallback)
+
+NS_IMPL_ADDREF_INHERITED(WrapperFutureCallback, FutureCallback)
+NS_IMPL_RELEASE_INHERITED(WrapperFutureCallback, FutureCallback)
+
+WrapperFutureCallback::WrapperFutureCallback(FutureResolver* aNextResolver,
+                                             AnyCallback* aCallback)
+  : mNextResolver(aNextResolver)
+  , mCallback(aCallback)
+{
+  MOZ_ASSERT(aNextResolver);
+  MOZ_COUNT_CTOR(WrapperFutureCallback);
+}
+
+WrapperFutureCallback::~WrapperFutureCallback()
+{
+  MOZ_COUNT_DTOR(WrapperFutureCallback);
+}
+
+void
+WrapperFutureCallback::Call(const Optional<JS::Handle<JS::Value> >& aValue)
+{
+  AutoJSContext cx;
+  // FIXME Bug 878849
+  Maybe<JSAutoCompartment> ac;
+  if (aValue.WasPassed() && aValue.Value().isObject()) {
+    JS::Rooted<JSObject*> rooted(cx, &aValue.Value().toObject());
+    ac.construct(cx, rooted);
+  }
+
+  ErrorResult rv;
+
+  // If invoking callback threw an exception, run resolver's reject with the
+  // thrown exception as argument and the synchronous flag set.
+  Optional<JS::Handle<JS::Value> > value(cx,
+    mCallback->Call(mNextResolver->GetParentObject(), aValue, rv,
+                    CallbackObject::eRethrowExceptions));
+
+  rv.WouldReportJSException();
+
+  if (rv.Failed() && rv.IsJSException()) {
+    Optional<JS::Handle<JS::Value> > value(cx);
+    rv.StealJSException(cx, &value.Value());
+    mNextResolver->Reject(cx, value, FutureResolver::SyncTask);
+    return;
+  }
+
+  // Otherwise, run resolver's resolve with value and the synchronous flag
+  // set.
+  mNextResolver->Resolve(cx, value, FutureResolver::SyncTask);
+}
+
+// SimpleWrapperFutureCallback
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_2(SimpleWrapperFutureCallback,
+                                     FutureCallback,
+                                     mFuture, mCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SimpleWrapperFutureCallback)
+NS_INTERFACE_MAP_END_INHERITING(FutureCallback)
+
+NS_IMPL_ADDREF_INHERITED(SimpleWrapperFutureCallback, FutureCallback)
+NS_IMPL_RELEASE_INHERITED(SimpleWrapperFutureCallback, FutureCallback)
+
+SimpleWrapperFutureCallback::SimpleWrapperFutureCallback(Future* aFuture,
+                                                         AnyCallback* aCallback)
+  : mFuture(aFuture)
+  , mCallback(aCallback)
+{
+  MOZ_ASSERT(aFuture);
+  MOZ_COUNT_CTOR(SimpleWrapperFutureCallback);
+}
+
+SimpleWrapperFutureCallback::~SimpleWrapperFutureCallback()
+{
+  MOZ_COUNT_DTOR(SimpleWrapperFutureCallback);
+}
+
+void
+SimpleWrapperFutureCallback::Call(const Optional<JS::Handle<JS::Value> >& aValue)
+{
+  ErrorResult rv;
+  mCallback->Call(mFuture, aValue, rv);
+}
+
+/* static */ FutureCallback*
+FutureCallback::Factory(FutureResolver* aNextResolver,
+                        AnyCallback* aCallback, Task aTask)
+{
+  MOZ_ASSERT(aNextResolver);
+
+  // If we have a callback and a next resolver, we have to exec the callback and
+  // then propagate the return value to the next resolver->resolve().
+  if (aCallback) {
+    return new WrapperFutureCallback(aNextResolver, aCallback);
+  }
+
+  if (aTask == Resolve) {
+    return new ResolveFutureCallback(aNextResolver);
+  }
+
+  if (aTask == Reject) {
+    return new RejectFutureCallback(aNextResolver);
+  }
+
+  MOZ_ASSERT(false, "This should not happen");
+  return nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/future/FutureCallback.h
@@ -0,0 +1,121 @@
+/* -*- 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_FutureCallback_h
+#define mozilla_dom_FutureCallback_h
+
+#include "mozilla/dom/Future.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla {
+namespace dom {
+
+class FutureResolver;
+
+// This is the base class for any FutureCallback.
+// It's a logical step in the future chain of callbacks.
+class FutureCallback : public nsISupports
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(FutureCallback)
+
+  FutureCallback();
+  virtual ~FutureCallback();
+
+  virtual void Call(const Optional<JS::Handle<JS::Value> >& aValue) = 0;
+
+  enum Task {
+    Resolve,
+    Reject
+  };
+
+  // This factory returns a FutureCallback object with refcount of 0.
+  static FutureCallback*
+  Factory(FutureResolver* aNextResolver, AnyCallback* aCallback,
+          Task aTask);
+};
+
+// WrapperFutureCallback execs a JS Callback with a value, and then the return
+// value is sent to the aNextResolver->resolve() or to aNextResolver->Reject()
+// if the JS Callback throws.
+class WrapperFutureCallback MOZ_FINAL : public FutureCallback
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WrapperFutureCallback,
+                                           FutureCallback)
+
+  void Call(const Optional<JS::Handle<JS::Value> >& aValue) MOZ_OVERRIDE;
+
+  WrapperFutureCallback(FutureResolver* aNextResolver,
+                        AnyCallback* aCallback);
+  ~WrapperFutureCallback();
+
+private:
+  nsRefPtr<FutureResolver> mNextResolver;
+  nsRefPtr<AnyCallback> mCallback;
+};
+
+// SimpleWrapperFutureCallback execs a JS Callback with a value.
+class SimpleWrapperFutureCallback MOZ_FINAL : public FutureCallback
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SimpleWrapperFutureCallback,
+                                           FutureCallback)
+
+  void Call(const Optional<JS::Handle<JS::Value> >& aValue) MOZ_OVERRIDE;
+
+  SimpleWrapperFutureCallback(Future* aFuture,
+                              AnyCallback* aCallback);
+  ~SimpleWrapperFutureCallback();
+
+private:
+  nsRefPtr<Future> mFuture;
+  nsRefPtr<AnyCallback> mCallback;
+};
+
+// ResolveFutureCallback calls aResolver->Resolve() with the value received by
+// Call().
+class ResolveFutureCallback MOZ_FINAL : public FutureCallback
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ResolveFutureCallback,
+                                           FutureCallback)
+
+  void Call(const Optional<JS::Handle<JS::Value> >& aValue) MOZ_OVERRIDE;
+
+  ResolveFutureCallback(FutureResolver* aResolver);
+  ~ResolveFutureCallback();
+
+private:
+  nsRefPtr<FutureResolver> mResolver;
+};
+
+// RejectFutureCallback calls aResolver->Reject() with the value received by
+// Call().
+class RejectFutureCallback MOZ_FINAL : public FutureCallback
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RejectFutureCallback,
+                                           FutureCallback)
+
+  void Call(const Optional<JS::Handle<JS::Value> >& aValue) MOZ_OVERRIDE;
+
+  RejectFutureCallback(FutureResolver* aResolver);
+  ~RejectFutureCallback();
+
+private:
+  nsRefPtr<FutureResolver> mResolver;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FutureCallback_h
--- a/dom/future/FutureResolver.h
+++ b/dom/future/FutureResolver.h
@@ -17,16 +17,19 @@ struct JSContext;
 
 namespace mozilla {
 namespace dom {
 
 class FutureResolver MOZ_FINAL : public nsISupports,
                                  public nsWrapperCache
 {
   friend class FutureResolverTask;
+  friend class WrapperFutureCallback;
+  friend class ResolveFutureCallback;
+  friend class RejectFutureCallback;
 
 private:
   enum FutureTaskSync {
     SyncTask,
     AsyncTask
   };
 
 public:
--- a/dom/future/moz.build
+++ b/dom/future/moz.build
@@ -13,9 +13,10 @@ MODULE = 'dom'
 EXPORTS.mozilla.dom += [
     'Future.h',
     'FutureResolver.h',
 ]
 
 CPP_SOURCES += [
     'Future.cpp',
     'FutureResolver.cpp',
+    'FutureCallback.cpp',
 ]
--- a/dom/future/tests/test_future.html
+++ b/dom/future/tests/test_future.html
@@ -13,17 +13,17 @@
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script type="application/javascript"><!--
 
 SimpleTest.waitForExplicitFinish();
 
-function singleFutureResolve() {
+function futureResolve() {
   ok(Future, "Future object should exist");
 
   var future = new Future(function(resolver) {
     ok(resolver, "FutureResolver exists");
     ok("reject" in resolver, "FutureResolver.reject exists");
     ok("resolve" in resolver, "FutureResolver.resolve exists");
 
     resolver.resolve(42);
@@ -32,17 +32,17 @@ function singleFutureResolve() {
     is(what, 42, "ResolveCb received 42");
     runTest();
   }, function() {
     ok(false, "Done - rejectCb has been called");
     runTest();
   });
 }
 
-function singleFutureReject() {
+function futureReject() {
   var future = new Future(function(resolver) {
     resolver.reject(42);
   }).done(function(what) {
     ok(false, "Done - resolveCb has been called");
     runTest();
   }, function(what) {
     ok(true, "Done - rejectCb has been called");
     is(what, 42, "RejectCb received 42");
@@ -92,18 +92,215 @@ function futureAsync() {
       runTest();
     }, 0);
   }).done(function() {
     global = "bar";
   });
   is(global, "foo", "Global should still be foo (2)");
 }
 
-var tests = [ singleFutureResolve, singleFutureReject,
-              futureException, futureGC, futureAsync ];
+function futureDoubleDone() {
+  var steps = 0;
+  var future = new Future(function(resolver) {
+    resolver.resolve(42);
+  });
+
+  future.done(function(what) {
+    ok(true, "Done.resolve has been called");
+    is(what, 42, "Value == 42");
+    steps++;
+  }, function(what) {
+    ok(false, "Done.reject has been called");
+  });
+
+  future.done(function(what) {
+    ok(true, "Done.resolve has been called");
+    is(steps, 1, "Done.resolve - step == 1");
+    is(what, 42, "Value == 42");
+    runTest();
+  }, function(what) {
+    ok(false, "Done.reject has been called");
+  });
+}
+
+function futureDoneException() {
+  var future = new Future(function(resolver) {
+    resolver.resolve(42);
+  });
+
+  onErrorCb = window.onerror;
+  window.onerror = function(e) {
+    ok(true, "window.onerror has been called!");
+    window.onerror = onErrorCb;
+    runTest();
+  };
+
+  future.done(function(what) {
+    ok(true, "Done.resolve has been called");
+    throw "booh";
+  });
+}
+
+function futureThenCatchDone() {
+  var future = new Future(function(resolver) {
+    resolver.resolve(42);
+  });
+
+  var future2 = future.then(function(what) {
+    ok(true, "Then.resolve has been called");
+    is(what, 42, "Value == 42");
+    return what + 1;
+  }, function(what) {
+    ok(false, "Then.reject has been called");
+  });
+
+  isnot(future, future2, "These 2 future objs are different");
+
+  future2.then(function(what) {
+    ok(true, "Then.resolve has been called");
+    is(what, 43, "Value == 43");
+    return what + 1;
+  }, function(what) {
+    ok(false, "Then.reject has been called");
+  }).catch(function() {
+    ok(false, "Catch has been called");
+  }).done(function(what) {
+    ok(true, "Done.resolve has been called");
+    is(what, 44, "Value == 44");
+    runTest();
+  }, function(what) {
+    ok(false, "Done.reject has been called");
+  });
+}
+
+function futureRejectThenCatchDone() {
+  var future = new Future(function(resolver) {
+    resolver.reject(42);
+  });
+
+  var future2 = future.then(function(what) {
+    ok(false, "Then.resolve has been called");
+  }, function(what) {
+    ok(true, "Then.reject has been called");
+    is(what, 42, "Value == 42");
+    return what + 1;
+  });
+
+  isnot(future, future2, "These 2 future objs are different");
+
+  future2.then(function(what) {
+    ok(true, "Then.resolve has been called");
+    is(what, 43, "Value == 43");
+    return what+1;
+  }).catch(function(what) {
+    ok(false, "Catch has been called");
+  }).done(function(what) {
+    ok(true, "Then.resolve has been called");
+    is(what, 44, "Value == 44");
+    runTest();
+  });
+}
+
+function futureRejectThenCatchDone2() {
+  var future = new Future(function(resolver) {
+    resolver.reject(42);
+  });
+
+  future.then(function(what) {
+    ok(true, "Then.resolve has been called");
+    is(what, 42, "Value == 42");
+    return what+1;
+  }).catch(function(what) {
+    is(what, 42, "Value == 42");
+    ok(true, "Catch has been called");
+    return what+1;
+  }).done(function(what) {
+    ok(true, "Then.resolve has been called");
+    is(what, 43, "Value == 43");
+    runTest();
+  });
+}
+
+function futureRejectThenCatchExceptionDone() {
+  var future = new Future(function(resolver) {
+    resolver.reject(42);
+  });
+
+  future.then(function(what) {
+    ok(false, "Then.resolve has been called");
+  }, function(what) {
+    ok(true, "Then.reject has been called");
+    is(what, 42, "Value == 42");
+    throw(what + 1);
+  }).catch(function(what) {
+    ok(true, "Catch has been called");
+    is(what, 43, "Value == 43");
+    return what + 1;
+  }).done(function(what) {
+    ok(true, "Then.resolve has been called");
+    is(what, 44, "Value == 44");
+    runTest();
+  });
+}
+
+function futureThenCatchOrderingResolve() {
+  var global = 0;
+  var f = new Future(function(r) {
+    r.resolve(42);
+  });
+
+  f.done(function() {
+    f.then(function() {
+      global++;
+    });
+    f.catch(function() {
+      global++;
+    });
+    f.done(function() {
+      global++;
+    });
+    setTimeout(function() {
+      is(global, 2, "Many steps... should return 2");
+      runTest();
+    }, 0);
+  });
+}
+
+function futureThenCatchOrderingReject() {
+  var global = 0;
+  var f = new Future(function(r) {
+    r.reject(42);
+  })
+
+  f.done(function() {}, function() {
+    f.then(function() {
+      global++;
+    });
+    f.catch(function() {
+      global++;
+    });
+    f.done(function() {}, function() {
+      global++;
+    });
+    setTimeout(function() {
+      is(global, 2, "Many steps... should return 2");
+      runTest();
+    }, 0);
+  });
+}
+
+var tests = [ futureResolve, futureReject,
+              futureException, futureGC, futureAsync,
+              futureDoubleDone, futureDoneException,
+              futureThenCatchDone, futureRejectThenCatchDone,
+              futureRejectThenCatchDone2,
+              futureRejectThenCatchExceptionDone,
+              futureThenCatchOrderingResolve,
+              futureThenCatchOrderingReject
+            ];
 
 function runTest() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
   var test = tests.shift();
--- a/dom/webidl/Future.webidl
+++ b/dom/webidl/Future.webidl
@@ -13,11 +13,18 @@ interface FutureResolver {
 };
 
 callback FutureInit = void (FutureResolver resolver);
 callback AnyCallback = any (optional any value);
 
 [Constructor(FutureInit init)]
 interface Future {
   // TODO: update this interface - bug 875289
+  [Creator]
+  Future then(optional AnyCallback? resolveCallback = null,
+              optional AnyCallback? rejectCallback = null);
+
+  [Creator]
+  Future catch(optional AnyCallback? rejectCallback = null);
+
   void done(optional AnyCallback? resolveCallback = null,
             optional AnyCallback? rejectCallback = null);
 };
--- a/js/xpconnect/src/nsCxPusher.cpp
+++ b/js/xpconnect/src/nsCxPusher.cpp
@@ -218,17 +218,17 @@ AutoJSContext::Init(bool aSafe MOZ_GUARD
   }
 
   if (!mCx) {
     mCx = xpc->GetSafeJSContext();
     mPusher.Push(mCx);
   }
 }
 
-AutoJSContext::operator JSContext*()
+AutoJSContext::operator JSContext*() const
 {
   return mCx;
 }
 
 AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
   : AutoJSContext(true MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
 {
 }
--- a/js/xpconnect/src/nsCxPusher.h
+++ b/js/xpconnect/src/nsCxPusher.h
@@ -56,17 +56,17 @@ namespace mozilla {
 /**
  * Use AutoJSContext when you need a JS context on the stack but don't have one
  * passed as a parameter. AutoJSContext will take care of finding the most
  * appropriate JS context and release it when leaving the stack.
  */
 class MOZ_STACK_CLASS AutoJSContext {
 public:
   AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
-  operator JSContext*();
+  operator JSContext*() const;
 
 protected:
   AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
 private:
   // We need this Init() method because we can't use delegating constructor for
   // the moment. It is a C++11 feature and we do not require C++11 to be
   // supported to be able to compile Gecko.