2015-05-03 19:32:37 +00:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
2013-06-12 01:41:21 +00:00
|
|
|
/* 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/. */
|
|
|
|
|
2013-07-11 20:40:36 +00:00
|
|
|
#include "mozilla/dom/Promise.h"
|
2013-08-29 04:30:06 +00:00
|
|
|
|
2014-11-17 09:42:00 +00:00
|
|
|
#include "js/Debug.h"
|
2015-08-06 12:45:21 +00:00
|
|
|
|
|
|
|
#include "mozilla/Atomics.h"
|
|
|
|
#include "mozilla/CycleCollectedJSRuntime.h"
|
|
|
|
#include "mozilla/OwningNonNull.h"
|
|
|
|
#include "mozilla/Preferences.h"
|
|
|
|
|
2014-07-19 01:31:11 +00:00
|
|
|
#include "mozilla/dom/BindingUtils.h"
|
2014-06-03 15:38:38 +00:00
|
|
|
#include "mozilla/dom/DOMError.h"
|
2016-04-19 23:29:21 +00:00
|
|
|
#include "mozilla/dom/DOMException.h"
|
|
|
|
#include "mozilla/dom/DOMExceptionBinding.h"
|
2015-08-06 12:45:21 +00:00
|
|
|
#include "mozilla/dom/MediaStreamError.h"
|
2013-07-11 20:40:36 +00:00
|
|
|
#include "mozilla/dom/PromiseBinding.h"
|
2014-07-19 01:31:11 +00:00
|
|
|
#include "mozilla/dom/ScriptSettings.h"
|
2015-08-06 12:45:21 +00:00
|
|
|
|
|
|
|
#include "jsfriendapi.h"
|
2015-09-02 16:20:30 +00:00
|
|
|
#include "js/StructuredClone.h"
|
2015-08-06 12:45:21 +00:00
|
|
|
#include "nsContentUtils.h"
|
|
|
|
#include "nsGlobalWindow.h"
|
|
|
|
#include "nsIScriptObjectPrincipal.h"
|
|
|
|
#include "nsJSEnvironment.h"
|
|
|
|
#include "nsJSPrincipals.h"
|
|
|
|
#include "nsJSUtils.h"
|
|
|
|
#include "nsPIDOMWindow.h"
|
2013-07-11 20:40:36 +00:00
|
|
|
#include "PromiseCallback.h"
|
2015-04-10 15:27:57 +00:00
|
|
|
#include "PromiseDebugging.h"
|
2013-11-19 18:43:51 +00:00
|
|
|
#include "PromiseNativeHandler.h"
|
2014-02-24 13:56:54 +00:00
|
|
|
#include "PromiseWorkerProxy.h"
|
2013-08-07 21:40:20 +00:00
|
|
|
#include "WorkerPrivate.h"
|
2013-10-23 13:16:49 +00:00
|
|
|
#include "WorkerRunnable.h"
|
2015-11-25 20:48:09 +00:00
|
|
|
#include "WrapperFactory.h"
|
2014-07-04 05:24:59 +00:00
|
|
|
#include "xpcpublic.h"
|
2015-09-09 01:23:55 +00:00
|
|
|
#ifdef MOZ_CRASHREPORTER
|
|
|
|
#include "nsExceptionHandler.h"
|
|
|
|
#endif
|
2013-06-12 01:41:21 +00:00
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace dom {
|
|
|
|
|
2015-04-10 15:27:57 +00:00
|
|
|
namespace {
|
|
|
|
// Generator used by Promise::GetID.
|
|
|
|
Atomic<uintptr_t> gIDGenerator(0);
|
2015-07-13 15:25:42 +00:00
|
|
|
} // namespace
|
2015-04-10 15:27:57 +00:00
|
|
|
|
2013-11-24 19:26:07 +00:00
|
|
|
using namespace workers;
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2013-07-11 20:40:36 +00:00
|
|
|
// This class processes the promise's callbacks with promise's result.
|
2016-04-26 00:23:21 +00:00
|
|
|
class PromiseReactionJob final : public Runnable
|
2013-06-12 01:41:21 +00:00
|
|
|
{
|
|
|
|
public:
|
2015-08-06 15:18:30 +00:00
|
|
|
PromiseReactionJob(Promise* aPromise,
|
|
|
|
PromiseCallback* aCallback,
|
|
|
|
const JS::Value& aValue)
|
2013-07-11 20:40:36 +00:00
|
|
|
: mPromise(aPromise)
|
2014-10-28 12:08:19 +00:00
|
|
|
, mCallback(aCallback)
|
|
|
|
, mValue(CycleCollectedJSRuntime::Get()->Runtime(), aValue)
|
2013-06-12 01:41:21 +00:00
|
|
|
{
|
2013-07-11 20:40:36 +00:00
|
|
|
MOZ_ASSERT(aPromise);
|
2014-10-28 12:08:19 +00:00
|
|
|
MOZ_ASSERT(aCallback);
|
2015-08-06 15:18:30 +00:00
|
|
|
MOZ_COUNT_CTOR(PromiseReactionJob);
|
2013-06-12 01:41:21 +00:00
|
|
|
}
|
|
|
|
|
2014-10-28 12:08:19 +00:00
|
|
|
virtual
|
2015-08-06 15:18:30 +00:00
|
|
|
~PromiseReactionJob()
|
2013-06-12 01:41:21 +00:00
|
|
|
{
|
2015-08-06 15:18:30 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(PromiseReactionJob);
|
|
|
|
MOZ_COUNT_DTOR(PromiseReactionJob);
|
2013-06-12 01:41:21 +00:00
|
|
|
}
|
|
|
|
|
2014-10-28 12:08:19 +00:00
|
|
|
protected:
|
2014-08-19 10:39:56 +00:00
|
|
|
NS_IMETHOD
|
2015-03-21 16:28:04 +00:00
|
|
|
Run() override
|
2013-06-12 01:41:21 +00:00
|
|
|
{
|
2015-08-06 15:18:30 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(PromiseReactionJob);
|
2016-03-23 15:02:57 +00:00
|
|
|
|
|
|
|
MOZ_ASSERT(mPromise->GetWrapper()); // It was preserved!
|
|
|
|
|
|
|
|
AutoJSAPI jsapi;
|
|
|
|
if (!jsapi.Init(mPromise->GetWrapper())) {
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
JSContext* cx = jsapi.cx();
|
2013-06-12 01:41:21 +00:00
|
|
|
|
2014-10-28 12:08:19 +00:00
|
|
|
JS::Rooted<JS::Value> value(cx, mValue);
|
|
|
|
if (!MaybeWrapValue(cx, &value)) {
|
|
|
|
NS_WARNING("Failed to wrap value into the right compartment.");
|
|
|
|
JS_ClearPendingException(cx);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2013-09-11 16:03:04 +00:00
|
|
|
|
2015-03-09 12:36:29 +00:00
|
|
|
JS::Rooted<JSObject*> asyncStack(cx, mPromise->mAllocationStack);
|
|
|
|
|
|
|
|
{
|
|
|
|
Maybe<JS::AutoSetAsyncStackForNewCalls> sas;
|
|
|
|
if (asyncStack) {
|
2016-03-23 14:40:53 +00:00
|
|
|
sas.emplace(cx, asyncStack, "Promise");
|
2015-03-09 12:36:29 +00:00
|
|
|
}
|
|
|
|
mCallback->Call(cx, value);
|
|
|
|
}
|
2013-09-11 16:03:04 +00:00
|
|
|
|
2014-08-19 10:39:56 +00:00
|
|
|
return NS_OK;
|
2013-09-11 16:03:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<Promise> mPromise;
|
|
|
|
RefPtr<PromiseCallback> mCallback;
|
2014-04-16 08:47:53 +00:00
|
|
|
JS::PersistentRooted<JS::Value> mValue;
|
2013-11-24 19:26:07 +00:00
|
|
|
NS_DECL_OWNINGTHREAD;
|
|
|
|
};
|
|
|
|
|
2014-05-20 21:21:13 +00:00
|
|
|
/*
|
|
|
|
* Utilities for thenable callbacks.
|
|
|
|
*
|
|
|
|
* A thenable is a { then: function(resolve, reject) { } }.
|
|
|
|
* `then` is called with a resolve and reject callback pair.
|
|
|
|
* Since only one of these should be called at most once (first call wins), the
|
|
|
|
* two keep a reference to each other in SLOT_DATA. When either of them is
|
|
|
|
* called, the references are cleared. Further calls are ignored.
|
|
|
|
*/
|
|
|
|
namespace {
|
|
|
|
void
|
|
|
|
LinkThenableCallables(JSContext* aCx, JS::Handle<JSObject*> aResolveFunc,
|
|
|
|
JS::Handle<JSObject*> aRejectFunc)
|
|
|
|
{
|
2015-11-25 20:48:09 +00:00
|
|
|
js::SetFunctionNativeReserved(aResolveFunc, Promise::SLOT_DATA,
|
2014-05-20 21:21:13 +00:00
|
|
|
JS::ObjectValue(*aRejectFunc));
|
2015-11-25 20:48:09 +00:00
|
|
|
js::SetFunctionNativeReserved(aRejectFunc, Promise::SLOT_DATA,
|
2014-05-20 21:21:13 +00:00
|
|
|
JS::ObjectValue(*aResolveFunc));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Returns false if callback was already called before, otherwise breaks the
|
|
|
|
* links and returns true.
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
MarkAsCalledIfNotCalledBefore(JSContext* aCx, JS::Handle<JSObject*> aFunc)
|
|
|
|
{
|
2015-11-25 20:48:09 +00:00
|
|
|
JS::Value otherFuncVal =
|
|
|
|
js::GetFunctionNativeReserved(aFunc, Promise::SLOT_DATA);
|
2014-05-20 21:21:13 +00:00
|
|
|
|
|
|
|
if (!otherFuncVal.isObject()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSObject* otherFuncObj = &otherFuncVal.toObject();
|
2015-11-25 20:48:09 +00:00
|
|
|
MOZ_ASSERT(js::GetFunctionNativeReserved(otherFuncObj,
|
|
|
|
Promise::SLOT_DATA).isObject());
|
2014-05-20 21:21:13 +00:00
|
|
|
|
|
|
|
// Break both references.
|
2015-11-25 20:48:09 +00:00
|
|
|
js::SetFunctionNativeReserved(aFunc, Promise::SLOT_DATA,
|
|
|
|
JS::UndefinedValue());
|
|
|
|
js::SetFunctionNativeReserved(otherFuncObj, Promise::SLOT_DATA,
|
|
|
|
JS::UndefinedValue());
|
2014-05-20 21:21:13 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Promise*
|
|
|
|
GetPromise(JSContext* aCx, JS::Handle<JSObject*> aFunc)
|
|
|
|
{
|
2015-11-25 20:48:09 +00:00
|
|
|
JS::Value promiseVal = js::GetFunctionNativeReserved(aFunc,
|
|
|
|
Promise::SLOT_PROMISE);
|
2014-05-20 21:21:13 +00:00
|
|
|
|
|
|
|
MOZ_ASSERT(promiseVal.isObject());
|
|
|
|
|
|
|
|
Promise* promise;
|
|
|
|
UNWRAP_OBJECT(Promise, &promiseVal.toObject(), promise);
|
|
|
|
return promise;
|
|
|
|
}
|
2015-07-13 15:25:42 +00:00
|
|
|
} // namespace
|
2014-05-20 21:21:13 +00:00
|
|
|
|
2015-04-18 02:01:02 +00:00
|
|
|
// Runnable to resolve thenables.
|
2014-05-20 21:21:13 +00:00
|
|
|
// Equivalent to the specification's ResolvePromiseViaThenableTask.
|
2016-04-26 00:23:21 +00:00
|
|
|
class PromiseResolveThenableJob final : public Runnable
|
2014-05-20 21:21:13 +00:00
|
|
|
{
|
|
|
|
public:
|
2015-08-03 16:48:34 +00:00
|
|
|
PromiseResolveThenableJob(Promise* aPromise,
|
|
|
|
JS::Handle<JSObject*> aThenable,
|
|
|
|
PromiseInit* aThen)
|
2014-05-20 21:21:13 +00:00
|
|
|
: mPromise(aPromise)
|
|
|
|
, mThenable(CycleCollectedJSRuntime::Get()->Runtime(), aThenable)
|
|
|
|
, mThen(aThen)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aPromise);
|
2015-08-03 16:48:34 +00:00
|
|
|
MOZ_COUNT_CTOR(PromiseResolveThenableJob);
|
2014-05-20 21:21:13 +00:00
|
|
|
}
|
|
|
|
|
2014-08-19 10:39:56 +00:00
|
|
|
virtual
|
2015-08-03 16:48:34 +00:00
|
|
|
~PromiseResolveThenableJob()
|
2014-05-20 21:21:13 +00:00
|
|
|
{
|
2015-08-03 16:48:34 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(PromiseResolveThenableJob);
|
|
|
|
MOZ_COUNT_DTOR(PromiseResolveThenableJob);
|
2014-05-20 21:21:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
2014-08-19 10:39:56 +00:00
|
|
|
NS_IMETHOD
|
2015-03-21 16:28:04 +00:00
|
|
|
Run() override
|
2014-05-20 21:21:13 +00:00
|
|
|
{
|
2015-08-03 16:48:34 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(PromiseResolveThenableJob);
|
2016-03-23 15:02:57 +00:00
|
|
|
|
|
|
|
MOZ_ASSERT(mPromise->GetWrapper()); // It was preserved!
|
|
|
|
|
|
|
|
AutoJSAPI jsapi;
|
2015-04-18 02:01:02 +00:00
|
|
|
// If we ever change which compartment we're working in here, make sure to
|
|
|
|
// fix the fast-path for resolved-with-a-Promise in ResolveInternal.
|
2016-03-23 15:02:57 +00:00
|
|
|
if (!jsapi.Init(mPromise->GetWrapper())) {
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
JSContext* cx = jsapi.cx();
|
2014-05-20 21:21:13 +00:00
|
|
|
|
|
|
|
JS::Rooted<JSObject*> resolveFunc(cx,
|
|
|
|
mPromise->CreateThenableFunction(cx, mPromise, PromiseCallback::Resolve));
|
|
|
|
|
|
|
|
if (!resolveFunc) {
|
|
|
|
mPromise->HandleException(cx);
|
2014-08-19 10:39:56 +00:00
|
|
|
return NS_OK;
|
2014-05-20 21:21:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> rejectFunc(cx,
|
|
|
|
mPromise->CreateThenableFunction(cx, mPromise, PromiseCallback::Reject));
|
|
|
|
if (!rejectFunc) {
|
|
|
|
mPromise->HandleException(cx);
|
2014-08-19 10:39:56 +00:00
|
|
|
return NS_OK;
|
2014-05-20 21:21:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LinkThenableCallables(cx, resolveFunc, rejectFunc);
|
|
|
|
|
|
|
|
ErrorResult rv;
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> rootedThenable(cx, mThenable);
|
|
|
|
|
|
|
|
mThen->Call(rootedThenable, resolveFunc, rejectFunc, rv,
|
2015-04-09 01:23:48 +00:00
|
|
|
"promise thenable", CallbackObject::eRethrowExceptions,
|
2015-01-15 22:39:02 +00:00
|
|
|
mPromise->Compartment());
|
2014-05-20 21:21:13 +00:00
|
|
|
|
|
|
|
rv.WouldReportJSException();
|
2015-01-15 22:39:02 +00:00
|
|
|
if (rv.Failed()) {
|
2014-05-20 21:21:13 +00:00
|
|
|
JS::Rooted<JS::Value> exn(cx);
|
2015-11-20 21:29:41 +00:00
|
|
|
{ // Scope for JSAutoCompartment
|
|
|
|
|
2015-01-15 22:39:02 +00:00
|
|
|
// Convert the ErrorResult to a JS exception object that we can reject
|
|
|
|
// ourselves with. This will be exactly the exception that would get
|
|
|
|
// thrown from a binding method whose ErrorResult ended up with
|
|
|
|
// whatever is on "rv" right now.
|
|
|
|
JSAutoCompartment ac(cx, mPromise->GlobalJSObject());
|
|
|
|
DebugOnly<bool> conversionResult = ToJSValue(cx, rv, &exn);
|
|
|
|
MOZ_ASSERT(conversionResult);
|
|
|
|
}
|
2014-05-20 21:21:13 +00:00
|
|
|
|
|
|
|
bool couldMarkAsCalled = MarkAsCalledIfNotCalledBefore(cx, resolveFunc);
|
|
|
|
|
|
|
|
// If we could mark as called, neither of the callbacks had been called
|
|
|
|
// when the exception was thrown. So we can reject the Promise.
|
|
|
|
if (couldMarkAsCalled) {
|
|
|
|
bool ok = JS_WrapValue(cx, &exn);
|
|
|
|
MOZ_ASSERT(ok);
|
|
|
|
if (!ok) {
|
|
|
|
NS_WARNING("Failed to wrap value into the right compartment.");
|
|
|
|
}
|
|
|
|
|
2014-10-28 12:08:19 +00:00
|
|
|
mPromise->RejectInternal(cx, exn);
|
2014-05-20 21:21:13 +00:00
|
|
|
}
|
|
|
|
// At least one of resolveFunc or rejectFunc have been called, so ignore
|
|
|
|
// the exception. FIXME(nsm): This should be reported to the error
|
|
|
|
// console though, for debugging.
|
|
|
|
}
|
2014-08-19 10:39:56 +00:00
|
|
|
|
2015-04-27 19:00:41 +00:00
|
|
|
return rv.StealNSResult();
|
2014-05-20 21:21:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<Promise> mPromise;
|
2014-05-20 21:21:13 +00:00
|
|
|
JS::PersistentRooted<JSObject*> mThenable;
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseInit> mThen;
|
2014-05-20 21:21:13 +00:00
|
|
|
NS_DECL_OWNINGTHREAD;
|
|
|
|
};
|
|
|
|
|
2015-11-25 20:48:08 +00:00
|
|
|
// A struct implementing
|
|
|
|
// <http://www.ecma-international.org/ecma-262/6.0/#sec-promisecapability-records>.
|
|
|
|
// While the spec holds on to these in some places, in practice those places
|
|
|
|
// don't actually need everything from this struct, so we explicitly grab
|
|
|
|
// members from it as needed in those situations. That allows us to make this a
|
|
|
|
// stack-only struct and keep the rooting simple.
|
|
|
|
//
|
|
|
|
// We also add an optimization for the (common) case when we discover that the
|
|
|
|
// Promise constructor we're supposed to use is in fact the canonical Promise
|
|
|
|
// constructor. In that case we will just set mNativePromise in our
|
|
|
|
// PromiseCapability and not set mPromise/mResolve/mReject; the correct
|
|
|
|
// callbacks will be the standard Promise ones, and we don't really want to
|
|
|
|
// synthesize JSFunctions for them in that situation.
|
|
|
|
struct MOZ_STACK_CLASS Promise::PromiseCapability
|
|
|
|
{
|
|
|
|
explicit PromiseCapability(JSContext* aCx)
|
|
|
|
: mPromise(aCx)
|
|
|
|
, mResolve(aCx)
|
|
|
|
, mReject(aCx)
|
|
|
|
{}
|
|
|
|
|
|
|
|
// Take an exception on aCx and try to convert it into a promise rejection.
|
|
|
|
// Note that this can result in a new exception being thrown on aCx, or an
|
|
|
|
// exception getting thrown on aRv. On entry to this method, aRv is assumed
|
|
|
|
// to not be a failure. This should only be called if NewPromiseCapability
|
|
|
|
// succeeded on this PromiseCapability.
|
|
|
|
void RejectWithException(JSContext* aCx, ErrorResult& aRv);
|
|
|
|
|
|
|
|
// Return a JS::Value representing the promise. This should only be called if
|
|
|
|
// NewPromiseCapability succeeded on this PromiseCapability. It makes no
|
|
|
|
// guarantees about compartments (e.g. in the mNativePromise case it's in the
|
|
|
|
// compartment of the reflector, but in the mPromise case it might be in the
|
|
|
|
// compartment of some cross-compartment wrapper for a reflector).
|
|
|
|
JS::Value PromiseValue() const;
|
|
|
|
|
|
|
|
// All the JS::Value fields of this struct are actually objects, but for our
|
|
|
|
// purposes it's simpler to store them as JS::Value.
|
|
|
|
|
|
|
|
// [[Promise]].
|
2016-01-27 07:26:39 +00:00
|
|
|
JS::Rooted<JSObject*> mPromise;
|
2015-11-25 20:48:08 +00:00
|
|
|
// [[Resolve]]. Value in the context compartment.
|
|
|
|
JS::Rooted<JS::Value> mResolve;
|
|
|
|
// [[Reject]]. Value in the context compartment.
|
|
|
|
JS::Rooted<JS::Value> mReject;
|
|
|
|
// If mNativePromise is non-null, we should use it, not mPromise.
|
|
|
|
RefPtr<Promise> mNativePromise;
|
|
|
|
|
|
|
|
private:
|
|
|
|
// We don't want to allow creation of temporaries of this type, ever.
|
|
|
|
PromiseCapability(const PromiseCapability&) = delete;
|
|
|
|
PromiseCapability(PromiseCapability&&) = delete;
|
|
|
|
};
|
|
|
|
|
|
|
|
void
|
|
|
|
Promise::PromiseCapability::RejectWithException(JSContext* aCx,
|
|
|
|
ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
// This method basically implements
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-ifabruptrejectpromise
|
|
|
|
// or at least the parts of it that happen if we have an abrupt completion.
|
|
|
|
|
|
|
|
MOZ_ASSERT(!aRv.Failed());
|
2016-01-27 07:26:39 +00:00
|
|
|
MOZ_ASSERT(mNativePromise || mPromise,
|
2015-11-25 20:48:08 +00:00
|
|
|
"NewPromiseCapability didn't succeed");
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> exn(aCx);
|
|
|
|
if (!JS_GetPendingException(aCx, &exn)) {
|
|
|
|
// This is an uncatchable exception, so can't be converted into a rejection.
|
|
|
|
// Just rethrow that on aRv.
|
|
|
|
aRv.ThrowUncatchableException();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS_ClearPendingException(aCx);
|
|
|
|
|
|
|
|
// If we have a native promise, just reject it without trying to call out into
|
|
|
|
// JS.
|
|
|
|
if (mNativePromise) {
|
|
|
|
mNativePromise->MaybeRejectInternal(aCx, exn);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> ignored(aCx);
|
|
|
|
if (!JS::Call(aCx, JS::UndefinedHandleValue, mReject, JS::HandleValueArray(exn),
|
|
|
|
&ignored)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Value
|
|
|
|
Promise::PromiseCapability::PromiseValue() const
|
|
|
|
{
|
2016-01-27 07:26:39 +00:00
|
|
|
MOZ_ASSERT(mNativePromise || mPromise,
|
2015-11-25 20:48:08 +00:00
|
|
|
"NewPromiseCapability didn't succeed");
|
|
|
|
|
|
|
|
if (mNativePromise) {
|
|
|
|
return JS::ObjectValue(*mNativePromise->GetWrapper());
|
|
|
|
}
|
|
|
|
|
2016-01-27 07:26:39 +00:00
|
|
|
return JS::ObjectValue(*mPromise);
|
2015-11-25 20:48:08 +00:00
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
|
2013-07-11 20:40:36 +00:00
|
|
|
// Promise
|
2013-06-12 01:41:21 +00:00
|
|
|
|
2013-08-02 01:29:05 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(Promise)
|
|
|
|
|
2013-07-11 20:40:36 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise)
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2015-04-10 15:27:57 +00:00
|
|
|
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2014-03-12 14:31:03 +00:00
|
|
|
tmp->MaybeReportRejectedOnce();
|
2015-04-10 15:27:57 +00:00
|
|
|
#else
|
|
|
|
tmp->mResult = JS::UndefinedValue();
|
|
|
|
#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
2014-02-25 21:34:55 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2014-04-09 08:30:24 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveCallbacks)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectCallbacks)
|
2013-06-12 01:41:21 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
2016-02-09 22:40:31 +00:00
|
|
|
#else // SPIDERMONKEY_PROMISE
|
|
|
|
tmp->mPromiseObj = nullptr;
|
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
2013-06-12 01:41:21 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
|
2013-07-11 20:40:36 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise)
|
2014-02-25 21:34:55 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2014-04-09 08:30:24 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolveCallbacks)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRejectCallbacks)
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
2013-06-12 01:41:21 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
|
2013-07-11 20:40:36 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise)
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2016-02-22 18:11:02 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResult)
|
2014-10-20 02:27:12 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAllocationStack)
|
2014-10-20 02:27:12 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRejectionStack)
|
2014-10-20 02:27:12 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mFullfillmentStack)
|
2013-06-12 01:41:21 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
2016-02-09 22:40:31 +00:00
|
|
|
#else // SPIDERMONKEY_PROMISE
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj);
|
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
2013-06-12 01:41:21 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2015-02-24 22:24:45 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Promise)
|
|
|
|
if (tmp->IsBlack()) {
|
2015-10-09 20:48:10 +00:00
|
|
|
JS::ExposeValueToActiveJS(tmp->mResult);
|
2015-02-24 22:24:45 +00:00
|
|
|
if (tmp->mAllocationStack) {
|
|
|
|
JS::ExposeObjectToActiveJS(tmp->mAllocationStack);
|
|
|
|
}
|
|
|
|
if (tmp->mRejectionStack) {
|
|
|
|
JS::ExposeObjectToActiveJS(tmp->mRejectionStack);
|
|
|
|
}
|
|
|
|
if (tmp->mFullfillmentStack) {
|
|
|
|
JS::ExposeObjectToActiveJS(tmp->mFullfillmentStack);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Promise)
|
|
|
|
return tmp->IsBlackAndDoesNotNeedTracing(tmp);
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Promise)
|
|
|
|
return tmp->IsBlack();
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
2015-02-24 22:24:45 +00:00
|
|
|
|
2013-07-11 20:40:36 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(Promise)
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(Promise)
|
2013-06-12 01:41:21 +00:00
|
|
|
|
2013-07-11 20:40:36 +00:00
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Promise)
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2013-06-12 01:41:21 +00:00
|
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
2013-06-12 01:41:21 +00:00
|
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
2014-12-26 02:11:20 +00:00
|
|
|
NS_INTERFACE_MAP_ENTRY(Promise)
|
2013-06-12 01:41:21 +00:00
|
|
|
NS_INTERFACE_MAP_END
|
|
|
|
|
2014-02-25 21:34:55 +00:00
|
|
|
Promise::Promise(nsIGlobalObject* aGlobal)
|
|
|
|
: mGlobal(aGlobal)
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2013-06-12 01:41:21 +00:00
|
|
|
, mResult(JS::UndefinedValue())
|
2014-10-20 02:27:12 +00:00
|
|
|
, mAllocationStack(nullptr)
|
2014-10-20 02:27:12 +00:00
|
|
|
, mRejectionStack(nullptr)
|
2014-10-20 02:27:12 +00:00
|
|
|
, mFullfillmentStack(nullptr)
|
2013-06-12 01:41:21 +00:00
|
|
|
, mState(Pending)
|
2015-04-10 15:27:57 +00:00
|
|
|
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2013-08-29 04:30:06 +00:00
|
|
|
, mHadRejectCallback(false)
|
2015-04-10 15:27:57 +00:00
|
|
|
#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
|
|
|
, mTaskPending(false)
|
2013-09-11 16:03:04 +00:00
|
|
|
, mResolvePending(false)
|
2015-04-10 15:27:57 +00:00
|
|
|
, mIsLastInChain(true)
|
|
|
|
, mWasNotifiedAsUncaught(false)
|
|
|
|
, mID(0)
|
2016-02-09 22:40:31 +00:00
|
|
|
#else // SPIDERMONKEY_PROMISE
|
|
|
|
, mPromiseObj(nullptr)
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
2013-06-12 01:41:21 +00:00
|
|
|
{
|
2014-02-25 21:34:55 +00:00
|
|
|
MOZ_ASSERT(mGlobal);
|
|
|
|
|
2013-08-16 20:10:17 +00:00
|
|
|
mozilla::HoldJSObjects(this);
|
2014-10-20 17:02:21 +00:00
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2014-10-20 17:02:21 +00:00
|
|
|
mCreationTimestamp = TimeStamp::Now();
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
2013-06-12 01:41:21 +00:00
|
|
|
}
|
|
|
|
|
2013-07-11 20:40:36 +00:00
|
|
|
Promise::~Promise()
|
2013-06-12 01:41:21 +00:00
|
|
|
{
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2015-04-10 15:27:57 +00:00
|
|
|
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2014-03-12 14:31:03 +00:00
|
|
|
MaybeReportRejectedOnce();
|
2015-04-10 15:27:57 +00:00
|
|
|
#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
2013-08-16 20:10:17 +00:00
|
|
|
mozilla::DropJSObjects(this);
|
2013-06-12 01:41:21 +00:00
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
|
|
|
|
|
|
bool
|
|
|
|
Promise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
|
|
|
|
JS::MutableHandle<JSObject*> aWrapper)
|
2013-06-12 01:41:21 +00:00
|
|
|
{
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifdef DEBUG
|
|
|
|
binding_detail::AssertReflectorHasGivenProto(aCx, mPromiseObj, aGivenProto);
|
|
|
|
#endif // DEBUG
|
|
|
|
JS::ExposeObjectToActiveJS(mPromiseObj);
|
|
|
|
aWrapper.set(mPromiseObj);
|
|
|
|
return true;
|
2013-06-12 01:41:21 +00:00
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
// static
|
|
|
|
already_AddRefed<Promise>
|
|
|
|
Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv)
|
|
|
|
{
|
2016-04-25 09:06:30 +00:00
|
|
|
if (!aGlobal) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
|
|
return nullptr;
|
|
|
|
}
|
2016-02-09 22:40:31 +00:00
|
|
|
RefPtr<Promise> p = new Promise(aGlobal);
|
|
|
|
p->CreateWrapper(nullptr, aRv);
|
|
|
|
if (aRv.Failed()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return p.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
already_AddRefed<Promise>
|
|
|
|
Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
|
|
|
|
JS::Handle<JS::Value> aValue, ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
JSAutoCompartment ac(aCx, aGlobal->GetGlobalJSObject());
|
|
|
|
JS::Rooted<JSObject*> p(aCx,
|
|
|
|
JS::CallOriginalPromiseResolve(aCx, aValue));
|
|
|
|
if (!p) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2016-02-09 22:40:31 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CreateFromExisting(aGlobal, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
already_AddRefed<Promise>
|
|
|
|
Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
|
|
|
|
JS::Handle<JS::Value> aValue, ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
JSAutoCompartment ac(aCx, aGlobal->GetGlobalJSObject());
|
|
|
|
JS::Rooted<JSObject*> p(aCx,
|
|
|
|
JS::CallOriginalPromiseReject(aCx, aValue));
|
|
|
|
if (!p) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2016-02-09 22:40:31 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CreateFromExisting(aGlobal, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
already_AddRefed<Promise>
|
|
|
|
Promise::All(const GlobalObject& aGlobal,
|
|
|
|
const nsTArray<RefPtr<Promise>>& aPromiseList, ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsIGlobalObject> global;
|
|
|
|
global = do_QueryInterface(aGlobal.GetAsSupports());
|
|
|
|
if (!global) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSContext* cx = aGlobal.Context();
|
|
|
|
|
|
|
|
JS::AutoObjectVector promises(cx);
|
|
|
|
if (!promises.reserve(aPromiseList.Length())) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(cx);
|
2016-02-09 22:40:31 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto& promise : aPromiseList) {
|
2016-02-09 22:40:31 +00:00
|
|
|
JS::Rooted<JSObject*> promiseObj(cx, promise->PromiseObj());
|
2016-02-09 22:40:31 +00:00
|
|
|
// Just in case, make sure these are all in the context compartment.
|
|
|
|
if (!JS_WrapObject(cx, &promiseObj)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(cx);
|
2016-02-09 22:40:31 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
promises.infallibleAppend(promiseObj);
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> result(cx, JS::GetWaitForAllPromise(cx, promises));
|
|
|
|
if (!result) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(cx);
|
2016-02-09 22:40:31 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CreateFromExisting(global, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Promise::Then(JSContext* aCx,
|
|
|
|
// aCalleeGlobal may not be in the compartment of aCx, when called over
|
|
|
|
// Xrays.
|
|
|
|
JS::Handle<JSObject*> aCalleeGlobal,
|
|
|
|
AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
|
|
|
|
JS::MutableHandle<JS::Value> aRetval,
|
|
|
|
ErrorResult& aRv)
|
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
// Let's hope this does the right thing with Xrays... Ensure everything is
|
|
|
|
// just in the caller compartment; that ought to do the trick. In theory we
|
|
|
|
// should consider aCalleeGlobal, but in practice our only caller is
|
|
|
|
// DOMRequest::Then, which is not working with a Promise subclass, so things
|
|
|
|
// should be OK.
|
2016-02-09 22:40:31 +00:00
|
|
|
JS::Rooted<JSObject*> promise(aCx, PromiseObj());
|
2016-02-09 22:40:31 +00:00
|
|
|
if (!JS_WrapObject(aCx, &promise)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2016-02-09 22:40:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> resolveCallback(aCx);
|
|
|
|
if (aResolveCallback) {
|
|
|
|
resolveCallback = aResolveCallback->Callback();
|
|
|
|
if (!JS_WrapObject(aCx, &resolveCallback)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2016-02-09 22:40:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> rejectCallback(aCx);
|
|
|
|
if (aRejectCallback) {
|
|
|
|
rejectCallback = aRejectCallback->Callback();
|
|
|
|
if (!JS_WrapObject(aCx, &rejectCallback)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2016-02-09 22:40:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> retval(aCx);
|
|
|
|
retval = JS::CallOriginalPromiseThen(aCx, promise, resolveCallback,
|
|
|
|
rejectCallback);
|
|
|
|
if (!retval) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2016-02-09 22:40:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
aRetval.setObject(*retval);
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need a dummy function to pass to JS::NewPromiseObject.
|
|
|
|
static bool
|
|
|
|
DoNothingPromiseExecutor(JSContext*, unsigned aArgc, JS::Value* aVp)
|
|
|
|
{
|
|
|
|
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
|
|
|
|
args.rval().setUndefined();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Promise::CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
AutoJSAPI jsapi;
|
|
|
|
if (!jsapi.Init(mGlobal)) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
JSContext* cx = jsapi.cx();
|
|
|
|
|
|
|
|
JSFunction* doNothingFunc =
|
|
|
|
JS_NewFunction(cx, DoNothingPromiseExecutor, /* nargs = */ 2,
|
|
|
|
/* flags = */ 0, nullptr);
|
|
|
|
if (!doNothingFunc) {
|
|
|
|
JS_ClearPendingException(cx);
|
|
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> doNothingObj(cx, JS_GetFunctionObject(doNothingFunc));
|
2016-02-09 22:40:31 +00:00
|
|
|
mPromiseObj = JS::NewPromiseObject(cx, doNothingObj, aDesiredProto);
|
|
|
|
if (!mPromiseObj) {
|
2016-02-09 22:40:31 +00:00
|
|
|
JS_ClearPendingException(cx);
|
|
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Promise::MaybeResolve(JSContext* aCx,
|
|
|
|
JS::Handle<JS::Value> aValue)
|
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
JS::Rooted<JSObject*> p(aCx, PromiseObj());
|
2016-02-09 22:40:31 +00:00
|
|
|
if (!JS::ResolvePromise(aCx, p, aValue)) {
|
|
|
|
// Now what? There's nothing sane to do here.
|
|
|
|
JS_ClearPendingException(aCx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Promise::MaybeReject(JSContext* aCx,
|
|
|
|
JS::Handle<JS::Value> aValue)
|
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
JS::Rooted<JSObject*> p(aCx, PromiseObj());
|
2016-02-09 22:40:31 +00:00
|
|
|
if (!JS::RejectPromise(aCx, p, aValue)) {
|
|
|
|
// Now what? There's nothing sane to do here.
|
|
|
|
JS_ClearPendingException(aCx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#define SLOT_NATIVEHANDLER 0
|
|
|
|
#define SLOT_NATIVEHANDLER_TASK 1
|
|
|
|
|
|
|
|
enum class NativeHandlerTask : int32_t {
|
|
|
|
Resolve,
|
|
|
|
Reject
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool
|
|
|
|
NativeHandlerCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
|
|
|
|
{
|
|
|
|
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> v(aCx,
|
|
|
|
js::GetFunctionNativeReserved(&args.callee(),
|
|
|
|
SLOT_NATIVEHANDLER));
|
|
|
|
MOZ_ASSERT(v.isObject());
|
|
|
|
|
|
|
|
PromiseNativeHandler* handler;
|
|
|
|
if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &v.toObject(),
|
|
|
|
handler))) {
|
|
|
|
return Throw(aCx, NS_ERROR_UNEXPECTED);
|
|
|
|
}
|
|
|
|
|
|
|
|
v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
|
|
|
|
NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
|
|
|
|
|
|
|
|
if (task == NativeHandlerTask::Resolve) {
|
|
|
|
handler->ResolvedCallback(aCx, args.get(0));
|
|
|
|
} else {
|
|
|
|
MOZ_ASSERT(task == NativeHandlerTask::Reject);
|
|
|
|
handler->RejectedCallback(aCx, args.get(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static JSObject*
|
|
|
|
CreateNativeHandlerFunction(JSContext* aCx, JS::Handle<JSObject*> aHolder,
|
|
|
|
NativeHandlerTask aTask)
|
|
|
|
{
|
|
|
|
JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback,
|
|
|
|
/* nargs = */ 1,
|
|
|
|
/* flags = */ 0, nullptr);
|
|
|
|
if (!func) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
|
|
|
|
|
|
|
|
JS::ExposeObjectToActiveJS(aHolder);
|
|
|
|
js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER,
|
|
|
|
JS::ObjectValue(*aHolder));
|
|
|
|
js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER_TASK,
|
|
|
|
JS::Int32Value(static_cast<int32_t>(aTask)));
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
void
|
|
|
|
Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable)
|
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
AutoJSAPI jsapi;
|
|
|
|
if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
|
|
|
|
// Our API doesn't allow us to return a useful error. Not like this should
|
|
|
|
// happen anyway.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSContext* cx = jsapi.cx();
|
|
|
|
JS::Rooted<JSObject*> handlerWrapper(cx);
|
|
|
|
// Note: PromiseNativeHandler is NOT wrappercached. So we can't use
|
|
|
|
// ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
|
|
|
|
if (NS_WARN_IF(!aRunnable->WrapObject(cx, nullptr, &handlerWrapper))) {
|
|
|
|
// Again, no way to report errors.
|
|
|
|
jsapi.ClearException();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> resolveFunc(cx);
|
|
|
|
resolveFunc =
|
|
|
|
CreateNativeHandlerFunction(cx, handlerWrapper, NativeHandlerTask::Resolve);
|
|
|
|
if (NS_WARN_IF(!resolveFunc)) {
|
|
|
|
jsapi.ClearException();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> rejectFunc(cx);
|
|
|
|
rejectFunc =
|
|
|
|
CreateNativeHandlerFunction(cx, handlerWrapper, NativeHandlerTask::Reject);
|
|
|
|
if (NS_WARN_IF(!rejectFunc)) {
|
|
|
|
jsapi.ClearException();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
JS::Rooted<JSObject*> promiseObj(cx, PromiseObj());
|
2016-02-09 22:40:31 +00:00
|
|
|
if (NS_WARN_IF(!JS::AddPromiseReactions(cx, promiseObj, resolveFunc,
|
|
|
|
rejectFunc))) {
|
|
|
|
jsapi.ClearException();
|
|
|
|
return;
|
|
|
|
}
|
2016-02-09 22:40:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Promise::HandleException(JSContext* aCx)
|
|
|
|
{
|
|
|
|
JS::Rooted<JS::Value> exn(aCx);
|
|
|
|
if (JS_GetPendingException(aCx, &exn)) {
|
|
|
|
JS_ClearPendingException(aCx);
|
|
|
|
// This is only called from MaybeSomething, so it's OK to MaybeReject here,
|
|
|
|
// unlike in the version that's used when !SPIDERMONKEY_PROMISE.
|
|
|
|
MaybeReject(aCx, exn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
already_AddRefed<Promise>
|
|
|
|
Promise::CreateFromExisting(nsIGlobalObject* aGlobal,
|
|
|
|
JS::Handle<JSObject*> aPromiseObj)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(js::GetObjectCompartment(aGlobal->GetGlobalJSObject()) ==
|
|
|
|
js::GetObjectCompartment(aPromiseObj));
|
|
|
|
RefPtr<Promise> p = new Promise(aGlobal);
|
2016-02-09 22:40:31 +00:00
|
|
|
p->mPromiseObj = aPromiseObj;
|
2016-02-09 22:40:31 +00:00
|
|
|
return p.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
#else // SPIDERMONKEY_PROMISE
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
JSObject*
|
|
|
|
Promise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
|
|
|
{
|
|
|
|
return PromiseBinding::Wrap(aCx, this, aGivenProto);
|
|
|
|
}
|
|
|
|
|
2014-07-19 01:31:11 +00:00
|
|
|
already_AddRefed<Promise>
|
2015-07-31 17:30:55 +00:00
|
|
|
Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv,
|
|
|
|
JS::Handle<JSObject*> aDesiredProto)
|
2014-09-12 02:18:49 +00:00
|
|
|
{
|
2016-04-25 09:06:30 +00:00
|
|
|
if (!aGlobal) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
|
|
return nullptr;
|
|
|
|
}
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<Promise> p = new Promise(aGlobal);
|
2015-07-31 17:30:55 +00:00
|
|
|
p->CreateWrapper(aDesiredProto, aRv);
|
2014-09-12 02:18:49 +00:00
|
|
|
if (aRv.Failed()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return p.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-07-31 17:30:55 +00:00
|
|
|
Promise::CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv)
|
2014-02-25 21:34:55 +00:00
|
|
|
{
|
2014-07-19 01:31:11 +00:00
|
|
|
AutoJSAPI jsapi;
|
2014-09-12 02:18:49 +00:00
|
|
|
if (!jsapi.Init(mGlobal)) {
|
2014-07-19 01:31:11 +00:00
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
2014-09-12 02:18:49 +00:00
|
|
|
return;
|
2014-02-25 21:34:55 +00:00
|
|
|
}
|
2014-07-19 01:31:11 +00:00
|
|
|
JSContext* cx = jsapi.cx();
|
2014-02-25 21:34:55 +00:00
|
|
|
|
2014-11-17 09:42:00 +00:00
|
|
|
JS::Rooted<JS::Value> wrapper(cx);
|
2015-07-31 17:30:55 +00:00
|
|
|
if (!GetOrCreateDOMReflector(cx, this, &wrapper, aDesiredProto)) {
|
2014-07-19 01:31:11 +00:00
|
|
|
JS_ClearPendingException(cx);
|
|
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
2014-09-12 02:18:49 +00:00
|
|
|
return;
|
2014-02-25 21:34:55 +00:00
|
|
|
}
|
|
|
|
|
2014-09-12 02:18:49 +00:00
|
|
|
dom::PreserveWrapper(this);
|
2014-10-20 02:27:12 +00:00
|
|
|
|
|
|
|
// Now grab our allocation stack
|
|
|
|
if (!CaptureStack(cx, mAllocationStack)) {
|
|
|
|
JS_ClearPendingException(cx);
|
|
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
return;
|
|
|
|
}
|
2014-11-17 09:42:00 +00:00
|
|
|
|
|
|
|
JS::RootedObject obj(cx, &wrapper.toObject());
|
|
|
|
JS::dbg::onNewPromise(cx, obj);
|
2014-02-25 21:34:55 +00:00
|
|
|
}
|
|
|
|
|
2013-09-26 18:09:16 +00:00
|
|
|
void
|
|
|
|
Promise::MaybeResolve(JSContext* aCx,
|
2013-11-19 18:39:51 +00:00
|
|
|
JS::Handle<JS::Value> aValue)
|
2013-09-26 18:09:16 +00:00
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2013-09-26 18:09:16 +00:00
|
|
|
MaybeResolveInternal(aCx, aValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Promise::MaybeReject(JSContext* aCx,
|
2013-11-19 18:39:51 +00:00
|
|
|
JS::Handle<JS::Value> aValue)
|
2013-09-26 18:09:16 +00:00
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2013-09-26 18:09:16 +00:00
|
|
|
MaybeRejectInternal(aCx, aValue);
|
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
|
2014-09-20 06:20:41 +00:00
|
|
|
void
|
2015-10-18 05:24:48 +00:00
|
|
|
Promise::MaybeReject(const RefPtr<MediaStreamError>& aArg) {
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2014-09-20 06:20:41 +00:00
|
|
|
MaybeSomething(aArg, &Promise::MaybeReject);
|
|
|
|
}
|
|
|
|
|
2016-01-19 06:35:01 +00:00
|
|
|
void
|
|
|
|
Promise::MaybeRejectWithNull()
|
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2016-01-19 06:35:01 +00:00
|
|
|
MaybeSomething(JS::NullHandleValue, &Promise::MaybeReject);
|
2016-06-09 19:26:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Promise::MaybeRejectWithUndefined()
|
|
|
|
{
|
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
|
|
|
MaybeSomething(JS::UndefinedHandleValue, &Promise::MaybeReject);
|
2016-01-19 06:35:01 +00:00
|
|
|
}
|
|
|
|
|
2016-03-22 15:22:24 +00:00
|
|
|
|
|
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
|
|
void
|
|
|
|
Promise::ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(!js::IsWrapper(aPromise));
|
|
|
|
|
|
|
|
MOZ_ASSERT(JS::GetPromiseState(aPromise) == JS::PromiseState::Rejected);
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> result(aCx, JS::GetPromiseResult(aPromise));
|
|
|
|
|
|
|
|
js::ErrorReport report(aCx);
|
|
|
|
if (!report.init(aCx, result, js::ErrorReport::NoSideEffects)) {
|
|
|
|
JS_ClearPendingException(aCx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
|
|
|
|
bool isMainThread = MOZ_LIKELY(NS_IsMainThread());
|
|
|
|
bool isChrome = isMainThread ? nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(aPromise))
|
|
|
|
: GetCurrentThreadWorkerPrivate()->IsChromeWorker();
|
|
|
|
nsGlobalWindow* win = isMainThread ? xpc::WindowGlobalOrNull(aPromise) : nullptr;
|
|
|
|
xpcReport->Init(report.report(), report.message(), isChrome, win ? win->AsInner()->WindowID() : 0);
|
|
|
|
|
|
|
|
// Now post an event to do the real reporting async
|
|
|
|
NS_DispatchToMainThread(new AsyncErrorReporter(xpcReport));
|
|
|
|
}
|
|
|
|
#endif // defined(SPIDERMONKEY_PROMISE)
|
|
|
|
|
2014-11-11 13:47:28 +00:00
|
|
|
bool
|
2014-10-28 12:08:19 +00:00
|
|
|
Promise::PerformMicroTaskCheckpoint()
|
|
|
|
{
|
2016-03-24 15:12:00 +00:00
|
|
|
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
|
2014-10-28 12:08:19 +00:00
|
|
|
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
|
2016-03-24 15:12:00 +00:00
|
|
|
|
|
|
|
// On the main thread, we always use the main promise micro task queue.
|
2015-05-02 02:33:01 +00:00
|
|
|
std::queue<nsCOMPtr<nsIRunnable>>& microtaskQueue =
|
2014-10-28 12:08:19 +00:00
|
|
|
runtime->GetPromiseMicroTaskQueue();
|
|
|
|
|
2015-05-02 02:33:01 +00:00
|
|
|
if (microtaskQueue.empty()) {
|
2014-11-11 13:47:28 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-03-24 15:12:00 +00:00
|
|
|
AutoSafeJSContext cx;
|
2015-04-27 19:00:41 +00:00
|
|
|
|
2014-11-11 13:47:28 +00:00
|
|
|
do {
|
2016-07-05 22:49:06 +00:00
|
|
|
nsCOMPtr<nsIRunnable> runnable = microtaskQueue.front().forget();
|
2014-10-28 12:08:19 +00:00
|
|
|
MOZ_ASSERT(runnable);
|
|
|
|
|
|
|
|
// This function can re-enter, so we remove the element before calling.
|
2015-05-02 02:33:01 +00:00
|
|
|
microtaskQueue.pop();
|
2015-04-27 19:00:41 +00:00
|
|
|
nsresult rv = runnable->Run();
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-03-24 15:12:00 +00:00
|
|
|
JS_CheckForInterrupt(cx);
|
Bug 1179909: Refactor stable state handling. r=smaug
This is motivated by three separate but related problems:
1. Our concept of recursion depth is broken for things that run from AfterProcessNextEvent observers (e.g. Promises). We decrement the recursionDepth counter before firing observers, so a Promise callback running at the lowest event loop depth has a recursion depth of 0 (whereas a regular nsIRunnable would be 1). This is a problem because it's impossible to distinguish a Promise running after a sync XHR's onreadystatechange handler from a top-level event (since the former runs with depth 2 - 1 = 1, and the latter runs with just 1).
2. The nsIThreadObserver mechanism that is used by a lot of code to run "after" the current event is a poor fit for anything that runs script. First, the order the observers fire in is the order they were added, not anything fixed by spec. Additionally, running script can cause the event loop to spin, which is a big source of pain here (bholley has some nasty bug caused by this).
3. We run Promises from different points in the code for workers and main thread. The latter runs from XPConnect's nsIThreadObserver callbacks, while the former runs from a hardcoded call to run Promises in the worker event loop. What workers do is particularly problematic because it means we can't get the right recursion depth no matter what we do to nsThread.
The solve this, this patch does the following:
1. Consolidate some handling of microtasks and all handling of stable state from appshell and WorkerPrivate into CycleCollectedJSRuntime.
2. Make the recursionDepth counter only available to CycleCollectedJSRuntime (and its consumers) and remove it from the nsIThreadInternal and nsIThreadObserver APIs.
3. Adjust the recursionDepth counter so that microtasks run with the recursionDepth of the task they are associated with.
4. Introduce the concept of metastable state to replace appshell's RunBeforeNextEvent. Metastable state is reached after every microtask or task is completed. This provides the semantics that bent and I want for IndexedDB, where transactions autocommit at the end of a microtask and do not "spill" from one microtask into a subsequent microtask. This differs from appshell's RunBeforeNextEvent in two ways:
a) It fires between microtasks, which was the motivation for starting this.
b) It no longer ensures that we're at the same event loop depth in the native event queue. bent decided we don't care about this.
5. Reorder stable state to happen after microtasks such as Promises, per HTML. Right now we call the regular thread observers, including appshell, before the main thread observer (XPConnect), so stable state tasks happen before microtasks.
2015-08-11 13:10:46 +00:00
|
|
|
runtime->AfterProcessMicrotask();
|
2015-05-02 02:33:01 +00:00
|
|
|
} while (!microtaskQueue.empty());
|
2014-11-11 13:47:28 +00:00
|
|
|
|
|
|
|
return true;
|
2014-10-28 12:08:19 +00:00
|
|
|
}
|
|
|
|
|
2016-03-24 15:12:00 +00:00
|
|
|
void
|
|
|
|
Promise::PerformWorkerMicroTaskCheckpoint()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
|
|
|
|
|
|
|
|
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
// For a normal microtask checkpoint, we try to use the debugger microtask
|
|
|
|
// queue first. If the debugger queue is empty, we use the normal microtask
|
|
|
|
// queue instead.
|
|
|
|
std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
|
|
|
|
&runtime->GetDebuggerPromiseMicroTaskQueue();
|
|
|
|
|
|
|
|
if (microtaskQueue->empty()) {
|
|
|
|
microtaskQueue = &runtime->GetPromiseMicroTaskQueue();
|
|
|
|
if (microtaskQueue->empty()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-05 22:49:06 +00:00
|
|
|
nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front().forget();
|
2016-03-24 15:12:00 +00:00
|
|
|
MOZ_ASSERT(runnable);
|
|
|
|
|
|
|
|
// This function can re-enter, so we remove the element before calling.
|
|
|
|
microtaskQueue->pop();
|
|
|
|
nsresult rv = runnable->Run();
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
runtime->AfterProcessMicrotask();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Promise::PerformWorkerDebuggerMicroTaskCheckpoint()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
|
|
|
|
|
|
|
|
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
// For a debugger microtask checkpoint, we always use the debugger microtask
|
|
|
|
// queue.
|
|
|
|
std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
|
|
|
|
&runtime->GetDebuggerPromiseMicroTaskQueue();
|
|
|
|
|
|
|
|
if (microtaskQueue->empty()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-07-05 22:49:06 +00:00
|
|
|
nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front().forget();
|
2016-03-24 15:12:00 +00:00
|
|
|
MOZ_ASSERT(runnable);
|
|
|
|
|
|
|
|
// This function can re-enter, so we remove the element before calling.
|
|
|
|
microtaskQueue->pop();
|
|
|
|
nsresult rv = runnable->Run();
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
runtime->AfterProcessMicrotask();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
|
|
|
|
2013-09-11 16:03:04 +00:00
|
|
|
/* static */ bool
|
2014-01-23 18:47:29 +00:00
|
|
|
Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
|
2013-09-11 16:03:04 +00:00
|
|
|
{
|
|
|
|
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> v(aCx,
|
|
|
|
js::GetFunctionNativeReserved(&args.callee(),
|
|
|
|
SLOT_PROMISE));
|
|
|
|
MOZ_ASSERT(v.isObject());
|
|
|
|
|
|
|
|
Promise* promise;
|
2013-11-21 12:51:16 +00:00
|
|
|
if (NS_FAILED(UNWRAP_OBJECT(Promise, &v.toObject(), promise))) {
|
2013-09-11 16:03:04 +00:00
|
|
|
return Throw(aCx, NS_ERROR_UNEXPECTED);
|
|
|
|
}
|
|
|
|
|
2014-01-23 18:47:29 +00:00
|
|
|
v = js::GetFunctionNativeReserved(&args.callee(), SLOT_DATA);
|
2013-09-11 16:03:04 +00:00
|
|
|
PromiseCallback::Task task = static_cast<PromiseCallback::Task>(v.toInt32());
|
|
|
|
|
|
|
|
if (task == PromiseCallback::Resolve) {
|
2014-10-20 02:27:12 +00:00
|
|
|
if (!promise->CaptureStack(aCx, promise->mFullfillmentStack)) {
|
|
|
|
return false;
|
|
|
|
}
|
2015-09-09 01:23:55 +00:00
|
|
|
promise->MaybeResolveInternal(aCx, args.get(0));
|
2013-09-11 16:03:04 +00:00
|
|
|
} else {
|
2013-11-19 18:39:51 +00:00
|
|
|
promise->MaybeRejectInternal(aCx, args.get(0));
|
2014-10-20 02:27:12 +00:00
|
|
|
if (!promise->CaptureStack(aCx, promise->mRejectionStack)) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-09-11 16:03:04 +00:00
|
|
|
}
|
|
|
|
|
2015-01-13 03:35:33 +00:00
|
|
|
args.rval().setUndefined();
|
2013-09-11 16:03:04 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-01-23 18:47:29 +00:00
|
|
|
/*
|
|
|
|
* Common bits of (JSCallbackThenableResolver/JSCallbackThenableRejecter).
|
|
|
|
* Resolves/rejects the Promise if it is ok to do so, based on whether either of
|
|
|
|
* the callbacks have been called before or not.
|
|
|
|
*/
|
|
|
|
/* static */ bool
|
|
|
|
Promise::ThenableResolverCommon(JSContext* aCx, uint32_t aTask,
|
|
|
|
unsigned aArgc, JS::Value* aVp)
|
|
|
|
{
|
|
|
|
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
|
|
|
|
JS::Rooted<JSObject*> thisFunc(aCx, &args.callee());
|
|
|
|
if (!MarkAsCalledIfNotCalledBefore(aCx, thisFunc)) {
|
|
|
|
// A function from this pair has been called before.
|
2015-01-13 03:35:33 +00:00
|
|
|
args.rval().setUndefined();
|
2014-01-23 18:47:29 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Promise* promise = GetPromise(aCx, thisFunc);
|
|
|
|
MOZ_ASSERT(promise);
|
|
|
|
|
|
|
|
if (aTask == PromiseCallback::Resolve) {
|
2014-05-20 21:55:36 +00:00
|
|
|
promise->ResolveInternal(aCx, args.get(0));
|
2014-01-23 18:47:29 +00:00
|
|
|
} else {
|
2014-05-20 21:55:36 +00:00
|
|
|
promise->RejectInternal(aCx, args.get(0));
|
2014-01-23 18:47:29 +00:00
|
|
|
}
|
2015-01-13 03:35:33 +00:00
|
|
|
|
|
|
|
args.rval().setUndefined();
|
2014-01-23 18:47:29 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */ bool
|
|
|
|
Promise::JSCallbackThenableResolver(JSContext* aCx,
|
|
|
|
unsigned aArgc, JS::Value* aVp)
|
|
|
|
{
|
|
|
|
return ThenableResolverCommon(aCx, PromiseCallback::Resolve, aArgc, aVp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */ bool
|
|
|
|
Promise::JSCallbackThenableRejecter(JSContext* aCx,
|
|
|
|
unsigned aArgc, JS::Value* aVp)
|
|
|
|
{
|
|
|
|
return ThenableResolverCommon(aCx, PromiseCallback::Reject, aArgc, aVp);
|
|
|
|
}
|
|
|
|
|
2013-09-11 16:03:04 +00:00
|
|
|
/* static */ JSObject*
|
2015-03-09 16:50:07 +00:00
|
|
|
Promise::CreateFunction(JSContext* aCx, Promise* aPromise, int32_t aTask)
|
2013-09-11 16:03:04 +00:00
|
|
|
{
|
2015-11-25 20:48:09 +00:00
|
|
|
// If this function ever changes, make sure to update
|
|
|
|
// WrapperPromiseCallback::GetDependentPromise.
|
2013-09-11 16:03:04 +00:00
|
|
|
JSFunction* func = js::NewFunctionWithReserved(aCx, JSCallback,
|
|
|
|
1 /* nargs */, 0 /* flags */,
|
2015-03-09 16:50:07 +00:00
|
|
|
nullptr);
|
2013-09-11 16:03:04 +00:00
|
|
|
if (!func) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> promiseObj(aCx);
|
2014-11-26 19:25:20 +00:00
|
|
|
if (!dom::GetOrCreateDOMReflector(aCx, aPromise, &promiseObj)) {
|
2013-09-11 16:03:04 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-10-09 20:48:10 +00:00
|
|
|
JS::ExposeValueToActiveJS(promiseObj);
|
2013-09-11 16:03:04 +00:00
|
|
|
js::SetFunctionNativeReserved(obj, SLOT_PROMISE, promiseObj);
|
2014-01-23 18:47:29 +00:00
|
|
|
js::SetFunctionNativeReserved(obj, SLOT_DATA, JS::Int32Value(aTask));
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */ JSObject*
|
|
|
|
Promise::CreateThenableFunction(JSContext* aCx, Promise* aPromise, uint32_t aTask)
|
|
|
|
{
|
|
|
|
JSNative whichFunc =
|
|
|
|
aTask == PromiseCallback::Resolve ? JSCallbackThenableResolver :
|
|
|
|
JSCallbackThenableRejecter ;
|
|
|
|
|
|
|
|
JSFunction* func = js::NewFunctionWithReserved(aCx, whichFunc,
|
|
|
|
1 /* nargs */, 0 /* flags */,
|
2015-03-09 16:50:07 +00:00
|
|
|
nullptr);
|
2014-01-23 18:47:29 +00:00
|
|
|
if (!func) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> promiseObj(aCx);
|
2014-11-26 19:25:20 +00:00
|
|
|
if (!dom::GetOrCreateDOMReflector(aCx, aPromise, &promiseObj)) {
|
2014-01-23 18:47:29 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-10-09 20:48:10 +00:00
|
|
|
JS::ExposeValueToActiveJS(promiseObj);
|
2014-01-23 18:47:29 +00:00
|
|
|
js::SetFunctionNativeReserved(obj, SLOT_PROMISE, promiseObj);
|
2013-09-11 16:03:04 +00:00
|
|
|
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
2013-07-11 20:40:36 +00:00
|
|
|
/* static */ already_AddRefed<Promise>
|
2015-07-31 17:30:55 +00:00
|
|
|
Promise::Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
|
|
|
|
ErrorResult& aRv, JS::Handle<JSObject*> aDesiredProto)
|
2013-06-12 01:41:21 +00:00
|
|
|
{
|
2014-02-25 21:34:55 +00:00
|
|
|
nsCOMPtr<nsIGlobalObject> global;
|
|
|
|
global = do_QueryInterface(aGlobal.GetAsSupports());
|
|
|
|
if (!global) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
|
|
return nullptr;
|
2013-06-12 01:41:21 +00:00
|
|
|
}
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<Promise> promise = Create(global, aRv, aDesiredProto);
|
2014-07-19 01:31:11 +00:00
|
|
|
if (aRv.Failed()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2013-06-12 01:41:21 +00:00
|
|
|
|
2014-09-12 02:18:49 +00:00
|
|
|
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();
|
|
|
|
|
2013-09-11 16:03:04 +00:00
|
|
|
JS::Rooted<JSObject*> resolveFunc(cx,
|
2015-03-09 16:50:07 +00:00
|
|
|
CreateFunction(cx, this,
|
2013-09-11 16:03:04 +00:00
|
|
|
PromiseCallback::Resolve));
|
|
|
|
if (!resolveFunc) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
2014-09-12 02:18:49 +00:00
|
|
|
return;
|
2013-09-11 16:03:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> rejectFunc(cx,
|
2015-03-09 16:50:07 +00:00
|
|
|
CreateFunction(cx, this,
|
2013-09-11 16:03:04 +00:00
|
|
|
PromiseCallback::Reject));
|
|
|
|
if (!rejectFunc) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
2014-09-12 02:18:49 +00:00
|
|
|
return;
|
2013-09-11 16:03:04 +00:00
|
|
|
}
|
|
|
|
|
2015-04-09 01:23:48 +00:00
|
|
|
aInit.Call(resolveFunc, rejectFunc, aRv, "promise initializer",
|
|
|
|
CallbackObject::eRethrowExceptions, Compartment());
|
2013-06-12 01:41:21 +00:00
|
|
|
aRv.WouldReportJSException();
|
|
|
|
|
2015-11-20 21:29:41 +00:00
|
|
|
if (aRv.Failed()) {
|
2016-03-10 09:50:56 +00:00
|
|
|
if (aRv.IsUncatchableException()) {
|
|
|
|
// Just propagate this to the caller.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:08 +00:00
|
|
|
// There are two possibilities here. Either we've got a rethrown exception,
|
|
|
|
// or we reported that already and synthesized a generic NS_ERROR_FAILURE on
|
|
|
|
// the ErrorResult. In the former case, it doesn't much matter how we get
|
|
|
|
// the exception JS::Value from the ErrorResult to us, since we'll just end
|
|
|
|
// up wrapping it into the right compartment as needed if we hand it to
|
|
|
|
// someone. But in the latter case we have to ensure that the new exception
|
|
|
|
// object we create is created in our reflector compartment, not in our
|
|
|
|
// current compartment, because in the case when we're a Promise constructor
|
|
|
|
// called over Xrays creating it in the current compartment would mean
|
|
|
|
// rejecting with a value that can't be accessed by code that can call
|
|
|
|
// then() on this Promise.
|
2015-11-20 21:29:41 +00:00
|
|
|
//
|
2015-11-25 20:48:08 +00:00
|
|
|
// Luckily, MaybeReject(aRv) does exactly what we want here: it enters our
|
|
|
|
// reflector compartment before trying to produce a JS::Value from the
|
|
|
|
// ErrorResult.
|
|
|
|
MaybeReject(aRv);
|
2015-11-25 20:48:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
#define GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT 0
|
|
|
|
#define GET_CAPABILITIES_EXECUTOR_REJECT_SLOT 1
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
bool
|
|
|
|
GetCapabilitiesExecutor(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
|
|
|
|
{
|
|
|
|
// Implements
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-getcapabilitiesexecutor-functions
|
|
|
|
// except we store the [[Resolve]] and [[Reject]] in our own internal slots,
|
|
|
|
// not in a PromiseCapability. The PromiseCapability will then read them from
|
|
|
|
// us.
|
|
|
|
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
|
|
|
|
|
|
|
|
// Step 1 is an assert.
|
|
|
|
|
|
|
|
// Step 2 doesn't need to be done, because it's just giving a name to the
|
|
|
|
// PromiseCapability record which is supposed to be stored in an internal
|
|
|
|
// slot. But we don't store that at all, per the comment above; we just
|
|
|
|
// directly store its [[Resolve]] and [[Reject]] members.
|
|
|
|
|
|
|
|
// Steps 3 and 4.
|
|
|
|
if (!js::GetFunctionNativeReserved(&args.callee(),
|
|
|
|
GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT).isUndefined() ||
|
|
|
|
!js::GetFunctionNativeReserved(&args.callee(),
|
|
|
|
GET_CAPABILITIES_EXECUTOR_REJECT_SLOT).isUndefined()) {
|
|
|
|
ErrorResult rv;
|
|
|
|
rv.ThrowTypeError<MSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY>();
|
|
|
|
return !rv.MaybeSetPendingException(aCx);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 5.
|
|
|
|
js::SetFunctionNativeReserved(&args.callee(),
|
|
|
|
GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT,
|
|
|
|
args.get(0));
|
|
|
|
|
|
|
|
// Step 6.
|
|
|
|
js::SetFunctionNativeReserved(&args.callee(),
|
|
|
|
GET_CAPABILITIES_EXECUTOR_REJECT_SLOT,
|
|
|
|
args.get(1));
|
|
|
|
|
|
|
|
// Step 7.
|
|
|
|
args.rval().setUndefined();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
/* static */ void
|
|
|
|
Promise::NewPromiseCapability(JSContext* aCx, nsIGlobalObject* aGlobal,
|
|
|
|
JS::Handle<JS::Value> aConstructor,
|
|
|
|
bool aForceCallbackCreation,
|
|
|
|
PromiseCapability& aCapability,
|
|
|
|
ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
// Implements
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-newpromisecapability
|
|
|
|
|
|
|
|
if (!aConstructor.isObject() ||
|
|
|
|
!JS::IsConstructor(&aConstructor.toObject())) {
|
|
|
|
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 2 is a note.
|
|
|
|
// Step 3 is already done because we got the PromiseCapability passed in.
|
|
|
|
|
|
|
|
// Optimization: Check whether constructor is in fact the canonical
|
|
|
|
// Promise constructor for aGlobal.
|
|
|
|
JS::Rooted<JSObject*> global(aCx, aGlobal->GetGlobalJSObject());
|
|
|
|
{
|
|
|
|
// Scope for the JSAutoCompartment, since we need to enter the compartment
|
|
|
|
// of global to get constructors from it. Save the compartment we used to
|
|
|
|
// be in, though; we'll need it later.
|
|
|
|
JS::Rooted<JSObject*> callerGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
|
|
|
|
JSAutoCompartment ac(aCx, global);
|
|
|
|
|
|
|
|
// Now wrap aConstructor into the compartment of aGlobal, so comparing it to
|
|
|
|
// the canonical Promise for that compartment actually makes sense.
|
|
|
|
JS::Rooted<JS::Value> constructorValue(aCx, aConstructor);
|
|
|
|
if (!MaybeWrapObjectValue(aCx, &constructorValue)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-07-09 04:19:52 +00:00
|
|
|
JSObject* defaultCtor = PromiseBinding::GetConstructorObject(aCx);
|
2015-11-25 20:48:09 +00:00
|
|
|
if (!defaultCtor) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (defaultCtor == &constructorValue.toObject()) {
|
|
|
|
// This is the canonical Promise constructor.
|
|
|
|
aCapability.mNativePromise = Promise::Create(aGlobal, aRv);
|
|
|
|
if (aForceCallbackCreation) {
|
|
|
|
// We have to be a bit careful here. We want to create these functions
|
|
|
|
// in the compartment in which they would be created if we actually
|
|
|
|
// invoked the constructor via JS::Construct below. That means our
|
|
|
|
// callerGlobal compartment if aConstructor is an Xray and the reflector
|
|
|
|
// compartment of the promise we're creating otherwise. But note that
|
|
|
|
// our callerGlobal compartment is precisely the reflector compartment
|
|
|
|
// unless the call was done over Xrays, because the reflector
|
|
|
|
// compartment comes from xpc::XrayAwareCalleeGlobal. So we really just
|
|
|
|
// want to create these functions in the callerGlobal compartment.
|
|
|
|
MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(&aConstructor.toObject()) ||
|
|
|
|
callerGlobal == global);
|
|
|
|
JSAutoCompartment ac2(aCx, callerGlobal);
|
|
|
|
|
|
|
|
JSObject* resolveFuncObj =
|
|
|
|
CreateFunction(aCx, aCapability.mNativePromise,
|
|
|
|
PromiseCallback::Resolve);
|
|
|
|
if (!resolveFuncObj) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
aCapability.mResolve.setObject(*resolveFuncObj);
|
|
|
|
|
|
|
|
JSObject* rejectFuncObj =
|
|
|
|
CreateFunction(aCx, aCapability.mNativePromise,
|
|
|
|
PromiseCallback::Reject);
|
|
|
|
if (!rejectFuncObj) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
aCapability.mReject.setObject(*rejectFuncObj);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 4.
|
|
|
|
// We can create our get-capabilities function in the calling compartment. It
|
|
|
|
// will work just as if we did |new promiseConstructor(function(a,b){}).
|
|
|
|
// Notably, if we're called over Xrays that's all fine, because we will end up
|
|
|
|
// creating the callbacks in the caller compartment in that case.
|
|
|
|
JSFunction* getCapabilitiesFunc =
|
|
|
|
js::NewFunctionWithReserved(aCx, GetCapabilitiesExecutor,
|
|
|
|
2 /* nargs */,
|
|
|
|
0 /* flags */,
|
|
|
|
nullptr);
|
|
|
|
if (!getCapabilitiesFunc) {
|
|
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> getCapabilitiesObj(aCx);
|
|
|
|
getCapabilitiesObj = JS_GetFunctionObject(getCapabilitiesFunc);
|
|
|
|
|
|
|
|
// Step 5 doesn't need to be done, since we're not actually storing a
|
|
|
|
// PromiseCapability in the executor; see the comments in
|
|
|
|
// GetCapabilitiesExecutor above.
|
|
|
|
|
|
|
|
// Step 6 and step 7.
|
|
|
|
JS::Rooted<JS::Value> getCapabilities(aCx,
|
|
|
|
JS::ObjectValue(*getCapabilitiesObj));
|
2015-10-28 11:08:27 +00:00
|
|
|
JS::Rooted<JSObject*> promiseObj(aCx);
|
2015-11-25 20:48:09 +00:00
|
|
|
if (!JS::Construct(aCx, aConstructor,
|
|
|
|
JS::HandleValueArray(getCapabilities),
|
2015-10-28 11:08:27 +00:00
|
|
|
&promiseObj)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 8 plus copying over the value to the PromiseCapability.
|
|
|
|
JS::Rooted<JS::Value> v(aCx);
|
|
|
|
v = js::GetFunctionNativeReserved(getCapabilitiesObj,
|
|
|
|
GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT);
|
|
|
|
if (!v.isObject() || !JS::IsCallable(&v.toObject())) {
|
|
|
|
aRv.ThrowTypeError<MSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE>();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
aCapability.mResolve = v;
|
|
|
|
|
|
|
|
// Step 9 plus copying over the value to the PromiseCapability.
|
|
|
|
v = js::GetFunctionNativeReserved(getCapabilitiesObj,
|
|
|
|
GET_CAPABILITIES_EXECUTOR_REJECT_SLOT);
|
|
|
|
if (!v.isObject() || !JS::IsCallable(&v.toObject())) {
|
|
|
|
aRv.ThrowTypeError<MSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE>();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
aCapability.mReject = v;
|
|
|
|
|
|
|
|
// Step 10.
|
2016-01-27 07:26:39 +00:00
|
|
|
aCapability.mPromise = promiseObj;
|
2015-11-25 20:48:09 +00:00
|
|
|
|
|
|
|
// Step 11 doesn't need anything, since the PromiseCapability was passed in.
|
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
/* static */ void
|
2015-11-25 20:48:08 +00:00
|
|
|
Promise::Resolve(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
|
2015-11-25 20:48:09 +00:00
|
|
|
JS::Handle<JS::Value> aValue,
|
|
|
|
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv)
|
2013-06-12 01:41:22 +00:00
|
|
|
{
|
2015-11-25 20:48:09 +00:00
|
|
|
// Implementation of
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-promise.resolve
|
|
|
|
|
|
|
|
JSContext* cx = aGlobal.Context();
|
|
|
|
|
|
|
|
nsCOMPtr<nsIGlobalObject> global =
|
|
|
|
do_QueryInterface(aGlobal.GetAsSupports());
|
|
|
|
if (!global) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Steps 1 and 2.
|
|
|
|
if (!aThisv.isObject()) {
|
|
|
|
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 3. If a Promise was passed and matches our constructor, just return it.
|
2014-02-19 15:13:38 +00:00
|
|
|
if (aValue.isObject()) {
|
2015-11-25 20:48:09 +00:00
|
|
|
JS::Rooted<JSObject*> valueObj(cx, &aValue.toObject());
|
2014-02-10 17:27:02 +00:00
|
|
|
Promise* nextPromise;
|
|
|
|
nsresult rv = UNWRAP_OBJECT(Promise, valueObj, nextPromise);
|
|
|
|
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
2015-11-25 20:48:09 +00:00
|
|
|
JS::Rooted<JS::Value> constructor(cx);
|
|
|
|
if (!JS_GetProperty(cx, valueObj, "constructor", &constructor)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(cx);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cheat instead of calling JS_SameValue, since we know one's an object.
|
|
|
|
if (aThisv == constructor) {
|
|
|
|
aRetval.setObject(*valueObj);
|
|
|
|
return;
|
|
|
|
}
|
2014-02-10 17:27:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// Step 4.
|
|
|
|
PromiseCapability capability(cx);
|
|
|
|
NewPromiseCapability(cx, global, aThisv, false, capability, aRv);
|
|
|
|
// Step 5.
|
|
|
|
if (aRv.Failed()) {
|
|
|
|
return;
|
2013-06-12 01:41:22 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// Step 6.
|
|
|
|
Promise* p = capability.mNativePromise;
|
2014-10-20 02:27:12 +00:00
|
|
|
if (p) {
|
2015-11-25 20:48:09 +00:00
|
|
|
p->MaybeResolveInternal(cx, aValue);
|
2014-10-20 02:27:12 +00:00
|
|
|
p->mFullfillmentStack = p->mAllocationStack;
|
2015-11-25 20:48:09 +00:00
|
|
|
} else {
|
|
|
|
JS::Rooted<JS::Value> value(cx, aValue);
|
|
|
|
JS::Rooted<JS::Value> ignored(cx);
|
|
|
|
if (!JS::Call(cx, JS::UndefinedHandleValue /* thisVal */,
|
|
|
|
capability.mResolve, JS::HandleValueArray(value),
|
|
|
|
&ignored)) {
|
|
|
|
// Step 7.
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(cx);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-10-20 02:27:12 +00:00
|
|
|
}
|
2015-11-25 20:48:09 +00:00
|
|
|
|
|
|
|
// Step 8.
|
|
|
|
aRetval.set(capability.PromiseValue());
|
2014-01-13 22:36:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */ already_AddRefed<Promise>
|
2014-02-25 21:34:55 +00:00
|
|
|
Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
|
2014-02-10 17:27:02 +00:00
|
|
|
JS::Handle<JS::Value> aValue, ErrorResult& aRv)
|
2014-01-13 22:36:03 +00:00
|
|
|
{
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<Promise> promise = Create(aGlobal, aRv);
|
2014-07-19 01:31:11 +00:00
|
|
|
if (aRv.Failed()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2013-06-12 01:41:22 +00:00
|
|
|
|
2014-01-13 22:36:03 +00:00
|
|
|
promise->MaybeResolveInternal(aCx, aValue);
|
2013-07-11 20:40:36 +00:00
|
|
|
return promise.forget();
|
2013-06-12 01:41:22 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
/* static */ void
|
2015-11-25 20:48:08 +00:00
|
|
|
Promise::Reject(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
|
2015-11-25 20:48:09 +00:00
|
|
|
JS::Handle<JS::Value> aValue,
|
|
|
|
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv)
|
2013-06-12 01:41:22 +00:00
|
|
|
{
|
2015-11-25 20:48:09 +00:00
|
|
|
// Implementation of
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-promise.reject
|
|
|
|
|
|
|
|
JSContext* cx = aGlobal.Context();
|
|
|
|
|
2014-02-25 21:34:55 +00:00
|
|
|
nsCOMPtr<nsIGlobalObject> global =
|
|
|
|
do_QueryInterface(aGlobal.GetAsSupports());
|
|
|
|
if (!global) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Steps 1 and 2.
|
|
|
|
if (!aThisv.isObject()) {
|
|
|
|
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 3.
|
|
|
|
PromiseCapability capability(cx);
|
|
|
|
NewPromiseCapability(cx, global, aThisv, false, capability, aRv);
|
|
|
|
// Step 4.
|
|
|
|
if (aRv.Failed()) {
|
|
|
|
return;
|
2013-06-12 01:41:22 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// Step 5.
|
|
|
|
Promise* p = capability.mNativePromise;
|
2014-10-20 02:27:12 +00:00
|
|
|
if (p) {
|
2015-11-25 20:48:09 +00:00
|
|
|
p->MaybeRejectInternal(cx, aValue);
|
2014-10-20 02:27:12 +00:00
|
|
|
p->mRejectionStack = p->mAllocationStack;
|
2015-11-25 20:48:09 +00:00
|
|
|
} else {
|
|
|
|
JS::Rooted<JS::Value> value(cx, aValue);
|
|
|
|
JS::Rooted<JS::Value> ignored(cx);
|
|
|
|
if (!JS::Call(cx, JS::UndefinedHandleValue /* thisVal */,
|
|
|
|
capability.mReject, JS::HandleValueArray(value),
|
|
|
|
&ignored)) {
|
|
|
|
// Step 6.
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(cx);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-10-20 02:27:12 +00:00
|
|
|
}
|
2015-11-25 20:48:09 +00:00
|
|
|
|
|
|
|
// Step 7.
|
|
|
|
aRetval.set(capability.PromiseValue());
|
2014-01-13 22:36:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */ already_AddRefed<Promise>
|
2014-02-25 21:34:55 +00:00
|
|
|
Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
|
2014-01-13 22:36:03 +00:00
|
|
|
JS::Handle<JS::Value> aValue, ErrorResult& aRv)
|
|
|
|
{
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<Promise> promise = Create(aGlobal, aRv);
|
2014-07-19 01:31:11 +00:00
|
|
|
if (aRv.Failed()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2013-06-12 01:41:22 +00:00
|
|
|
|
2014-01-13 22:36:03 +00:00
|
|
|
promise->MaybeRejectInternal(aCx, aValue);
|
2013-07-11 20:40:36 +00:00
|
|
|
return promise.forget();
|
2013-06-12 01:41:22 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:10 +00:00
|
|
|
namespace {
|
|
|
|
void
|
|
|
|
SpeciesConstructor(JSContext* aCx,
|
|
|
|
JS::Handle<JSObject*> promise,
|
|
|
|
JS::Handle<JS::Value> defaultCtor,
|
|
|
|
JS::MutableHandle<JS::Value> ctor,
|
|
|
|
ErrorResult& aRv)
|
2015-11-25 20:48:10 +00:00
|
|
|
{
|
2015-11-25 20:48:10 +00:00
|
|
|
// Implements
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-speciesconstructor
|
|
|
|
|
|
|
|
// Step 1.
|
|
|
|
MOZ_ASSERT(promise);
|
|
|
|
|
|
|
|
// Step 2.
|
|
|
|
JS::Rooted<JS::Value> constructorVal(aCx);
|
|
|
|
if (!JS_GetProperty(aCx, promise, "constructor", &constructorVal)) {
|
|
|
|
// Step 3.
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 4.
|
|
|
|
if (constructorVal.isUndefined()) {
|
|
|
|
ctor.set(defaultCtor);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 5.
|
|
|
|
if (!constructorVal.isObject()) {
|
|
|
|
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 6.
|
|
|
|
JS::Rooted<jsid> species(aCx,
|
|
|
|
SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::species)));
|
|
|
|
JS::Rooted<JS::Value> speciesVal(aCx);
|
|
|
|
JS::Rooted<JSObject*> constructorObj(aCx, &constructorVal.toObject());
|
|
|
|
if (!JS_GetPropertyById(aCx, constructorObj, species, &speciesVal)) {
|
|
|
|
// Step 7.
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 8.
|
|
|
|
if (speciesVal.isNullOrUndefined()) {
|
|
|
|
ctor.set(defaultCtor);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 9.
|
|
|
|
if (speciesVal.isObject() && JS::IsConstructor(&speciesVal.toObject())) {
|
|
|
|
ctor.set(speciesVal);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 10.
|
|
|
|
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
|
|
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
void
|
|
|
|
Promise::Then(JSContext* aCx, JS::Handle<JSObject*> aCalleeGlobal,
|
|
|
|
AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
|
|
|
|
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv)
|
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2015-11-25 20:48:10 +00:00
|
|
|
// Implements
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-promise.prototype.then
|
|
|
|
|
|
|
|
// Step 1.
|
|
|
|
JS::Rooted<JS::Value> promiseVal(aCx, JS::ObjectValue(*GetWrapper()));
|
|
|
|
if (!MaybeWrapObjectValue(aCx, &promiseVal)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
JS::Rooted<JSObject*> promiseObj(aCx, &promiseVal.toObject());
|
|
|
|
MOZ_ASSERT(promiseObj);
|
|
|
|
|
|
|
|
// Step 2 was done by the bindings.
|
|
|
|
|
|
|
|
// Step 3. We want to use aCalleeGlobal here because it will do the
|
|
|
|
// right thing for us via Xrays (where we won't find @@species on
|
|
|
|
// our promise constructor for now).
|
|
|
|
JS::Rooted<JS::Value> defaultCtorVal(aCx);
|
|
|
|
{ // Scope for JSAutoCompartment
|
|
|
|
JSAutoCompartment ac(aCx, aCalleeGlobal);
|
2016-07-09 04:19:52 +00:00
|
|
|
JSObject* defaultCtor = PromiseBinding::GetConstructorObject(aCx);
|
2015-11-25 20:48:10 +00:00
|
|
|
if (!defaultCtor) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
defaultCtorVal.setObject(*defaultCtor);
|
|
|
|
}
|
|
|
|
if (!MaybeWrapObjectValue(aCx, &defaultCtorVal)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> constructor(aCx);
|
|
|
|
SpeciesConstructor(aCx, promiseObj, defaultCtorVal, &constructor, aRv);
|
2015-11-25 20:48:10 +00:00
|
|
|
if (aRv.Failed()) {
|
2015-11-25 20:48:10 +00:00
|
|
|
// Step 4.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 5.
|
|
|
|
GlobalObject globalObj(aCx, GetWrapper());
|
|
|
|
if (globalObj.Failed()) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
nsCOMPtr<nsIGlobalObject> globalObject =
|
|
|
|
do_QueryInterface(globalObj.GetAsSupports());
|
|
|
|
if (!globalObject) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
PromiseCapability capability(aCx);
|
|
|
|
NewPromiseCapability(aCx, globalObject, constructor, false, capability, aRv);
|
|
|
|
if (aRv.Failed()) {
|
|
|
|
// Step 6.
|
|
|
|
return;
|
2014-07-19 01:31:11 +00:00
|
|
|
}
|
2013-06-12 01:41:22 +00:00
|
|
|
|
2015-11-25 20:48:10 +00:00
|
|
|
// Now step 7: start
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-performpromisethen
|
|
|
|
|
|
|
|
// Step 1 and step 2 are just assertions.
|
|
|
|
|
|
|
|
// Step 3 and step 4 are kinda handled for us already; we use null
|
|
|
|
// to represent "Identity" and "Thrower".
|
|
|
|
|
|
|
|
// Steps 5 and 6. These branch based on whether we know we have a
|
|
|
|
// vanilla Promise or not.
|
2014-04-09 08:30:24 +00:00
|
|
|
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
|
2015-11-25 20:48:10 +00:00
|
|
|
if (capability.mNativePromise) {
|
|
|
|
Promise* promise = capability.mNativePromise;
|
2015-11-25 20:48:10 +00:00
|
|
|
|
2015-11-25 20:48:10 +00:00
|
|
|
RefPtr<PromiseCallback> resolveCb =
|
|
|
|
PromiseCallback::Factory(promise, global, aResolveCallback,
|
|
|
|
PromiseCallback::Resolve);
|
2015-11-25 20:48:10 +00:00
|
|
|
|
2015-11-25 20:48:10 +00:00
|
|
|
RefPtr<PromiseCallback> rejectCb =
|
|
|
|
PromiseCallback::Factory(promise, global, aRejectCallback,
|
|
|
|
PromiseCallback::Reject);
|
2015-11-25 20:48:10 +00:00
|
|
|
|
2015-11-25 20:48:10 +00:00
|
|
|
AppendCallbacks(resolveCb, rejectCb);
|
|
|
|
} else {
|
|
|
|
JS::Rooted<JSObject*> resolveObj(aCx, &capability.mResolve.toObject());
|
|
|
|
RefPtr<AnyCallback> resolveFunc =
|
|
|
|
new AnyCallback(aCx, resolveObj, GetIncumbentGlobal());
|
2015-11-25 20:48:10 +00:00
|
|
|
|
2015-11-25 20:48:10 +00:00
|
|
|
JS::Rooted<JSObject*> rejectObj(aCx, &capability.mReject.toObject());
|
|
|
|
RefPtr<AnyCallback> rejectFunc =
|
|
|
|
new AnyCallback(aCx, rejectObj, GetIncumbentGlobal());
|
|
|
|
|
2016-01-27 07:26:39 +00:00
|
|
|
if (!capability.mPromise) {
|
2015-11-25 20:48:10 +00:00
|
|
|
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
|
|
|
|
return;
|
|
|
|
}
|
2016-01-27 07:26:39 +00:00
|
|
|
JS::Rooted<JSObject*> newPromiseObj(aCx, capability.mPromise);
|
2015-11-25 20:48:10 +00:00
|
|
|
// We want to store the reflector itself.
|
|
|
|
newPromiseObj = js::CheckedUnwrap(newPromiseObj);
|
|
|
|
if (!newPromiseObj) {
|
|
|
|
// Just throw something.
|
|
|
|
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
RefPtr<PromiseCallback> resolveCb;
|
|
|
|
if (aResolveCallback) {
|
|
|
|
resolveCb = new WrapperPromiseCallback(global, aResolveCallback,
|
|
|
|
newPromiseObj,
|
|
|
|
resolveFunc, rejectFunc);
|
|
|
|
} else {
|
|
|
|
resolveCb = new InvokePromiseFuncCallback(global, newPromiseObj,
|
|
|
|
resolveFunc);
|
|
|
|
}
|
|
|
|
|
|
|
|
RefPtr<PromiseCallback> rejectCb;
|
|
|
|
if (aRejectCallback) {
|
|
|
|
rejectCb = new WrapperPromiseCallback(global, aRejectCallback,
|
|
|
|
newPromiseObj,
|
|
|
|
resolveFunc, rejectFunc);
|
|
|
|
} else {
|
|
|
|
rejectCb = new InvokePromiseFuncCallback(global, newPromiseObj,
|
|
|
|
rejectFunc);
|
|
|
|
}
|
|
|
|
|
|
|
|
AppendCallbacks(resolveCb, rejectCb);
|
|
|
|
}
|
|
|
|
|
|
|
|
aRetval.set(capability.PromiseValue());
|
2013-06-12 01:41:22 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:10 +00:00
|
|
|
void
|
|
|
|
Promise::Catch(JSContext* aCx, AnyCallback* aRejectCallback,
|
|
|
|
JS::MutableHandle<JS::Value> aRetval,
|
|
|
|
ErrorResult& aRv)
|
2013-06-12 01:41:22 +00:00
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2015-11-25 20:48:10 +00:00
|
|
|
// Implements
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-promise.prototype.catch
|
|
|
|
|
|
|
|
// We can't call Promise::Then directly, because someone might have
|
|
|
|
// overridden Promise.prototype.then.
|
|
|
|
JS::Rooted<JS::Value> promiseVal(aCx, JS::ObjectValue(*GetWrapper()));
|
|
|
|
if (!MaybeWrapObjectValue(aCx, &promiseVal)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
JS::Rooted<JSObject*> promiseObj(aCx, &promiseVal.toObject());
|
|
|
|
MOZ_ASSERT(promiseObj);
|
|
|
|
JS::AutoValueArray<2> callbacks(aCx);
|
|
|
|
callbacks[0].setUndefined();
|
|
|
|
if (aRejectCallback) {
|
|
|
|
callbacks[1].setObject(*aRejectCallback->Callable());
|
|
|
|
// It could be in any compartment, so put it in ours.
|
|
|
|
if (!MaybeWrapObjectValue(aCx, callbacks[1])) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
callbacks[1].setNull();
|
|
|
|
}
|
|
|
|
if (!JS_CallFunctionName(aCx, promiseObj, "then", callbacks, aRetval)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(aCx);
|
2015-11-25 20:48:10 +00:00
|
|
|
}
|
2013-06-12 01:41:21 +00:00
|
|
|
}
|
|
|
|
|
2013-11-19 21:53:00 +00:00
|
|
|
/**
|
|
|
|
* The CountdownHolder class encapsulates Promise.all countdown functions and
|
|
|
|
* the countdown holder parts of the Promises spec. It maintains the result
|
2015-08-06 15:18:30 +00:00
|
|
|
* array and AllResolveElementFunctions use SetValue() to set the array indices.
|
2013-11-19 21:53:00 +00:00
|
|
|
*/
|
2015-03-21 16:28:04 +00:00
|
|
|
class CountdownHolder final : public nsISupports
|
2013-11-19 21:53:00 +00:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
|
|
|
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CountdownHolder)
|
|
|
|
|
2014-04-09 08:30:24 +00:00
|
|
|
CountdownHolder(const GlobalObject& aGlobal, Promise* aPromise,
|
|
|
|
uint32_t aCountdown)
|
2013-11-19 21:53:00 +00:00
|
|
|
: mPromise(aPromise), mCountdown(aCountdown)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aCountdown != 0);
|
2014-06-16 18:08:00 +00:00
|
|
|
JSContext* cx = aGlobal.Context();
|
2014-04-09 08:30:24 +00:00
|
|
|
|
2014-06-16 18:08:00 +00:00
|
|
|
// The only time aGlobal.Context() and aGlobal.Get() are not
|
2014-04-09 08:30:24 +00:00
|
|
|
// same-compartment is when we're called via Xrays, and in that situation we
|
|
|
|
// in fact want to create the array in the callee compartment
|
|
|
|
|
2013-11-19 21:53:00 +00:00
|
|
|
JSAutoCompartment ac(cx, aGlobal.Get());
|
2014-02-12 10:50:46 +00:00
|
|
|
mValues = JS_NewArrayObject(cx, aCountdown);
|
2013-11-19 21:53:00 +00:00
|
|
|
mozilla::HoldJSObjects(this);
|
|
|
|
}
|
|
|
|
|
2014-06-23 19:56:07 +00:00
|
|
|
private:
|
2013-11-19 21:53:00 +00:00
|
|
|
~CountdownHolder()
|
|
|
|
{
|
|
|
|
mozilla::DropJSObjects(this);
|
|
|
|
}
|
|
|
|
|
2014-06-23 19:56:07 +00:00
|
|
|
public:
|
2013-11-19 21:53:00 +00:00
|
|
|
void SetValue(uint32_t index, const JS::Handle<JS::Value> aValue)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(mCountdown > 0);
|
|
|
|
|
2016-03-15 00:48:39 +00:00
|
|
|
AutoJSAPI jsapi;
|
|
|
|
if (!jsapi.Init(mValues)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
JSContext* cx = jsapi.cx();
|
2013-11-19 21:53:00 +00:00
|
|
|
|
2016-03-15 00:48:39 +00:00
|
|
|
JS::Rooted<JS::Value> value(cx, aValue);
|
|
|
|
JS::Rooted<JSObject*> values(cx, mValues);
|
|
|
|
if (!JS_WrapValue(cx, &value) ||
|
|
|
|
!JS_DefineElement(cx, values, index, value, JSPROP_ENUMERATE)) {
|
|
|
|
MOZ_ASSERT(JS_IsExceptionPending(cx));
|
|
|
|
JS::Rooted<JS::Value> exn(cx);
|
2016-07-14 03:18:11 +00:00
|
|
|
if (!jsapi.StealException(&exn)) {
|
|
|
|
mPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
} else {
|
|
|
|
mPromise->MaybeReject(cx, exn);
|
|
|
|
}
|
2013-11-19 21:53:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
--mCountdown;
|
|
|
|
if (mCountdown == 0) {
|
|
|
|
JS::Rooted<JS::Value> result(cx, JS::ObjectValue(*mValues));
|
|
|
|
mPromise->MaybeResolve(cx, result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<Promise> mPromise;
|
2013-11-19 21:53:00 +00:00
|
|
|
uint32_t mCountdown;
|
|
|
|
JS::Heap<JSObject*> mValues;
|
|
|
|
};
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(CountdownHolder)
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(CountdownHolder)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(CountdownHolder)
|
|
|
|
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CountdownHolder)
|
|
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
|
|
NS_INTERFACE_MAP_END
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CountdownHolder)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mValues)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CountdownHolder)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CountdownHolder)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
|
|
|
|
tmp->mValues = nullptr;
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
|
|
|
|
/**
|
2015-08-06 15:18:30 +00:00
|
|
|
* An AllResolveElementFunction is the per-promise
|
|
|
|
* part of the Promise.all() algorithm.
|
2013-11-19 21:53:00 +00:00
|
|
|
* Every Promise in the handler is handed an instance of this as a resolution
|
|
|
|
* handler and it sets the relevant index in the CountdownHolder.
|
|
|
|
*/
|
2015-08-06 15:18:30 +00:00
|
|
|
class AllResolveElementFunction final : public PromiseNativeHandler
|
2013-11-19 21:53:00 +00:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
2015-08-06 15:18:30 +00:00
|
|
|
NS_DECL_CYCLE_COLLECTION_CLASS(AllResolveElementFunction)
|
2013-11-19 21:53:00 +00:00
|
|
|
|
2015-08-06 15:18:30 +00:00
|
|
|
AllResolveElementFunction(CountdownHolder* aHolder, uint32_t aIndex)
|
2013-11-19 21:53:00 +00:00
|
|
|
: mCountdownHolder(aHolder), mIndex(aIndex)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aHolder);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-03-21 16:28:04 +00:00
|
|
|
ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
|
2013-11-19 21:53:00 +00:00
|
|
|
{
|
|
|
|
mCountdownHolder->SetValue(mIndex, aValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-03-21 16:28:04 +00:00
|
|
|
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
|
2013-11-19 21:53:00 +00:00
|
|
|
{
|
|
|
|
// Should never be attached to Promise as a reject handler.
|
2015-08-06 15:18:30 +00:00
|
|
|
MOZ_CRASH("AllResolveElementFunction should never be attached to a Promise's reject handler!");
|
2013-11-19 21:53:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2015-08-06 15:18:30 +00:00
|
|
|
~AllResolveElementFunction()
|
2014-06-23 19:56:07 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<CountdownHolder> mCountdownHolder;
|
2013-11-19 21:53:00 +00:00
|
|
|
uint32_t mIndex;
|
|
|
|
};
|
|
|
|
|
2015-08-06 15:18:30 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(AllResolveElementFunction)
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(AllResolveElementFunction)
|
2013-11-19 21:53:00 +00:00
|
|
|
|
2015-08-06 15:18:30 +00:00
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AllResolveElementFunction)
|
2015-07-09 06:56:00 +00:00
|
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
|
|
NS_INTERFACE_MAP_END
|
2013-11-19 21:53:00 +00:00
|
|
|
|
2015-08-06 15:18:30 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION(AllResolveElementFunction, mCountdownHolder)
|
2013-11-19 21:53:00 +00:00
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
static const JSClass PromiseAllDataHolderClass = {
|
|
|
|
"PromiseAllDataHolder", JSCLASS_HAS_RESERVED_SLOTS(3)
|
|
|
|
};
|
|
|
|
|
|
|
|
// Slot indices for objects of class PromiseAllDataHolderClass.
|
|
|
|
#define DATA_HOLDER_REMAINING_ELEMENTS_SLOT 0
|
|
|
|
#define DATA_HOLDER_VALUES_ARRAY_SLOT 1
|
|
|
|
#define DATA_HOLDER_RESOLVE_FUNCTION_SLOT 2
|
|
|
|
|
|
|
|
// Slot indices for PromiseAllResolveElement.
|
|
|
|
// The RESOLVE_ELEMENT_INDEX_SLOT stores our index unless we've already been
|
|
|
|
// called. Then it stores INT32_MIN (which is never a valid index value).
|
|
|
|
#define RESOLVE_ELEMENT_INDEX_SLOT 0
|
|
|
|
// The RESOLVE_ELEMENT_DATA_HOLDER_SLOT slot stores an object of class
|
|
|
|
// PromiseAllDataHolderClass.
|
|
|
|
#define RESOLVE_ELEMENT_DATA_HOLDER_SLOT 1
|
|
|
|
|
|
|
|
static bool
|
|
|
|
PromiseAllResolveElement(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
|
|
|
|
{
|
|
|
|
// Implements
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-promise.all-resolve-element-functions
|
|
|
|
//
|
|
|
|
// See the big comment about compartments in Promise::All "Substep 4" that
|
|
|
|
// explains what compartments the various stuff here lives in.
|
|
|
|
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
|
|
|
|
|
|
|
|
// Step 1.
|
|
|
|
int32_t index =
|
|
|
|
js::GetFunctionNativeReserved(&args.callee(),
|
|
|
|
RESOLVE_ELEMENT_INDEX_SLOT).toInt32();
|
|
|
|
// Step 2.
|
|
|
|
if (index == INT32_MIN) {
|
|
|
|
args.rval().setUndefined();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 3.
|
|
|
|
js::SetFunctionNativeReserved(&args.callee(),
|
|
|
|
RESOLVE_ELEMENT_INDEX_SLOT,
|
|
|
|
JS::Int32Value(INT32_MIN));
|
|
|
|
|
|
|
|
// Step 4 already done.
|
|
|
|
|
|
|
|
// Step 5.
|
|
|
|
JS::Rooted<JSObject*> dataHolder(aCx,
|
|
|
|
&js::GetFunctionNativeReserved(&args.callee(),
|
|
|
|
RESOLVE_ELEMENT_DATA_HOLDER_SLOT).toObject());
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> values(aCx,
|
|
|
|
js::GetReservedSlot(dataHolder, DATA_HOLDER_VALUES_ARRAY_SLOT));
|
|
|
|
|
|
|
|
// Step 6, effectively.
|
|
|
|
JS::Rooted<JS::Value> resolveFunc(aCx,
|
|
|
|
js::GetReservedSlot(dataHolder, DATA_HOLDER_RESOLVE_FUNCTION_SLOT));
|
|
|
|
|
|
|
|
// Step 7.
|
|
|
|
int32_t remainingElements =
|
|
|
|
js::GetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT).toInt32();
|
|
|
|
|
|
|
|
// Step 8.
|
|
|
|
JS::Rooted<JSObject*> valuesObj(aCx, &values.toObject());
|
|
|
|
if (!JS_DefineElement(aCx, valuesObj, index, args.get(0), JSPROP_ENUMERATE)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 9.
|
|
|
|
remainingElements -= 1;
|
|
|
|
js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT,
|
|
|
|
JS::Int32Value(remainingElements));
|
|
|
|
|
|
|
|
// Step 10.
|
|
|
|
if (remainingElements == 0) {
|
|
|
|
return JS::Call(aCx, JS::UndefinedHandleValue, resolveFunc,
|
|
|
|
JS::HandleValueArray(values), args.rval());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 11.
|
|
|
|
args.rval().setUndefined();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* static */ void
|
2015-11-25 20:48:08 +00:00
|
|
|
Promise::All(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
|
2015-11-25 20:48:09 +00:00
|
|
|
JS::Handle<JS::Value> aIterable,
|
|
|
|
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv)
|
2015-04-29 15:59:43 +00:00
|
|
|
{
|
2015-11-25 20:48:09 +00:00
|
|
|
// Implements http://www.ecma-international.org/ecma-262/6.0/#sec-promise.all
|
|
|
|
nsCOMPtr<nsIGlobalObject> global =
|
|
|
|
do_QueryInterface(aGlobal.GetAsSupports());
|
|
|
|
if (!global) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-29 15:59:43 +00:00
|
|
|
JSContext* cx = aGlobal.Context();
|
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// Steps 1-5: nothing to do. Note that the @@species bits got removed in
|
|
|
|
// https://github.com/tc39/ecma262/pull/211
|
2015-04-29 15:59:43 +00:00
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// Step 6.
|
|
|
|
PromiseCapability capability(cx);
|
|
|
|
NewPromiseCapability(cx, global, aThisv, true, capability, aRv);
|
|
|
|
// Step 7.
|
|
|
|
if (aRv.Failed()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_ASSERT(aThisv.isObject(), "How did NewPromiseCapability succeed?");
|
|
|
|
JS::Rooted<JSObject*> constructorObj(cx, &aThisv.toObject());
|
|
|
|
|
|
|
|
// After this point we have a useful promise value in "capability", so just go
|
|
|
|
// ahead and put it in our retval now. Every single return path below would
|
|
|
|
// want to do that anyway.
|
|
|
|
aRetval.set(capability.PromiseValue());
|
|
|
|
if (!MaybeWrapValue(cx, aRetval)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(cx);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The arguments we're going to be passing to "then" on each loop iteration.
|
|
|
|
// The second one we know already; the first one will be created on each
|
|
|
|
// iteration of the loop.
|
|
|
|
JS::AutoValueArray<2> callbackFunctions(cx);
|
|
|
|
callbackFunctions[1].set(capability.mReject);
|
|
|
|
|
|
|
|
// Steps 8 and 9.
|
|
|
|
JS::ForOfIterator iter(cx);
|
|
|
|
if (!iter.init(aIterable, JS::ForOfIterator::AllowNonIterable)) {
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!iter.valueIsIterable()) {
|
|
|
|
ThrowErrorMessage(cx, MSG_PROMISE_ARG_NOT_ITERABLE,
|
|
|
|
"Argument of Promise.all");
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
|
|
|
}
|
2015-11-25 20:48:09 +00:00
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// Step 10 doesn't need to be done, because ForOfIterator handles it
|
|
|
|
// for us.
|
|
|
|
|
|
|
|
// Now we jump over to
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-performpromiseall
|
|
|
|
// and do its steps.
|
|
|
|
|
|
|
|
// Substep 4. Create our data holder that holds all the things shared across
|
|
|
|
// every step of the iterator. In particular, this holds the
|
|
|
|
// remainingElementsCount (as an integer reserved slot), the array of values,
|
|
|
|
// and the resolve function from our PromiseCapability.
|
|
|
|
//
|
|
|
|
// We have to be very careful about which compartments we create things in
|
|
|
|
// here. In particular, we have to maintain the invariant that anything
|
|
|
|
// stored in a reserved slot is same-compartment with the object whose
|
|
|
|
// reserved slot it's in. But we want to create the values array in the
|
|
|
|
// Promise reflector compartment, because that array can get exposed to code
|
|
|
|
// that has access to the Promise reflector (in particular code from that
|
|
|
|
// compartment), and that should work, even if the Promise reflector
|
|
|
|
// compartment is less-privileged than our caller compartment.
|
|
|
|
//
|
|
|
|
// So the plan is as follows: Create the values array in the promise reflector
|
|
|
|
// compartment. Create the PromiseAllResolveElement function and the data
|
|
|
|
// holder in our current compartment. Store a cross-compartment wrapper to
|
|
|
|
// the values array in the holder. This should be OK because the only things
|
|
|
|
// we hand the PromiseAllResolveElement function to are the "then" calls we do
|
|
|
|
// and in the case when the reflector compartment is not the current
|
|
|
|
// compartment those are happening over Xrays anyway, which means they get the
|
|
|
|
// canonical "then" function and content can't see our
|
|
|
|
// PromiseAllResolveElement.
|
|
|
|
JS::Rooted<JSObject*> dataHolder(cx);
|
|
|
|
dataHolder = JS_NewObjectWithGivenProto(cx, &PromiseAllDataHolderClass,
|
|
|
|
nullptr);
|
|
|
|
if (!dataHolder) {
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
|
|
|
}
|
2015-11-25 20:48:09 +00:00
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
JS::Rooted<JSObject*> reflectorGlobal(cx, global->GetGlobalJSObject());
|
|
|
|
JS::Rooted<JSObject*> valuesArray(cx);
|
|
|
|
{ // Scope for JSAutoCompartment.
|
|
|
|
JSAutoCompartment ac(cx, reflectorGlobal);
|
|
|
|
valuesArray = JS_NewArrayObject(cx, 0);
|
|
|
|
}
|
|
|
|
if (!valuesArray) {
|
|
|
|
// It's important that we've exited the JSAutoCompartment by now, before
|
|
|
|
// calling RejectWithException and possibly invoking capability.mReject.
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
2015-11-25 20:48:09 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// The values array as a value we can pass to a function in our current
|
|
|
|
// compartment, or store in the holder's reserved slot.
|
|
|
|
JS::Rooted<JS::Value> valuesArrayVal(cx, JS::ObjectValue(*valuesArray));
|
|
|
|
if (!MaybeWrapObjectValue(cx, &valuesArrayVal)) {
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT,
|
|
|
|
JS::Int32Value(1));
|
|
|
|
js::SetReservedSlot(dataHolder, DATA_HOLDER_VALUES_ARRAY_SLOT,
|
|
|
|
valuesArrayVal);
|
|
|
|
js::SetReservedSlot(dataHolder, DATA_HOLDER_RESOLVE_FUNCTION_SLOT,
|
|
|
|
capability.mResolve);
|
|
|
|
|
|
|
|
// Substep 5.
|
|
|
|
CheckedInt32 index = 0;
|
|
|
|
|
|
|
|
// Substep 6.
|
|
|
|
JS::Rooted<JS::Value> nextValue(cx);
|
|
|
|
while (true) {
|
|
|
|
bool done;
|
|
|
|
// Steps a, b, c, e, f, g.
|
|
|
|
if (!iter.next(&nextValue, &done)) {
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step d.
|
|
|
|
if (done) {
|
|
|
|
int32_t remainingCount =
|
|
|
|
js::GetReservedSlot(dataHolder,
|
|
|
|
DATA_HOLDER_REMAINING_ELEMENTS_SLOT).toInt32();
|
|
|
|
remainingCount -= 1;
|
|
|
|
if (remainingCount == 0) {
|
|
|
|
JS::Rooted<JS::Value> ignored(cx);
|
|
|
|
if (!JS::Call(cx, JS::UndefinedHandleValue, capability.mResolve,
|
|
|
|
JS::HandleValueArray(valuesArrayVal), &ignored)) {
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT,
|
|
|
|
JS::Int32Value(remainingCount));
|
|
|
|
// We're all set for now!
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step h.
|
|
|
|
{ // Scope for the JSAutoCompartment we need to work with valuesArray. We
|
|
|
|
// mostly do this for performance; we could go ahead and do the define via
|
|
|
|
// a cross-compartment proxy instead...
|
|
|
|
JSAutoCompartment ac(cx, valuesArray);
|
|
|
|
if (!JS_DefineElement(cx, valuesArray, index.value(),
|
|
|
|
JS::UndefinedHandleValue, JSPROP_ENUMERATE)) {
|
|
|
|
// Have to go back into the caller compartment before we try to touch
|
|
|
|
// capability.mReject. Luckily, capability.mReject is guaranteed to be
|
|
|
|
// an object in the right compartment here.
|
|
|
|
JSAutoCompartment ac2(cx, &capability.mReject.toObject());
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step i. Sadly, we can't take a shortcut here even if
|
|
|
|
// capability.mNativePromise exists, because someone could have overridden
|
|
|
|
// "resolve" on the canonical Promise constructor.
|
|
|
|
JS::Rooted<JS::Value> nextPromise(cx);
|
|
|
|
if (!JS_CallFunctionName(cx, constructorObj, "resolve",
|
|
|
|
JS::HandleValueArray(nextValue),
|
|
|
|
&nextPromise)) {
|
|
|
|
// Step j.
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step k.
|
|
|
|
JS::Rooted<JSObject*> resolveElement(cx);
|
|
|
|
JSFunction* resolveFunc =
|
|
|
|
js::NewFunctionWithReserved(cx, PromiseAllResolveElement,
|
|
|
|
1 /* nargs */, 0 /* flags */, nullptr);
|
|
|
|
if (!resolveFunc) {
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
resolveElement = JS_GetFunctionObject(resolveFunc);
|
|
|
|
// Steps l-p.
|
|
|
|
js::SetFunctionNativeReserved(resolveElement,
|
|
|
|
RESOLVE_ELEMENT_INDEX_SLOT,
|
|
|
|
JS::Int32Value(index.value()));
|
|
|
|
js::SetFunctionNativeReserved(resolveElement,
|
|
|
|
RESOLVE_ELEMENT_DATA_HOLDER_SLOT,
|
|
|
|
JS::ObjectValue(*dataHolder));
|
|
|
|
|
|
|
|
// Step q.
|
|
|
|
int32_t remainingElements =
|
|
|
|
js::GetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT).toInt32();
|
|
|
|
js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT,
|
|
|
|
JS::Int32Value(remainingElements + 1));
|
|
|
|
|
|
|
|
// Step r. And now we don't know whether nextPromise has an overridden
|
|
|
|
// "then" method, so no shortcuts here either.
|
|
|
|
callbackFunctions[0].setObject(*resolveElement);
|
|
|
|
JS::Rooted<JSObject*> nextPromiseObj(cx);
|
|
|
|
JS::Rooted<JS::Value> ignored(cx);
|
|
|
|
if (!JS_ValueToObject(cx, nextPromise, &nextPromiseObj) ||
|
|
|
|
!JS_CallFunctionName(cx, nextPromiseObj, "then", callbackFunctions,
|
|
|
|
&ignored)) {
|
|
|
|
// Step s.
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step t.
|
|
|
|
index += 1;
|
|
|
|
if (!index.isValid()) {
|
|
|
|
// Let's just claim OOM.
|
|
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
}
|
|
|
|
}
|
2015-04-29 15:59:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */ already_AddRefed<Promise>
|
|
|
|
Promise::All(const GlobalObject& aGlobal,
|
2015-10-18 05:24:48 +00:00
|
|
|
const nsTArray<RefPtr<Promise>>& aPromiseList, ErrorResult& aRv)
|
2013-11-19 21:53:00 +00:00
|
|
|
{
|
2014-02-25 21:34:55 +00:00
|
|
|
nsCOMPtr<nsIGlobalObject> global =
|
|
|
|
do_QueryInterface(aGlobal.GetAsSupports());
|
|
|
|
if (!global) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
|
|
return nullptr;
|
2013-11-19 21:53:00 +00:00
|
|
|
}
|
|
|
|
|
2014-06-16 16:52:00 +00:00
|
|
|
JSContext* cx = aGlobal.Context();
|
|
|
|
|
2015-04-29 15:59:43 +00:00
|
|
|
if (aPromiseList.IsEmpty()) {
|
2014-06-16 16:52:00 +00:00
|
|
|
JS::Rooted<JSObject*> empty(cx, JS_NewArrayObject(cx, 0));
|
2013-11-19 21:53:00 +00:00
|
|
|
if (!empty) {
|
|
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
return nullptr;
|
|
|
|
}
|
2014-06-16 16:52:00 +00:00
|
|
|
JS::Rooted<JS::Value> value(cx, JS::ObjectValue(*empty));
|
2014-10-20 02:27:12 +00:00
|
|
|
// We know "value" is not a promise, so call the Resolve function
|
|
|
|
// that doesn't have to check for that.
|
|
|
|
return Promise::Resolve(global, cx, value, aRv);
|
2013-11-19 21:53:00 +00:00
|
|
|
}
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<Promise> promise = Create(global, aRv);
|
2014-07-19 01:31:11 +00:00
|
|
|
if (aRv.Failed()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<CountdownHolder> holder =
|
2015-04-29 15:59:43 +00:00
|
|
|
new CountdownHolder(aGlobal, promise, aPromiseList.Length());
|
2013-11-19 21:53:00 +00:00
|
|
|
|
2014-06-16 16:52:00 +00:00
|
|
|
JS::Rooted<JSObject*> obj(cx, JS::CurrentGlobalOrNull(cx));
|
2014-04-09 08:30:24 +00:00
|
|
|
if (!obj) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseCallback> rejectCb = new RejectPromiseCallback(promise, obj);
|
2013-11-19 21:53:00 +00:00
|
|
|
|
2015-04-29 15:59:43 +00:00
|
|
|
for (uint32_t i = 0; i < aPromiseList.Length(); ++i) {
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseNativeHandler> resolveHandler =
|
2015-08-06 15:18:30 +00:00
|
|
|
new AllResolveElementFunction(holder, i);
|
2013-11-19 21:53:00 +00:00
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseCallback> resolveCb =
|
2013-11-19 21:53:00 +00:00
|
|
|
new NativePromiseCallback(resolveHandler, Resolved);
|
2015-04-29 15:59:43 +00:00
|
|
|
|
2013-11-19 21:53:00 +00:00
|
|
|
// Every promise gets its own resolve callback, which will set the right
|
|
|
|
// index in the array to the resolution value.
|
2015-04-29 15:59:43 +00:00
|
|
|
aPromiseList[i]->AppendCallbacks(resolveCb, rejectCb);
|
2013-11-19 21:53:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return promise.forget();
|
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
/* static */ void
|
2015-11-25 20:48:08 +00:00
|
|
|
Promise::Race(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
|
2015-11-25 20:48:09 +00:00
|
|
|
JS::Handle<JS::Value> aIterable, JS::MutableHandle<JS::Value> aRetval,
|
|
|
|
ErrorResult& aRv)
|
2013-11-19 21:53:00 +00:00
|
|
|
{
|
2015-11-25 20:48:09 +00:00
|
|
|
// Implements http://www.ecma-international.org/ecma-262/6.0/#sec-promise.race
|
2014-02-25 21:34:55 +00:00
|
|
|
nsCOMPtr<nsIGlobalObject> global =
|
|
|
|
do_QueryInterface(aGlobal.GetAsSupports());
|
|
|
|
if (!global) {
|
|
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
2013-11-19 21:53:00 +00:00
|
|
|
}
|
|
|
|
|
2014-06-16 16:52:00 +00:00
|
|
|
JSContext* cx = aGlobal.Context();
|
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// Steps 1-5: nothing to do. Note that the @@species bits got removed in
|
|
|
|
// https://github.com/tc39/ecma262/pull/211
|
|
|
|
PromiseCapability capability(cx);
|
2013-11-19 21:53:00 +00:00
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// Step 6.
|
|
|
|
NewPromiseCapability(cx, global, aThisv, true, capability, aRv);
|
|
|
|
// Step 7.
|
2015-11-26 05:02:55 +00:00
|
|
|
if (aRv.Failed()) {
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
2015-11-25 20:48:09 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
MOZ_ASSERT(aThisv.isObject(), "How did NewPromiseCapability succeed?");
|
|
|
|
JS::Rooted<JSObject*> constructorObj(cx, &aThisv.toObject());
|
2015-11-25 20:48:09 +00:00
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// After this point we have a useful promise value in "capability", so just go
|
|
|
|
// ahead and put it in our retval now. Every single return path below would
|
|
|
|
// want to do that anyway.
|
|
|
|
aRetval.set(capability.PromiseValue());
|
|
|
|
if (!MaybeWrapValue(cx, aRetval)) {
|
2016-03-10 09:50:56 +00:00
|
|
|
aRv.NoteJSContextException(cx);
|
2015-11-25 20:48:09 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-11-25 20:48:09 +00:00
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// The arguments we're going to be passing to "then" on each loop iteration.
|
|
|
|
JS::AutoValueArray<2> callbackFunctions(cx);
|
|
|
|
callbackFunctions[0].set(capability.mResolve);
|
|
|
|
callbackFunctions[1].set(capability.mReject);
|
|
|
|
|
|
|
|
// Steps 8 and 9.
|
|
|
|
JS::ForOfIterator iter(cx);
|
|
|
|
if (!iter.init(aIterable, JS::ForOfIterator::AllowNonIterable)) {
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
2015-11-25 20:48:09 +00:00
|
|
|
}
|
2013-11-19 21:53:00 +00:00
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
if (!iter.valueIsIterable()) {
|
|
|
|
ThrowErrorMessage(cx, MSG_PROMISE_ARG_NOT_ITERABLE,
|
|
|
|
"Argument of Promise.race");
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 10 doesn't need to be done, because ForOfIterator handles it
|
|
|
|
// for us.
|
|
|
|
|
|
|
|
// Now we jump over to
|
|
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-performpromiserace
|
|
|
|
// and do its steps.
|
|
|
|
JS::Rooted<JS::Value> nextValue(cx);
|
|
|
|
while (true) {
|
|
|
|
bool done;
|
|
|
|
// Steps a, b, c, e, f, g.
|
|
|
|
if (!iter.next(&nextValue, &done)) {
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step d.
|
|
|
|
if (done) {
|
|
|
|
// We're all set!
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step h. Sadly, we can't take a shortcut here even if
|
|
|
|
// capability.mNativePromise exists, because someone could have overridden
|
|
|
|
// "resolve" on the canonical Promise constructor.
|
|
|
|
JS::Rooted<JS::Value> nextPromise(cx);
|
|
|
|
if (!JS_CallFunctionName(cx, constructorObj, "resolve",
|
|
|
|
JS::HandleValueArray(nextValue), &nextPromise)) {
|
|
|
|
// Step i.
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step j. And now we don't know whether nextPromise has an overridden
|
|
|
|
// "then" method, so no shortcuts here either.
|
|
|
|
JS::Rooted<JSObject*> nextPromiseObj(cx);
|
|
|
|
JS::Rooted<JS::Value> ignored(cx);
|
|
|
|
if (!JS_ValueToObject(cx, nextPromise, &nextPromiseObj) ||
|
|
|
|
!JS_CallFunctionName(cx, nextPromiseObj, "then", callbackFunctions,
|
|
|
|
&ignored)) {
|
|
|
|
// Step k.
|
|
|
|
capability.RejectWithException(cx, aRv);
|
|
|
|
}
|
|
|
|
}
|
2015-11-25 20:48:08 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 20:48:08 +00:00
|
|
|
/* static */
|
|
|
|
bool
|
|
|
|
Promise::PromiseSpecies(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
|
|
|
|
{
|
|
|
|
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
|
|
|
|
args.rval().set(args.thisv());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-11-19 18:43:51 +00:00
|
|
|
void
|
|
|
|
Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable)
|
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseCallback> resolveCb =
|
2013-11-19 21:53:00 +00:00
|
|
|
new NativePromiseCallback(aRunnable, Resolved);
|
2013-11-19 18:43:51 +00:00
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseCallback> rejectCb =
|
2013-11-19 21:53:00 +00:00
|
|
|
new NativePromiseCallback(aRunnable, Rejected);
|
2013-11-19 18:43:51 +00:00
|
|
|
|
|
|
|
AppendCallbacks(resolveCb, rejectCb);
|
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
|
2015-01-15 22:39:02 +00:00
|
|
|
JSObject*
|
|
|
|
Promise::GlobalJSObject() const
|
|
|
|
{
|
|
|
|
return mGlobal->GetGlobalJSObject();
|
|
|
|
}
|
|
|
|
|
|
|
|
JSCompartment*
|
|
|
|
Promise::Compartment() const
|
|
|
|
{
|
|
|
|
return js::GetObjectCompartment(GlobalJSObject());
|
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2013-06-12 01:41:21 +00:00
|
|
|
void
|
2013-07-11 20:40:36 +00:00
|
|
|
Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
|
|
|
|
PromiseCallback* aRejectCallback)
|
2013-06-12 01:41:21 +00:00
|
|
|
{
|
2016-02-05 18:12:52 +00:00
|
|
|
if (!mGlobal || mGlobal->IsDying()) {
|
2015-04-27 19:00:41 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-10 15:27:57 +00:00
|
|
|
MOZ_ASSERT(aResolveCallback);
|
|
|
|
MOZ_ASSERT(aRejectCallback);
|
|
|
|
|
|
|
|
if (mIsLastInChain && mState == PromiseState::Rejected) {
|
|
|
|
// This rejection is now consumed.
|
|
|
|
PromiseDebugging::AddConsumedRejection(*this);
|
|
|
|
// Note that we may not have had the opportunity to call
|
|
|
|
// RunResolveTask() yet, so we may never have called
|
|
|
|
// `PromiseDebugging:AddUncaughtRejection`.
|
2013-06-12 01:41:22 +00:00
|
|
|
}
|
2015-04-10 15:27:57 +00:00
|
|
|
mIsLastInChain = false;
|
2013-06-12 01:41:22 +00:00
|
|
|
|
2015-04-10 15:27:57 +00:00
|
|
|
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
|
|
|
// Now that there is a callback, we don't need to report anymore.
|
|
|
|
mHadRejectCallback = true;
|
2016-06-23 08:53:14 +00:00
|
|
|
RemoveWorkerHolder();
|
2015-04-10 15:27:57 +00:00
|
|
|
#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2014-03-12 14:31:03 +00:00
|
|
|
|
2015-04-10 15:27:57 +00:00
|
|
|
mResolveCallbacks.AppendElement(aResolveCallback);
|
|
|
|
mRejectCallbacks.AppendElement(aRejectCallback);
|
2013-06-12 01:41:21 +00:00
|
|
|
|
2014-10-28 12:08:19 +00:00
|
|
|
// If promise's state is fulfilled, queue a task to process our fulfill
|
2013-09-11 16:03:04 +00:00
|
|
|
// callbacks with promise's result. If promise's state is rejected, queue a
|
|
|
|
// task to process our reject callbacks with promise's result.
|
2014-10-28 12:08:19 +00:00
|
|
|
if (mState != Pending) {
|
2015-08-06 15:18:30 +00:00
|
|
|
TriggerPromiseReactions();
|
2013-06-12 01:41:21 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
2013-06-12 01:41:21 +00:00
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2015-04-10 15:27:57 +00:00
|
|
|
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2013-08-29 04:30:06 +00:00
|
|
|
void
|
|
|
|
Promise::MaybeReportRejected()
|
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2013-08-29 04:30:06 +00:00
|
|
|
if (mState != Rejected || mHadRejectCallback || mResult.isUndefined()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-07-04 05:24:59 +00:00
|
|
|
AutoJSAPI jsapi;
|
|
|
|
// We may not have a usable global by now (if it got unlinked
|
|
|
|
// already), so don't init with it.
|
|
|
|
jsapi.Init();
|
|
|
|
JSContext* cx = jsapi.cx();
|
|
|
|
JS::Rooted<JSObject*> obj(cx, GetWrapper());
|
|
|
|
MOZ_ASSERT(obj); // We preserve our wrapper, so should always have one here.
|
|
|
|
JS::Rooted<JS::Value> val(cx, mResult);
|
|
|
|
JS::ExposeValueToActiveJS(val);
|
|
|
|
|
|
|
|
JSAutoCompartment ac(cx, obj);
|
|
|
|
if (!JS_WrapValue(cx, &val)) {
|
|
|
|
JS_ClearPendingException(cx);
|
2014-01-30 17:30:29 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-07-04 05:24:59 +00:00
|
|
|
|
|
|
|
js::ErrorReport report(cx);
|
2016-04-19 23:29:21 +00:00
|
|
|
RefPtr<Exception> exp;
|
|
|
|
bool isObject = val.isObject();
|
|
|
|
if (!isObject || NS_FAILED(UNWRAP_OBJECT(Exception, &val.toObject(), exp))) {
|
|
|
|
if (!isObject ||
|
|
|
|
NS_FAILED(UNWRAP_OBJECT(DOMException, &val.toObject(), exp))) {
|
|
|
|
if (!report.init(cx, val, js::ErrorReport::NoSideEffects)) {
|
|
|
|
NS_WARNING("Couldn't convert the unhandled rejected value to an exception.");
|
|
|
|
JS_ClearPendingException(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2013-08-29 04:30:06 +00:00
|
|
|
}
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
|
2014-09-29 13:34:20 +00:00
|
|
|
bool isMainThread = MOZ_LIKELY(NS_IsMainThread());
|
|
|
|
bool isChrome = isMainThread ? nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(obj))
|
|
|
|
: GetCurrentThreadWorkerPrivate()->IsChromeWorker();
|
2016-01-30 17:05:36 +00:00
|
|
|
nsGlobalWindow* win = isMainThread ? xpc::WindowGlobalOrNull(obj) : nullptr;
|
2016-04-19 23:29:21 +00:00
|
|
|
uint64_t windowID = win ? win->AsInner()->WindowID() : 0;
|
|
|
|
if (exp) {
|
|
|
|
xpcReport->Init(cx, exp, isChrome, windowID);
|
|
|
|
} else {
|
|
|
|
xpcReport->Init(report.report(), report.message(), isChrome, windowID);
|
|
|
|
}
|
2013-08-29 04:30:06 +00:00
|
|
|
|
|
|
|
// Now post an event to do the real reporting async
|
2015-10-18 05:24:48 +00:00
|
|
|
// Since Promises preserve their wrapper, it is essential to RefPtr<> the
|
2014-01-10 22:07:46 +00:00
|
|
|
// AsyncErrorReporter, otherwise if the call to DispatchToMainThread fails, it
|
2015-07-10 03:21:46 +00:00
|
|
|
// will leak. See Bug 958684. So... don't use DispatchToMainThread()
|
|
|
|
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
|
|
|
|
if (NS_WARN_IF(!mainThread)) {
|
|
|
|
// Would prefer NS_ASSERTION, but that causes failure in xpcshell tests
|
|
|
|
NS_WARNING("!!! Trying to report rejected Promise after MainThread shutdown");
|
|
|
|
}
|
|
|
|
if (mainThread) {
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<AsyncErrorReporter> r = new AsyncErrorReporter(xpcReport);
|
2015-07-10 03:21:46 +00:00
|
|
|
mainThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
2015-07-10 03:21:46 +00:00
|
|
|
}
|
2013-08-29 04:30:06 +00:00
|
|
|
}
|
2015-04-10 15:27:57 +00:00
|
|
|
#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2013-08-29 04:30:06 +00:00
|
|
|
|
2013-09-11 16:03:04 +00:00
|
|
|
void
|
2013-09-26 18:09:16 +00:00
|
|
|
Promise::MaybeResolveInternal(JSContext* aCx,
|
2014-10-28 12:08:19 +00:00
|
|
|
JS::Handle<JS::Value> aValue)
|
2013-09-11 16:03:04 +00:00
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2013-09-11 16:03:04 +00:00
|
|
|
if (mResolvePending) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-10-28 12:08:19 +00:00
|
|
|
ResolveInternal(aCx, aValue);
|
2013-09-11 16:03:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2013-09-26 18:09:16 +00:00
|
|
|
Promise::MaybeRejectInternal(JSContext* aCx,
|
2014-10-28 12:08:19 +00:00
|
|
|
JS::Handle<JS::Value> aValue)
|
2013-09-11 16:03:04 +00:00
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2013-09-11 16:03:04 +00:00
|
|
|
if (mResolvePending) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-10-28 12:08:19 +00:00
|
|
|
RejectInternal(aCx, aValue);
|
2013-09-11 16:03:04 +00:00
|
|
|
}
|
|
|
|
|
2014-01-23 18:47:29 +00:00
|
|
|
void
|
|
|
|
Promise::HandleException(JSContext* aCx)
|
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2014-01-23 18:47:29 +00:00
|
|
|
JS::Rooted<JS::Value> exn(aCx);
|
|
|
|
if (JS_GetPendingException(aCx, &exn)) {
|
|
|
|
JS_ClearPendingException(aCx);
|
2014-10-28 12:08:19 +00:00
|
|
|
RejectInternal(aCx, exn);
|
2014-01-23 18:47:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-11 16:03:04 +00:00
|
|
|
void
|
|
|
|
Promise::ResolveInternal(JSContext* aCx,
|
2014-10-28 12:08:19 +00:00
|
|
|
JS::Handle<JS::Value> aValue)
|
2013-09-11 16:03:04 +00:00
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2016-03-24 15:12:00 +00:00
|
|
|
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
|
|
|
|
|
2013-09-11 16:03:04 +00:00
|
|
|
mResolvePending = true;
|
|
|
|
|
2013-11-19 18:39:51 +00:00
|
|
|
if (aValue.isObject()) {
|
|
|
|
JS::Rooted<JSObject*> valueObj(aCx, &aValue.toObject());
|
2013-09-11 16:03:04 +00:00
|
|
|
|
2014-01-23 18:47:29 +00:00
|
|
|
// Thenables.
|
|
|
|
JS::Rooted<JS::Value> then(aCx);
|
|
|
|
if (!JS_GetProperty(aCx, valueObj, "then", &then)) {
|
|
|
|
HandleException(aCx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-09-25 11:13:28 +00:00
|
|
|
if (then.isObject() && JS::IsCallable(&then.toObject())) {
|
2014-05-20 21:21:13 +00:00
|
|
|
// This is the then() function of the thenable aValueObj.
|
2014-01-23 18:47:29 +00:00
|
|
|
JS::Rooted<JSObject*> thenObj(aCx, &then.toObject());
|
2015-04-18 02:01:02 +00:00
|
|
|
|
2015-11-25 20:48:09 +00:00
|
|
|
// We used to have a fast path here for the case when the following
|
|
|
|
// requirements held:
|
2015-04-18 02:01:02 +00:00
|
|
|
//
|
|
|
|
// 1) valueObj is a Promise.
|
|
|
|
// 2) thenObj is a JSFunction backed by our actual Promise::Then
|
|
|
|
// implementation.
|
|
|
|
//
|
2015-11-25 20:48:09 +00:00
|
|
|
// But now that we're doing subclassing in Promise.prototype.then we would
|
|
|
|
// also need the following requirements:
|
|
|
|
//
|
|
|
|
// 3) Getting valueObj.constructor has no side-effects.
|
|
|
|
// 4) Getting valueObj.constructor[@@species] has no side-effects.
|
|
|
|
// 5) valueObj.constructor[@@species] is a function and calling it has no
|
|
|
|
// side-effects (e.g. it's the canonical Promise constructor) and it
|
|
|
|
// provides some callback functions to call as arguments to its
|
|
|
|
// argument.
|
|
|
|
//
|
|
|
|
// Ensuring that stuff while not inside SpiderMonkey is painful, so let's
|
|
|
|
// drop the fast path for now.
|
2015-04-18 02:01:02 +00:00
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseInit> thenCallback =
|
2015-07-24 11:00:00 +00:00
|
|
|
new PromiseInit(nullptr, thenObj, mozilla::dom::GetIncumbentGlobal());
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseResolveThenableJob> task =
|
2015-08-03 16:48:34 +00:00
|
|
|
new PromiseResolveThenableJob(this, valueObj, thenCallback);
|
2016-07-05 22:49:06 +00:00
|
|
|
runtime->DispatchToMicroTask(task.forget());
|
2013-09-11 16:03:04 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-28 12:08:19 +00:00
|
|
|
MaybeSettle(aValue, Resolved);
|
2013-09-11 16:03:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Promise::RejectInternal(JSContext* aCx,
|
2014-10-28 12:08:19 +00:00
|
|
|
JS::Handle<JS::Value> aValue)
|
2013-09-11 16:03:04 +00:00
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2013-09-11 16:03:04 +00:00
|
|
|
mResolvePending = true;
|
|
|
|
|
2014-10-28 12:08:19 +00:00
|
|
|
MaybeSettle(aValue, Rejected);
|
2013-09-11 16:03:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2014-11-17 09:44:00 +00:00
|
|
|
Promise::Settle(JS::Handle<JS::Value> aValue, PromiseState aState)
|
2013-09-11 16:03:04 +00:00
|
|
|
{
|
2015-09-12 01:59:43 +00:00
|
|
|
MOZ_ASSERT(mGlobal,
|
|
|
|
"We really should have a global here. Except we sometimes don't "
|
|
|
|
"in the wild for some odd reason");
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2015-09-12 01:59:43 +00:00
|
|
|
if (!mGlobal || mGlobal->IsDying()) {
|
2015-04-27 19:00:41 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-11-17 09:44:00 +00:00
|
|
|
mSettlementTimestamp = TimeStamp::Now();
|
2015-04-27 19:00:41 +00:00
|
|
|
|
2014-11-17 09:44:00 +00:00
|
|
|
AutoJSAPI jsapi;
|
|
|
|
jsapi.Init();
|
|
|
|
JSContext* cx = jsapi.cx();
|
|
|
|
JS::RootedObject wrapper(cx, GetWrapper());
|
|
|
|
MOZ_ASSERT(wrapper); // We preserved it
|
|
|
|
JSAutoCompartment ac(cx, wrapper);
|
2016-04-07 20:33:50 +00:00
|
|
|
|
|
|
|
JS::Rooted<JS::Value> value(cx, aValue);
|
|
|
|
|
|
|
|
if (!JS_WrapValue(cx, &value)) {
|
|
|
|
JS_ClearPendingException(cx);
|
|
|
|
value = JS::UndefinedValue();
|
|
|
|
}
|
|
|
|
SetResult(value);
|
|
|
|
SetState(aState);
|
|
|
|
|
2014-11-17 09:44:00 +00:00
|
|
|
JS::dbg::onPromiseSettled(cx, wrapper);
|
2014-03-12 14:31:03 +00:00
|
|
|
|
2015-04-10 15:27:57 +00:00
|
|
|
if (aState == PromiseState::Rejected &&
|
|
|
|
mIsLastInChain) {
|
|
|
|
// The Promise has just been rejected, and it is last in chain.
|
|
|
|
// We need to inform PromiseDebugging.
|
|
|
|
// If the Promise is eventually not the last in chain anymore,
|
|
|
|
// we will need to inform PromiseDebugging again.
|
|
|
|
PromiseDebugging::AddUncaughtRejection(*this);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2014-03-12 14:31:03 +00:00
|
|
|
// If the Promise was rejected, and there is no reject handler already setup,
|
|
|
|
// watch for thread shutdown.
|
|
|
|
if (aState == PromiseState::Rejected &&
|
|
|
|
!mHadRejectCallback &&
|
|
|
|
!NS_IsMainThread()) {
|
2016-07-17 14:48:58 +00:00
|
|
|
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
|
2014-03-12 14:31:03 +00:00
|
|
|
MOZ_ASSERT(worker);
|
|
|
|
worker->AssertIsOnWorkerThread();
|
|
|
|
|
2016-06-23 08:53:14 +00:00
|
|
|
mWorkerHolder = new PromiseReportRejectWorkerHolder(this);
|
|
|
|
if (NS_WARN_IF(!mWorkerHolder->HoldWorker(worker))) {
|
|
|
|
mWorkerHolder = nullptr;
|
2014-03-12 14:31:03 +00:00
|
|
|
// Worker is shutting down, report rejection immediately since it is
|
|
|
|
// unlikely that reject callbacks will be added after this point.
|
2014-06-05 19:21:56 +00:00
|
|
|
MaybeReportRejectedOnce();
|
2014-03-12 14:31:03 +00:00
|
|
|
}
|
|
|
|
}
|
2015-04-10 15:27:57 +00:00
|
|
|
#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2014-03-12 14:31:03 +00:00
|
|
|
|
2015-08-06 15:18:30 +00:00
|
|
|
TriggerPromiseReactions();
|
2014-10-28 12:08:19 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 09:44:00 +00:00
|
|
|
void
|
|
|
|
Promise::MaybeSettle(JS::Handle<JS::Value> aValue,
|
|
|
|
PromiseState aState)
|
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2014-11-17 09:44:00 +00:00
|
|
|
// Promise.all() or Promise.race() implementations will repeatedly call
|
|
|
|
// Resolve/RejectInternal rather than using the Maybe... forms. Stop SetState
|
|
|
|
// from asserting.
|
|
|
|
if (mState != Pending) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Settle(aValue, aState);
|
|
|
|
}
|
|
|
|
|
2014-10-28 12:08:19 +00:00
|
|
|
void
|
2015-08-06 15:18:30 +00:00
|
|
|
Promise::TriggerPromiseReactions()
|
2014-10-28 12:08:19 +00:00
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2016-03-24 15:12:00 +00:00
|
|
|
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
nsTArray<RefPtr<PromiseCallback>> callbacks;
|
2014-10-28 12:08:19 +00:00
|
|
|
callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
|
|
|
|
: mRejectCallbacks);
|
|
|
|
mResolveCallbacks.Clear();
|
|
|
|
mRejectCallbacks.Clear();
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < callbacks.Length(); ++i) {
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseReactionJob> task =
|
2015-08-06 15:18:30 +00:00
|
|
|
new PromiseReactionJob(this, callbacks[i], mResult);
|
2016-07-05 22:49:06 +00:00
|
|
|
runtime->DispatchToMicroTask(task.forget());
|
2014-10-28 12:08:19 +00:00
|
|
|
}
|
2013-09-11 16:03:04 +00:00
|
|
|
}
|
|
|
|
|
2015-04-10 15:27:57 +00:00
|
|
|
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2014-03-12 14:31:03 +00:00
|
|
|
void
|
2016-06-23 08:53:14 +00:00
|
|
|
Promise::RemoveWorkerHolder()
|
2014-03-12 14:31:03 +00:00
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2016-06-23 08:53:14 +00:00
|
|
|
// The DTOR of this WorkerHolder will release the worker for us.
|
|
|
|
mWorkerHolder = nullptr;
|
2014-03-12 14:31:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2016-07-17 14:48:58 +00:00
|
|
|
PromiseReportRejectWorkerHolder::Notify(Status aStatus)
|
2014-03-12 14:31:03 +00:00
|
|
|
{
|
2016-07-17 14:48:58 +00:00
|
|
|
MOZ_ASSERT(aStatus > Running);
|
2014-03-12 14:31:03 +00:00
|
|
|
mPromise->MaybeReportRejectedOnce();
|
2016-06-23 08:53:14 +00:00
|
|
|
// After this point, `this` has been deleted by RemoveWorkerHolder!
|
2014-03-12 14:31:03 +00:00
|
|
|
return true;
|
|
|
|
}
|
2015-04-10 15:27:57 +00:00
|
|
|
#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
2014-03-12 14:31:03 +00:00
|
|
|
|
2014-10-20 02:27:12 +00:00
|
|
|
bool
|
|
|
|
Promise::CaptureStack(JSContext* aCx, JS::Heap<JSObject*>& aTarget)
|
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2014-10-20 02:27:12 +00:00
|
|
|
JS::Rooted<JSObject*> stack(aCx);
|
|
|
|
if (!JS::CaptureCurrentStack(aCx, &stack)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
aTarget = stack;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-10-20 02:27:36 +00:00
|
|
|
void
|
2015-10-18 05:24:48 +00:00
|
|
|
Promise::GetDependentPromises(nsTArray<RefPtr<Promise>>& aPromises)
|
2014-10-20 02:27:36 +00:00
|
|
|
{
|
2016-04-11 15:16:26 +00:00
|
|
|
NS_ASSERT_OWNINGTHREAD(Promise);
|
|
|
|
|
2014-10-20 02:27:36 +00:00
|
|
|
// We want to return promises that correspond to then() calls, Promise.all()
|
|
|
|
// calls, and Promise.race() calls.
|
|
|
|
//
|
|
|
|
// For the then() case, we have both resolve and reject callbacks that know
|
|
|
|
// what the next promise is.
|
|
|
|
//
|
|
|
|
// For the race() case, likewise.
|
|
|
|
//
|
|
|
|
// For the all() case, our reject callback knows what the next promise is, but
|
|
|
|
// our resolve callback just knows it needs to notify some
|
|
|
|
// PromiseNativeHandler, which itself only has an indirect relationship to the
|
|
|
|
// next promise.
|
|
|
|
//
|
|
|
|
// So we walk over our _reject_ callbacks and ask each of them what promise
|
|
|
|
// its dependent promise is.
|
|
|
|
for (size_t i = 0; i < mRejectCallbacks.Length(); ++i) {
|
|
|
|
Promise* p = mRejectCallbacks[i]->GetDependentPromise();
|
|
|
|
if (p) {
|
|
|
|
aPromises.AppendElement(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
|
2014-02-24 13:56:54 +00:00
|
|
|
// A WorkerRunnable to resolve/reject the Promise on the worker thread.
|
2015-06-10 22:35:18 +00:00
|
|
|
// Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
|
2016-07-17 14:48:58 +00:00
|
|
|
class PromiseWorkerProxyRunnable : public WorkerRunnable
|
2014-02-24 13:56:54 +00:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
PromiseWorkerProxyRunnable(PromiseWorkerProxy* aPromiseWorkerProxy,
|
|
|
|
PromiseWorkerProxy::RunCallbackFunc aFunc)
|
|
|
|
: WorkerRunnable(aPromiseWorkerProxy->GetWorkerPrivate(),
|
|
|
|
WorkerThreadUnchangedBusyCount)
|
|
|
|
, mPromiseWorkerProxy(aPromiseWorkerProxy)
|
|
|
|
, mFunc(aFunc)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MOZ_ASSERT(mPromiseWorkerProxy);
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool
|
2016-07-17 14:48:58 +00:00
|
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
2014-02-24 13:56:54 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(aWorkerPrivate);
|
|
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
|
|
|
|
|
|
|
|
MOZ_ASSERT(mPromiseWorkerProxy);
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<Promise> workerPromise = mPromiseWorkerProxy->WorkerPromise();
|
2014-02-24 13:56:54 +00:00
|
|
|
|
|
|
|
// Here we convert the buffer to a JS::Value.
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
2015-09-05 09:22:13 +00:00
|
|
|
if (!mPromiseWorkerProxy->Read(aCx, &value)) {
|
2014-02-24 13:56:54 +00:00
|
|
|
JS_ClearPendingException(aCx);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-07-28 21:08:00 +00:00
|
|
|
(workerPromise->*mFunc)(aCx, value);
|
2014-02-24 13:56:54 +00:00
|
|
|
|
|
|
|
// Release the Promise because it has been resolved/rejected for sure.
|
2016-02-29 19:52:42 +00:00
|
|
|
mPromiseWorkerProxy->CleanUp();
|
2014-02-24 13:56:54 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
~PromiseWorkerProxyRunnable() {}
|
|
|
|
|
|
|
|
private:
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
|
2014-02-24 13:56:54 +00:00
|
|
|
|
|
|
|
// Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
|
|
|
|
PromiseWorkerProxy::RunCallbackFunc mFunc;
|
|
|
|
};
|
|
|
|
|
2016-07-18 07:14:14 +00:00
|
|
|
class PromiseWorkerHolder final : public WorkerHolder
|
|
|
|
{
|
|
|
|
// RawPointer because this proxy keeps alive the holder.
|
|
|
|
PromiseWorkerProxy* mProxy;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit PromiseWorkerHolder(PromiseWorkerProxy* aProxy)
|
|
|
|
: mProxy(aProxy)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aProxy);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
Notify(Status aStatus) override
|
|
|
|
{
|
|
|
|
if (aStatus >= Canceling) {
|
|
|
|
mProxy->CleanUp();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-12-17 14:49:36 +00:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<PromiseWorkerProxy>
|
2016-07-17 14:48:58 +00:00
|
|
|
PromiseWorkerProxy::Create(WorkerPrivate* aWorkerPrivate,
|
2014-12-17 14:49:36 +00:00
|
|
|
Promise* aWorkerPromise,
|
2015-09-05 09:22:13 +00:00
|
|
|
const PromiseWorkerProxyStructuredCloneCallbacks* aCb)
|
2014-12-17 14:49:36 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(aWorkerPrivate);
|
|
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
MOZ_ASSERT(aWorkerPromise);
|
2015-09-05 09:22:13 +00:00
|
|
|
MOZ_ASSERT_IF(aCb, !!aCb->Write && !!aCb->Read);
|
2014-12-17 14:49:36 +00:00
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseWorkerProxy> proxy =
|
2014-12-17 14:49:36 +00:00
|
|
|
new PromiseWorkerProxy(aWorkerPrivate, aWorkerPromise, aCb);
|
|
|
|
|
|
|
|
// We do this to make sure the worker thread won't shut down before the
|
|
|
|
// promise is resolved/rejected on the worker thread.
|
2015-09-02 17:07:26 +00:00
|
|
|
if (!proxy->AddRefObject()) {
|
2014-12-17 14:49:36 +00:00
|
|
|
// Probably the worker is terminating. We cannot complete the operation
|
|
|
|
// and we have to release all the resources.
|
2015-09-02 17:07:26 +00:00
|
|
|
proxy->CleanProperties();
|
2014-12-17 14:49:36 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return proxy.forget();
|
|
|
|
}
|
|
|
|
|
2015-07-09 06:56:00 +00:00
|
|
|
NS_IMPL_ISUPPORTS0(PromiseWorkerProxy)
|
|
|
|
|
2016-07-17 14:48:58 +00:00
|
|
|
PromiseWorkerProxy::PromiseWorkerProxy(WorkerPrivate* aWorkerPrivate,
|
2014-02-24 13:56:54 +00:00
|
|
|
Promise* aWorkerPromise,
|
2015-09-05 09:22:13 +00:00
|
|
|
const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks)
|
2014-02-24 13:56:54 +00:00
|
|
|
: mWorkerPrivate(aWorkerPrivate)
|
|
|
|
, mWorkerPromise(aWorkerPromise)
|
|
|
|
, mCleanedUp(false)
|
|
|
|
, mCallbacks(aCallbacks)
|
|
|
|
, mCleanUpLock("cleanUpLock")
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PromiseWorkerProxy::~PromiseWorkerProxy()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(mCleanedUp);
|
2016-07-18 07:14:14 +00:00
|
|
|
MOZ_ASSERT(!mWorkerHolder);
|
2014-02-24 13:56:54 +00:00
|
|
|
MOZ_ASSERT(!mWorkerPromise);
|
2015-09-02 17:07:26 +00:00
|
|
|
MOZ_ASSERT(!mWorkerPrivate);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
PromiseWorkerProxy::CleanProperties()
|
|
|
|
{
|
|
|
|
#ifdef DEBUG
|
2016-07-17 14:48:58 +00:00
|
|
|
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
|
2015-09-02 17:07:26 +00:00
|
|
|
MOZ_ASSERT(worker);
|
|
|
|
worker->AssertIsOnWorkerThread();
|
|
|
|
#endif
|
|
|
|
// Ok to do this unprotected from Create().
|
|
|
|
// CleanUp() holds the lock before calling this.
|
|
|
|
mCleanedUp = true;
|
|
|
|
mWorkerPromise = nullptr;
|
|
|
|
mWorkerPrivate = nullptr;
|
2015-09-05 09:22:13 +00:00
|
|
|
|
2015-09-30 12:22:08 +00:00
|
|
|
// Clear the StructuredCloneHolderBase class.
|
|
|
|
Clear();
|
2015-09-02 17:07:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
PromiseWorkerProxy::AddRefObject()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(mWorkerPrivate);
|
|
|
|
mWorkerPrivate->AssertIsOnWorkerThread();
|
2016-07-18 07:14:14 +00:00
|
|
|
|
|
|
|
MOZ_ASSERT(!mWorkerHolder);
|
|
|
|
mWorkerHolder.reset(new PromiseWorkerHolder(this));
|
|
|
|
if (NS_WARN_IF(!mWorkerHolder->HoldWorker(mWorkerPrivate))) {
|
|
|
|
mWorkerHolder = nullptr;
|
2015-09-02 17:07:26 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Maintain a reference so that we have a valid object to clean up when
|
|
|
|
// removing the feature.
|
|
|
|
AddRef();
|
|
|
|
return true;
|
2014-02-24 13:56:54 +00:00
|
|
|
}
|
|
|
|
|
2016-07-17 14:48:58 +00:00
|
|
|
WorkerPrivate*
|
2014-02-24 13:56:54 +00:00
|
|
|
PromiseWorkerProxy::GetWorkerPrivate() const
|
|
|
|
{
|
2015-06-10 22:35:18 +00:00
|
|
|
#ifdef DEBUG
|
|
|
|
if (NS_IsMainThread()) {
|
|
|
|
mCleanUpLock.AssertCurrentThreadOwns();
|
|
|
|
}
|
|
|
|
#endif
|
2015-09-02 17:07:26 +00:00
|
|
|
// Safe to check this without a lock since we assert lock ownership on the
|
|
|
|
// main thread above.
|
|
|
|
MOZ_ASSERT(!mCleanedUp);
|
2016-07-18 07:14:14 +00:00
|
|
|
MOZ_ASSERT(mWorkerHolder);
|
2015-06-10 22:35:18 +00:00
|
|
|
|
2014-02-24 13:56:54 +00:00
|
|
|
return mWorkerPrivate;
|
|
|
|
}
|
|
|
|
|
|
|
|
Promise*
|
2015-09-02 17:07:26 +00:00
|
|
|
PromiseWorkerProxy::WorkerPromise() const
|
2014-02-24 13:56:54 +00:00
|
|
|
{
|
2015-06-10 22:35:18 +00:00
|
|
|
#ifdef DEBUG
|
2016-07-17 14:48:58 +00:00
|
|
|
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
|
2015-06-10 22:35:18 +00:00
|
|
|
MOZ_ASSERT(worker);
|
|
|
|
worker->AssertIsOnWorkerThread();
|
|
|
|
#endif
|
2015-09-02 17:07:26 +00:00
|
|
|
MOZ_ASSERT(mWorkerPromise);
|
2014-02-24 13:56:54 +00:00
|
|
|
return mWorkerPromise;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
PromiseWorkerProxy::StoreISupports(nsISupports* aSupports)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
2014-07-30 00:43:56 +00:00
|
|
|
nsMainThreadPtrHandle<nsISupports> supports(
|
|
|
|
new nsMainThreadPtrHolder<nsISupports>(aSupports));
|
2014-02-24 13:56:54 +00:00
|
|
|
mSupportsArray.AppendElement(supports);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
PromiseWorkerProxy::RunCallback(JSContext* aCx,
|
|
|
|
JS::Handle<JS::Value> aValue,
|
|
|
|
RunCallbackFunc aFunc)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
2015-09-02 17:07:26 +00:00
|
|
|
MutexAutoLock lock(Lock());
|
2014-02-24 13:56:54 +00:00
|
|
|
// If the worker thread's been cancelled we don't need to resolve the Promise.
|
2015-09-02 17:07:26 +00:00
|
|
|
if (CleanedUp()) {
|
2014-02-24 13:56:54 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-09-30 12:22:08 +00:00
|
|
|
// The |aValue| is written into the StructuredCloneHolderBase.
|
2015-09-05 09:22:13 +00:00
|
|
|
if (!Write(aCx, aValue)) {
|
2014-02-24 13:56:54 +00:00
|
|
|
JS_ClearPendingException(aCx);
|
2015-09-05 09:22:13 +00:00
|
|
|
MOZ_ASSERT(false, "cannot serialize the value with the StructuredCloneAlgorithm!");
|
2014-02-24 13:56:54 +00:00
|
|
|
}
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<PromiseWorkerProxyRunnable> runnable =
|
2015-09-05 09:22:13 +00:00
|
|
|
new PromiseWorkerProxyRunnable(this, aFunc);
|
2014-02-24 13:56:54 +00:00
|
|
|
|
2016-02-26 20:23:12 +00:00
|
|
|
runnable->Dispatch();
|
2014-02-24 13:56:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
PromiseWorkerProxy::ResolvedCallback(JSContext* aCx,
|
|
|
|
JS::Handle<JS::Value> aValue)
|
|
|
|
{
|
2016-02-09 22:40:31 +00:00
|
|
|
RunCallback(aCx, aValue, &Promise::MaybeResolve);
|
2014-02-24 13:56:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
PromiseWorkerProxy::RejectedCallback(JSContext* aCx,
|
|
|
|
JS::Handle<JS::Value> aValue)
|
|
|
|
{
|
2016-02-09 22:40:31 +00:00
|
|
|
RunCallback(aCx, aValue, &Promise::MaybeReject);
|
2014-02-24 13:56:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-02-29 19:52:42 +00:00
|
|
|
PromiseWorkerProxy::CleanUp()
|
2014-02-24 13:56:54 +00:00
|
|
|
{
|
2015-09-02 17:07:26 +00:00
|
|
|
// Can't release Mutex while it is still locked, so scope the lock.
|
|
|
|
{
|
|
|
|
MutexAutoLock lock(Lock());
|
2014-02-24 13:56:54 +00:00
|
|
|
|
2015-09-02 17:07:26 +00:00
|
|
|
// |mWorkerPrivate| is not safe to use anymore if we have already
|
2016-06-23 08:53:14 +00:00
|
|
|
// cleaned up and RemoveWorkerHolder(), so we need to check |mCleanedUp|
|
|
|
|
// first.
|
2015-09-02 17:07:26 +00:00
|
|
|
if (CleanedUp()) {
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 13:56:54 +00:00
|
|
|
|
2015-09-02 17:07:26 +00:00
|
|
|
MOZ_ASSERT(mWorkerPrivate);
|
|
|
|
mWorkerPrivate->AssertIsOnWorkerThread();
|
2014-02-24 13:56:54 +00:00
|
|
|
|
2016-07-18 07:14:14 +00:00
|
|
|
// Release the Promise and remove the PromiseWorkerProxy from the holders of
|
2015-09-02 17:07:26 +00:00
|
|
|
// the worker thread since the Promise has been resolved/rejected or the
|
|
|
|
// worker thread has been cancelled.
|
2016-07-18 07:14:14 +00:00
|
|
|
MOZ_ASSERT(mWorkerHolder);
|
|
|
|
mWorkerHolder = nullptr;
|
|
|
|
|
2015-09-02 17:07:26 +00:00
|
|
|
CleanProperties();
|
|
|
|
}
|
|
|
|
Release();
|
2014-02-24 13:56:54 +00:00
|
|
|
}
|
|
|
|
|
2015-09-05 09:22:13 +00:00
|
|
|
JSObject*
|
2015-09-30 12:22:08 +00:00
|
|
|
PromiseWorkerProxy::CustomReadHandler(JSContext* aCx,
|
|
|
|
JSStructuredCloneReader* aReader,
|
|
|
|
uint32_t aTag,
|
|
|
|
uint32_t aIndex)
|
2015-09-05 09:22:13 +00:00
|
|
|
{
|
|
|
|
if (NS_WARN_IF(!mCallbacks)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return mCallbacks->Read(aCx, aReader, this, aTag, aIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2015-09-30 12:22:08 +00:00
|
|
|
PromiseWorkerProxy::CustomWriteHandler(JSContext* aCx,
|
|
|
|
JSStructuredCloneWriter* aWriter,
|
|
|
|
JS::Handle<JSObject*> aObj)
|
2015-09-05 09:22:13 +00:00
|
|
|
{
|
|
|
|
if (NS_WARN_IF(!mCallbacks)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return mCallbacks->Write(aCx, aWriter, this, aObj);
|
|
|
|
}
|
|
|
|
|
2014-06-03 15:38:38 +00:00
|
|
|
// Specializations of MaybeRejectBrokenly we actually support.
|
|
|
|
template<>
|
2015-10-18 05:24:48 +00:00
|
|
|
void Promise::MaybeRejectBrokenly(const RefPtr<DOMError>& aArg) {
|
2014-06-03 15:38:38 +00:00
|
|
|
MaybeSomething(aArg, &Promise::MaybeReject);
|
|
|
|
}
|
|
|
|
template<>
|
|
|
|
void Promise::MaybeRejectBrokenly(const nsAString& aArg) {
|
|
|
|
MaybeSomething(aArg, &Promise::MaybeReject);
|
|
|
|
}
|
|
|
|
|
2016-02-09 22:40:31 +00:00
|
|
|
#ifndef SPIDERMONKEY_PROMISE
|
2015-04-10 15:27:57 +00:00
|
|
|
uint64_t
|
|
|
|
Promise::GetID() {
|
|
|
|
if (mID != 0) {
|
|
|
|
return mID;
|
|
|
|
}
|
|
|
|
return mID = ++gIDGenerator;
|
|
|
|
}
|
2016-02-09 22:40:31 +00:00
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
2015-04-10 15:27:57 +00:00
|
|
|
|
2013-06-12 01:41:21 +00:00
|
|
|
} // namespace dom
|
|
|
|
} // namespace mozilla
|