Bug 856410 - Implement futures - Part 1: Constructor, webidl, Done(). r=mounir, r=bz
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 11 Jun 2013 21:41:21 -0400
changeset 134798 d116952bb74e0f1e858c0b6ffa2a24fb21d2a7bc
parent 134797 58df50e6b861b8f23203486fc606930540a6de91
child 134799 562683b157e0d8573035c87ab1b051e32457ec0b
push id24816
push useremorley@mozilla.com
push dateThu, 13 Jun 2013 09:27:57 +0000
treeherderautoland@7ace61b0e3e4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmounir, bz
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 1: Constructor, webidl, Done(). r=mounir, r=bz
dom/bindings/BindingDeclarations.h
dom/bindings/Bindings.conf
dom/future/Future.cpp
dom/future/Future.h
dom/future/FutureResolver.cpp
dom/future/FutureResolver.h
dom/future/Makefile.in
dom/future/moz.build
dom/future/tests/Makefile.in
dom/future/tests/moz.build
dom/future/tests/test_future.html
dom/moz.build
dom/webidl/Future.webidl
dom/webidl/WebIDL.mk
layout/build/Makefile.in
--- a/dom/bindings/BindingDeclarations.h
+++ b/dom/bindings/BindingDeclarations.h
@@ -304,16 +304,22 @@ template<typename T>
 class Optional<JS::Handle<T> > :
   public Optional_base<JS::Handle<T>, JS::Rooted<T> >
 {
 public:
   Optional() :
     Optional_base<JS::Handle<T>, JS::Rooted<T> >()
   {}
 
+  Optional(JSContext* cx) :
+    Optional_base<JS::Handle<T>, JS::Rooted<T> >()
+  {
+    this->Construct(cx);
+  }
+
   Optional(JSContext* cx, const T& aValue) :
     Optional_base<JS::Handle<T>, JS::Rooted<T> >(cx, aValue)
   {}
 };
 
 // A specialization of Optional for JSObject* to make sure that when someone
 // calls Construct() on it we will pre-initialized the JSObject* to nullptr so
 // it can be traced safely.
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -399,16 +399,20 @@ DOMInterfaces = {
     'skipGen': True,
     'nativeType': 'JSObject'
 }],
 
 'FocusEvent': {
     'nativeType': 'nsDOMFocusEvent',
 },
 
+'Future': {
+    'implicitJSContext': [ 'constructor' ]
+},
+
 'GainNode': {
     'resultNotAddRefed': [ 'gain' ],
 },
 
 'Gamepad': {
     'nativeType': 'nsDOMGamepad',
 },
 
new file mode 100644
--- /dev/null
+++ b/dom/future/Future.cpp
@@ -0,0 +1,171 @@
+/* -*- 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/Future.h"
+#include "mozilla/dom/FutureBinding.h"
+#include "mozilla/dom/FutureResolver.h"
+#include "nsContentUtils.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+// FutureTask
+
+// This class processes the future's callbacks with future's result.
+class FutureTask MOZ_FINAL : public nsRunnable
+{
+public:
+  FutureTask(Future* aFuture)
+    : mFuture(aFuture)
+  {
+    MOZ_ASSERT(aFuture);
+    MOZ_COUNT_CTOR(FutureTask);
+  }
+
+  ~FutureTask()
+  {
+    MOZ_COUNT_DTOR(FutureTask);
+  }
+
+  NS_IMETHOD Run()
+  {
+    mFuture->mTaskPending = false;
+    mFuture->RunTask();
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<Future> mFuture;
+};
+
+// Future
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Future)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolver)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveCallbacks);
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectCallbacks);
+  tmp->mResult = JSVAL_VOID;
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Future)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolver)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolveCallbacks);
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRejectCallbacks);
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Future)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mResult)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Future)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Future)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Future)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+Future::Future(nsPIDOMWindow* aWindow)
+  : mWindow(aWindow)
+  , mResult(JS::UndefinedValue())
+  , mState(Pending)
+  , mTaskPending(false)
+{
+  MOZ_COUNT_CTOR(Future);
+  NS_HOLD_JS_OBJECTS(this, Future);
+  SetIsDOMBinding();
+}
+
+Future::~Future()
+{
+  mResult = JSVAL_VOID;
+  NS_DROP_JS_OBJECTS(this, Future);
+  MOZ_COUNT_DTOR(Future);
+}
+
+JSObject*
+Future::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return FutureBinding::Wrap(aCx, aScope, this);
+}
+
+/* static */ already_AddRefed<Future>
+Future::Constructor(const GlobalObject& aGlobal, JSContext* aCx,
+                    FutureInit& aInit, ErrorResult& aRv)
+{
+  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();
+}
+
+void
+Future::Done(AnyCallback* aResolveCallback, AnyCallback* aRejectCallback)
+{
+  AppendCallbacks(aResolveCallback, aRejectCallback);
+}
+
+void
+Future::AppendCallbacks(AnyCallback* aResolveCallback,
+                        AnyCallback* aRejectCallback)
+{
+  mResolveCallbacks.AppendElement(aResolveCallback);
+  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;
+  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);
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/future/Future.h
@@ -0,0 +1,101 @@
+/* -*- 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_Future_h
+#define mozilla_dom_Future_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/dom/FutureBinding.h"
+#include "nsWrapperCache.h"
+#include "nsAutoPtr.h"
+
+struct JSContext;
+class nsPIDOMWindow;
+
+namespace mozilla {
+namespace dom {
+
+class FutureInit;
+class AnyCallback;
+class FutureResolver;
+
+class Future MOZ_FINAL : public nsISupports,
+                         public nsWrapperCache
+{
+  friend class FutureTask;
+  friend class FutureResolver;
+  friend class FutureResolverTask;
+
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Future)
+
+  Future(nsPIDOMWindow* aWindow);
+  ~Future();
+
+  // WebIDL
+
+  nsPIDOMWindow* GetParentObject() const
+  {
+    return mWindow;
+  }
+
+  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);
+
+  void Done(AnyCallback* aResolveCallback, AnyCallback* aRejectCallback);
+
+private:
+  enum FutureState {
+    Pending,
+    Resolved,
+    Rejected
+  };
+
+  void SetState(FutureState aState)
+  {
+    MOZ_ASSERT(mState == Pending);
+    MOZ_ASSERT(aState != Pending);
+    mState = aState;
+  }
+
+  void SetResult(JS::Handle<JS::Value> aValue)
+  {
+    mResult = aValue;
+  }
+
+  // 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);
+
+  nsRefPtr<nsPIDOMWindow> mWindow;
+
+  nsRefPtr<FutureResolver> mResolver;
+
+  nsTArray<nsRefPtr<AnyCallback> > mResolveCallbacks;
+  nsTArray<nsRefPtr<AnyCallback> > mRejectCallbacks;
+
+  JS::Value mResult;
+  FutureState mState;
+  bool mTaskPending;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Future_h
new file mode 100644
--- /dev/null
+++ b/dom/future/FutureResolver.cpp
@@ -0,0 +1,142 @@
+/* -*- 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/FutureResolver.h"
+#include "mozilla/dom/FutureBinding.h"
+#include "mozilla/dom/Future.h"
+
+namespace mozilla {
+namespace dom {
+
+// FutureResolverTask
+
+// This class processes the future's callbacks with future's result.
+class FutureResolverTask MOZ_FINAL : public nsRunnable
+{
+public:
+  FutureResolverTask(FutureResolver* aResolver,
+                     const JS::Handle<JS::Value> aValue,
+                     Future::FutureState aState)
+    : mResolver(aResolver)
+    , mValue(aValue)
+    , mState(aState)
+  {
+    MOZ_ASSERT(aResolver);
+    MOZ_ASSERT(mState != Future::Pending);
+    MOZ_COUNT_CTOR(FutureResolverTask);
+
+    JSContext* cx = nsContentUtils::GetSafeJSContext();
+    JS_AddNamedValueRootRT(JS_GetRuntime(cx), &mValue,
+                           "FutureResolverTask.mValue");
+  }
+
+  ~FutureResolverTask()
+  {
+    MOZ_COUNT_DTOR(FutureResolverTask);
+
+    JSContext* cx = nsContentUtils::GetSafeJSContext();
+    JS_RemoveValueRootRT(JS_GetRuntime(cx), &mValue);
+  }
+
+  NS_IMETHOD Run()
+  {
+    mResolver->RunTask(JS::Handle<JS::Value>::fromMarkedLocation(&mValue),
+                       mState, FutureResolver::SyncTask);
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<FutureResolver> mResolver;
+  JS::Value mValue;
+  Future::FutureState mState;
+};
+
+// FutureResolver
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(FutureResolver, mFuture)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FutureResolver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FutureResolver)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FutureResolver)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+FutureResolver::FutureResolver(Future* aFuture)
+  : mFuture(aFuture)
+  , mResolvePending(false)
+{
+  SetIsDOMBinding();
+}
+
+JSObject*
+FutureResolver::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return FutureResolverBinding::Wrap(aCx, aScope, this);
+}
+
+void
+FutureResolver::Resolve(JSContext* aCx,
+                        const Optional<JS::Handle<JS::Value> >& aValue,
+                        FutureTaskSync aAsynchronous)
+{
+  if (mResolvePending) {
+    return;
+  }
+
+  // TODO: if the arg is a future?
+
+  mResolvePending = true;
+
+  // If the synchronous flag is set, process future's resolve callbacks with
+  // value. Otherwise, the synchronous flag is unset, queue a task to process
+  // future's resolve callbacks with value. Otherwise, the synchronous flag is
+  // unset, queue a task to process future's resolve callbacks with value.
+  RunTask(aValue.WasPassed() ? aValue.Value() : JS::UndefinedHandleValue,
+          Future::Resolved, aAsynchronous);
+}
+
+void
+FutureResolver::Reject(JSContext* aCx,
+                       const Optional<JS::Handle<JS::Value> >& aValue,
+                       FutureTaskSync aAsynchronous)
+{
+  if (mResolvePending) {
+    return;
+  }
+
+  mResolvePending = true;
+
+  // If the synchronous flag is set, process future's reject callbacks with
+  // value. Otherwise, the synchronous flag is unset, queue a task to process
+  // future's reject callbacks with value.
+  RunTask(aValue.WasPassed() ? aValue.Value() : JS::UndefinedHandleValue,
+          Future::Rejected, aAsynchronous);
+}
+
+void
+FutureResolver::RunTask(JS::Handle<JS::Value> aValue,
+                        Future::FutureState aState,
+                        FutureTaskSync aAsynchronous)
+{
+  // If the synchronous flag is unset, queue a task to process future's
+  // accept callbacks with value.
+  if (aAsynchronous == AsyncTask) {
+    nsRefPtr<FutureResolverTask> task =
+      new FutureResolverTask(this, aValue, aState);
+    NS_DispatchToCurrentThread(task);
+    return;
+  }
+
+  mFuture->SetResult(aValue);
+  mFuture->SetState(aState);
+  mFuture->RunTask();
+  mFuture = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/future/FutureResolver.h
@@ -0,0 +1,64 @@
+/* -*- 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_FutureResolver_h
+#define mozilla_dom_FutureResolver_h
+
+#include "mozilla/dom/Future.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+struct JSContext;
+
+namespace mozilla {
+namespace dom {
+
+class FutureResolver MOZ_FINAL : public nsISupports,
+                                 public nsWrapperCache
+{
+  friend class FutureResolverTask;
+
+private:
+  enum FutureTaskSync {
+    SyncTask,
+    AsyncTask
+  };
+
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FutureResolver)
+
+  FutureResolver(Future* aFuture);
+
+  Future* GetParentObject() const
+  {
+    return mFuture;
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  void Resolve(JSContext* aCx, const Optional<JS::Handle<JS::Value> >& aValue,
+               FutureTaskSync aSync = AsyncTask);
+
+  void Reject(JSContext* aCx, const Optional<JS::Handle<JS::Value> >& aValue,
+              FutureTaskSync aSync = AsyncTask);
+
+private:
+  void RunTask(JS::Handle<JS::Value> aValue,
+               Future::FutureState aState, FutureTaskSync aSync);
+
+  nsRefPtr<Future> mFuture;
+
+  bool mResolvePending;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FutureResolver_h
new file mode 100644
--- /dev/null
+++ b/dom/future/Makefile.in
@@ -0,0 +1,18 @@
+# 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/.
+
+DEPTH            = @DEPTH@
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+LIBRARY_NAME     = domfuture_s
+LIBXUL_LIBRARY = 1
+FORCE_STATIC_LIB = 1
+FAIL_ON_WARNINGS := 1
+
+include $(topsrcdir)/config/config.mk
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/future/moz.build
@@ -0,0 +1,21 @@
+# -*- 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/.
+
+TEST_DIRS += ['tests']
+
+XPIDL_MODULE = 'dom_future'
+
+MODULE = 'dom'
+
+EXPORTS.mozilla.dom += [
+    'Future.h',
+    'FutureResolver.h',
+]
+
+CPP_SOURCES += [
+    'Future.cpp',
+    'FutureResolver.cpp',
+]
new file mode 100644
--- /dev/null
+++ b/dom/future/tests/Makefile.in
@@ -0,0 +1,18 @@
+# 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/.
+
+DEPTH            = @DEPTH@
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+relativesrcdir   = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MOCHITEST_FILES = \
+  test_future.html \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/future/tests/moz.build
@@ -0,0 +1,6 @@
+# -*- 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/.
+
new file mode 100644
--- /dev/null
+++ b/dom/future/tests/test_future.html
@@ -0,0 +1,119 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Basic Future 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 onload="runTest()">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+SimpleTest.waitForExplicitFinish();
+
+function singleFutureResolve() {
+  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);
+  }).done(function(what) {
+    ok(true, "Done - resolveCb has been called");
+    is(what, 42, "ResolveCb received 42");
+    runTest();
+  }, function() {
+    ok(false, "Done - rejectCb has been called");
+    runTest();
+  });
+}
+
+function singleFutureReject() {
+  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");
+    runTest();
+  });
+}
+
+function futureException() {
+  var future = new Future(function(resolver) {
+    throw 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");
+    runTest();
+  });
+}
+
+function futureGC() {
+  var resolver;
+  var future = new Future(function(r) {
+    resolver = r;
+  }).done(function(what) {
+    ok(true, "Done - future is still alive");
+    runTest();
+  });
+
+  future = null;
+
+  SpecialPowers.gc();
+  SpecialPowers.forceGC();
+  SpecialPowers.forceCC();
+
+  resolver.resolve(42);
+}
+
+function futureAsync() {
+  var global = "foo";
+  var f = new Future(function(r) {
+    is(global, "foo", "Global should be foo");
+    r.resolve(42);
+    is(global, "foo", "Global should still be foo");
+    setTimeout(function() {
+      is(global, "bar", "Global should still be bar!");
+      runTest();
+    }, 0);
+  }).done(function() {
+    global = "bar";
+  });
+  is(global, "foo", "Global should still be foo (2)");
+}
+
+var tests = [ singleFutureResolve, singleFutureReject,
+              futureException, futureGC, futureAsync ];
+
+function runTest() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+// -->
+</script>
+</pre>
+<div id="testtarget"></div>
+</body>
+</html>
+
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -66,16 +66,17 @@ PARALLEL_DIRS += [
     'plugins/ipc',
     'indexedDB',
     'system',
     'ipc',
     'identity',
     'workers',
     'camera',
     'audiochannel',
+    'future',
     'wappush'
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     PARALLEL_DIRS += ['plugins/ipc/hangui']
 
 if CONFIG['MOZ_B2G_RIL']:
     PARALLEL_DIRS += [
new file mode 100644
--- /dev/null
+++ b/dom/webidl/Future.webidl
@@ -0,0 +1,23 @@
+/* -*- 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/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#futures
+ */
+
+interface FutureResolver {
+  void resolve(optional any value);
+  void reject(optional any value);
+};
+
+callback FutureInit = void (FutureResolver resolver);
+callback AnyCallback = any (optional any value);
+
+[Constructor(FutureInit init)]
+interface Future {
+  // TODO: update this interface - bug 875289
+  void done(optional AnyCallback? resolveCallback = null,
+            optional AnyCallback? rejectCallback = null);
+};
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -82,16 +82,17 @@ webidl_files = \
   FileList.webidl \
   FileMode.webidl \
   FileReader.webidl \
   FileReaderSync.webidl \
   FileRequest.webidl \
   FocusEvent.webidl \
   FormData.webidl \
   Function.webidl \
+  Future.webidl \
   GainNode.webidl \
   Geolocation.webidl \
   HTMLAnchorElement.webidl \
   HTMLAppletElement.webidl \
   HTMLAreaElement.webidl \
   HTMLAudioElement.webidl \
   HTMLBaseElement.webidl \
   HTMLBodyElement.webidl \
--- a/layout/build/Makefile.in
+++ b/layout/build/Makefile.in
@@ -66,16 +66,17 @@ SHARED_LIBRARY_LIBS = \
 	$(DEPTH)/dom/mobilemessage/src/$(LIB_PREFIX)dom_mobilemessage_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/events/$(LIB_PREFIX)jsdomevents_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/json/$(LIB_PREFIX)json_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/jsurl/$(LIB_PREFIX)jsurl_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/storage/$(LIB_PREFIX)jsdomstorage_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/offline/$(LIB_PREFIX)jsdomoffline_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/geolocation/$(LIB_PREFIX)jsdomgeolocation_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/audiochannel/$(LIB_PREFIX)domaudiochannel_s.$(LIB_SUFFIX) \
+	$(DEPTH)/dom/future/$(LIB_PREFIX)domfuture_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/notification/$(LIB_PREFIX)jsdomnotification_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/system/$(LIB_PREFIX)domsystem_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/workers/$(LIB_PREFIX)domworkers_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/indexedDB/$(LIB_PREFIX)dom_indexeddb_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/indexedDB/ipc/$(LIB_PREFIX)dom_indexeddb_ipc_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/browser-element/$(LIB_PREFIX)dom_browserelement_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/time/$(LIB_PREFIX)dom_time_s.$(LIB_SUFFIX) \
 	$(DEPTH)/editor/libeditor/text/$(LIB_PREFIX)texteditor_s.$(LIB_SUFFIX) \