/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise) // If you add new JS member variables, you may need to stop using // NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS.
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj);
NS_IMPL_CYCLE_COLLECTION_TRACE_END
// static
already_AddRefed<Promise> Promise::Create(
nsIGlobalObject* aGlobal, ErrorResult& aRv,
PropagateUserInteraction aPropagateUserInteraction) { if (!aGlobal) {
aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr;
}
RefPtr<Promise> p = new Promise(aGlobal);
p->CreateWrapper(aRv, aPropagateUserInteraction); if (aRv.Failed()) { return nullptr;
} return p.forget();
}
// static
already_AddRefed<Promise> Promise::CreateInfallible(
nsIGlobalObject* aGlobal,
PropagateUserInteraction aPropagateUserInteraction) {
MOZ_ASSERT(aGlobal);
RefPtr<Promise> p = new Promise(aGlobal);
IgnoredErrorResult rv;
p->CreateWrapper(rv, aPropagateUserInteraction); if (rv.Failed()) { if (rv.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY)) {
NS_ABORT_OOM(0); // (0 meaning unknown size)
} if (rv.ErrorCodeIs(NS_ERROR_NOT_INITIALIZED)) {
MOZ_CRASH("Failed to create promise wrapper for unknown non-OOM reason");
}
}
// We may have failed to init the wrapper here, because nsIGlobalObject had // null GlobalJSObject. In that case we consider the JS realm is dead, which // means: // 1. This promise can't be settled. // 2. Nothing can subscribe this promise anymore from that realm. // Such condition makes this promise a no-op object.
(void)NS_WARN_IF(!p->PromiseObj());
return p.forget();
}
bool Promise::MaybePropagateUserInputEventHandling() {
MOZ_ASSERT(mPromiseObj, "Should be called only if the wrapper is successfully created");
JS::PromiseUserInputEventHandlingState state =
UserActivation::IsHandlingUserInput()
? JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
: JS::PromiseUserInputEventHandlingState::
DidntHaveUserInteractionAtCreation;
JS::Rooted<JSObject*> p(RootingCx(), mPromiseObj); return JS::SetPromiseUserInputEventHandlingState(p, state);
}
// This promise will never be resolved, so we pass // eDontPropagateUserInteraction for aPropagateUserInteraction // unconditionally. return CreateFromExisting(aGlobal, p, eDontPropagateUserInteraction);
}
nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(globalObj); if (!global) {
aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr;
}
JS::RootedVector<JSObject*> promises(aCx); if (!promises.reserve(aPromiseList.Length())) {
aRv.NoteJSContextException(aCx); return nullptr;
}
for (constauto& promise : aPromiseList) {
JS::Rooted<JSObject*> promiseObj(aCx, promise->PromiseObj()); if (!promiseObj) { // No-op object will never settle, so we return a no-op Promise here, // which is equivalent of returning the existing no-op one. return do_AddRef(promise);
} // Just in case, make sure these are all in the context compartment. if (!JS_WrapObject(aCx, &promiseObj)) {
aRv.NoteJSContextException(aCx); return nullptr;
}
promises.infallibleAppend(promiseObj);
}
v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
ErrorResult rv; if (task == NativeHandlerTask::Resolve) { // handler is kept alive by "obj" on the stack.
MOZ_KnownLive(handler)->ResolvedCallback(aCx, args.get(0), rv);
} else {
MOZ_ASSERT(task == NativeHandlerTask::Reject); // handler is kept alive by "obj" on the stack.
MOZ_KnownLive(handler)->RejectedCallback(aCx, args.get(0), rv);
}
AutoJSAPI jsapi; if (NS_WARN_IF(!mPromiseObj || !jsapi.Init(mGlobal))) { // Our API doesn't allow us to return a useful error. Not like this should // happen anyway. return;
}
// The self-hosted promise js may keep the object we pass to it alive // for quite a while depending on when GC runs. Therefore, pass a shim // object instead. The shim will free its inner PromiseNativeHandler // after the promise has settled just like our previous c++ promises did.
RefPtr<PromiseNativeHandlerShim> shim = new PromiseNativeHandlerShim(aRunnable);
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(!shim->WrapObject(cx, nullptr, &handlerWrapper))) { // Again, no way to report errors.
jsapi.ClearException(); return;
}
void Promise::HandleException(JSContext* aCx) {
JS::Rooted<JS::Value> exn(aCx); if (JS_GetPendingException(aCx, &exn)) {
JS_ClearPendingException(aCx); // Always reject even if this was called in *Resolve.
MaybeReject(aCx, exn);
}
}
// static
already_AddRefed<Promise> Promise::RejectWithExceptionFromContext(
nsIGlobalObject* aGlobal, JSContext* aCx, ErrorResult& aError) {
JS::Rooted<JS::Value> exn(aCx); if (!JS_GetPendingException(aCx, &exn)) { // This is very important: if there is no pending exception here but we're // ending up in this code, that means the callee threw an uncatchable // exception. Just propagate that out as-is.
aError.ThrowUncatchableException(); return nullptr;
}
JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject()); if (!JS_WrapValue(aCx, &exn)) { // We just give up.
aError.StealExceptionFromJSContext(aCx); return nullptr;
}
JS_ClearPendingException(aCx);
IgnoredErrorResult error;
RefPtr<Promise> promise = Promise::Reject(aGlobal, aCx, exn, error); if (!promise) { // We just give up, let's store the exception in the ErrorResult.
aError.ThrowJSException(aCx, exn); return nullptr;
}
if (nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aPromise)) {
winForDispatch = win;
innerWindowID = win->WindowID();
} elseif (nsGlobalWindowInner* win = xpc::SandboxWindowOrNull(
JS::GetNonCCWObjectGlobal(aPromise), aCx)) { // Don't dispatch rejections from the sandbox to the associated DOM // window.
innerWindowID = win->WindowID();
}
} elseif (const WorkerPrivate* wp = GetCurrentThreadWorkerPrivate()) {
isChrome = wp->UsesSystemPrincipal();
innerWindowID = wp->WindowID();
} elseif (nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(aPromise)) { if (nsCOMPtr<WorkletGlobalScope> workletGlobal =
do_QueryInterface(global)) {
WorkletImpl* impl = workletGlobal->Impl();
isChrome = impl->PrincipalInfo().type() ==
mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo;
innerWindowID = impl->LoadInfo().InnerWindowID();
}
}
JS::Rooted<JS::Value> result(aCx, JS::GetPromiseResult(aPromise)); // resolutionSite can be null if async stacks are disabled.
JS::Rooted<JSObject*> resolutionSite(aCx,
JS::GetPromiseResolutionSite(aPromise));
// We're inspecting the rejection value only to report it to the console, and // we do so without side-effects, so we can safely unwrap it without regard to // the privileges of the Promise object that holds it. If we don't unwrap // before trying to create the error report, we wind up reporting any // cross-origin objects as "uncaught exception: Object".
RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
{
Maybe<JSAutoRealm> ar;
JS::Rooted<JS::Value> unwrapped(aCx, result); if (unwrapped.isObject()) {
unwrapped.setObject(*js::UncheckedUnwrap(&unwrapped.toObject()));
ar.emplace(aCx, &unwrapped.toObject());
}
JS::ErrorReportBuilder report(aCx);
RefPtr<Exception> exn; if (unwrapped.isObject() &&
(NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, &unwrapped, exn)) ||
NS_SUCCEEDED(UNWRAP_OBJECT(Exception, &unwrapped, exn)))) {
xpcReport->Init(aCx, exn, isChrome, innerWindowID);
} else { // Use the resolution site as the exception stack
JS::ExceptionStack exnStack(aCx, unwrapped, resolutionSite); if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::NoSideEffects)) {
JS_ClearPendingException(aCx); return;
}
// Used to initialize the similarly named nsISciptError attribute.
xpcReport->mIsPromiseRejection = true;
// Now post an event to do the real reporting async
RefPtr<AsyncErrorReporter> event = new AsyncErrorReporter(xpcReport); if (winForDispatch) { if (!winForDispatch->IsDying()) { // Exceptions from a dying window will cause the window to leak.
event->SetException(aCx, result); if (resolutionSite) {
event->SerializeStack(aCx, resolutionSite);
}
}
winForDispatch->Dispatch(event.forget());
} else {
NS_DispatchToMainThread(event);
}
}
// A WorkerRunnable to resolve/reject the Promise on the worker thread. // Calling thread MUST hold PromiseWorkerProxy's mutex before creating this. class PromiseWorkerProxyRunnable final : public WorkerThreadRunnable { public:
PromiseWorkerProxyRunnable(PromiseWorkerProxy* aPromiseWorkerProxy,
PromiseWorkerProxy::RunCallbackFunc aFunc)
: WorkerThreadRunnable("PromiseWorkerProxyRunnable"),
mPromiseWorkerProxy(aPromiseWorkerProxy),
mFunc(aFunc) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mPromiseWorkerProxy);
}
MOZ_ASSERT(mPromiseWorkerProxy);
RefPtr<Promise> workerPromise = mPromiseWorkerProxy->GetWorkerPromise(); // Once Worker had already started shutdown, workerPromise would be nullptr if (!workerPromise) { returntrue;
}
// Here we convert the buffer to a JS::Value.
JS::Rooted<JS::Value> value(aCx); if (!mPromiseWorkerProxy->Read(aCx, &value)) {
JS_ClearPendingException(aCx); returnfalse;
}
(workerPromise->*mFunc)(aCx, value);
// Release the Promise because it has been resolved/rejected for sure.
mPromiseWorkerProxy->CleanUp(); returntrue;
}
RefPtr<PromiseWorkerProxy> proxy = new PromiseWorkerProxy(aWorkerPromise, aCb);
// Maintain a reference so that we have a valid object to clean up when // removing the feature.
proxy.get()->AddRef();
// We do this to make sure the worker thread won't shut down before the // promise is resolved/rejected on the worker thread.
RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
aWorkerPrivate, "PromiseWorkerProxy", [proxy]() { proxy->CleanUp(); });
if (NS_WARN_IF(!workerRef)) { // Probably the worker is terminating. We cannot complete the operation // and we have to release all the resources. CleanUp releases the extra // ref, too
proxy->CleanUp(); return nullptr;
}
proxy->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
WorkerPrivate* PromiseWorkerProxy::GetWorkerPrivate() const { #ifdef DEBUG if (NS_IsMainThread()) {
mCleanUpLock.AssertCurrentThreadOwns();
} #endif // Safe to check this without a lock since we assert lock ownership on the // main thread above.
MOZ_ASSERT(!mCleanedUp);
MOZ_ASSERT(mWorkerRef);
MutexAutoLock lock(Lock()); // If the worker thread's been cancelled we don't need to resolve the Promise. if (CleanedUp()) { return;
}
// The |aValue| is written into the StructuredCloneHolderBase. if (!Write(aCx, aValue)) {
JS_ClearPendingException(aCx);
MOZ_ASSERT(false, "cannot serialize the value with the StructuredCloneAlgorithm!");
}
RefPtr<PromiseWorkerProxyRunnable> runnable = new PromiseWorkerProxyRunnable(this, aFunc);
void PromiseWorkerProxy::CleanUp() { // Can't release Mutex while it is still locked, so scope the lock.
{
MutexAutoLock lock(Lock());
if (CleanedUp()) { return;
}
// We can be called if we failed to get a WorkerRef if (mWorkerRef) {
mWorkerRef->Private()->AssertIsOnWorkerThread();
}
// Release the Promise and remove the PromiseWorkerProxy from the holders of // the worker thread since the Promise has been resolved/rejected or the // worker thread has been cancelled.
mCleanedUp = true;
mWorkerPromise = nullptr;
mWorkerRef = nullptr;
// Clear the StructuredCloneHolderBase class.
Clear();
}
Release();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.