Bug 1550519 - Show a translucent parent highlight when a subgrid is highlighted....
[gecko.git] / dom / promise / Promise.cpp
blobcf9cd435362d08d34562735e606d3156c4638ceb
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/Promise.h"
8 #include "mozilla/dom/Promise-inl.h"
10 #include "js/Debug.h"
12 #include "mozilla/Atomics.h"
13 #include "mozilla/CycleCollectedJSContext.h"
14 #include "mozilla/EventStateManager.h"
15 #include "mozilla/OwningNonNull.h"
16 #include "mozilla/Preferences.h"
17 #include "mozilla/ResultExtensions.h"
18 #include "mozilla/Unused.h"
20 #include "mozilla/dom/BindingUtils.h"
21 #include "mozilla/dom/DOMException.h"
22 #include "mozilla/dom/DOMExceptionBinding.h"
23 #include "mozilla/dom/MediaStreamError.h"
24 #include "mozilla/dom/PromiseBinding.h"
25 #include "mozilla/dom/ScriptSettings.h"
26 #include "mozilla/dom/WorkerPrivate.h"
27 #include "mozilla/dom/WorkerRunnable.h"
28 #include "mozilla/dom/WorkerRef.h"
30 #include "jsfriendapi.h"
31 #include "js/StructuredClone.h"
32 #include "nsContentUtils.h"
33 #include "nsGlobalWindow.h"
34 #include "nsIScriptObjectPrincipal.h"
35 #include "nsJSEnvironment.h"
36 #include "nsJSPrincipals.h"
37 #include "nsJSUtils.h"
38 #include "nsPIDOMWindow.h"
39 #include "PromiseDebugging.h"
40 #include "PromiseNativeHandler.h"
41 #include "PromiseWorkerProxy.h"
42 #include "WrapperFactory.h"
43 #include "xpcpublic.h"
44 #include "xpcprivate.h"
46 namespace mozilla {
47 namespace dom {
49 namespace {
50 // Generator used by Promise::GetID.
51 Atomic<uintptr_t> gIDGenerator(0);
52 } // namespace
54 // Promise
56 NS_IMPL_CYCLE_COLLECTION_CLASS(Promise)
58 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise)
59 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
60 tmp->mPromiseObj = nullptr;
61 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
63 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise)
64 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
65 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
67 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise)
68 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj);
69 NS_IMPL_CYCLE_COLLECTION_TRACE_END
71 NS_IMPL_CYCLE_COLLECTING_ADDREF(Promise)
72 NS_IMPL_CYCLE_COLLECTING_RELEASE(Promise)
74 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Promise)
75 NS_INTERFACE_MAP_ENTRY(nsISupports)
76 NS_INTERFACE_MAP_ENTRY(Promise)
77 NS_INTERFACE_MAP_END
79 Promise::Promise(nsIGlobalObject* aGlobal)
80 : mGlobal(aGlobal), mPromiseObj(nullptr) {
81 MOZ_ASSERT(mGlobal);
83 mozilla::HoldJSObjects(this);
86 Promise::~Promise() { mozilla::DropJSObjects(this); }
88 // static
89 already_AddRefed<Promise> Promise::Create(
90 nsIGlobalObject* aGlobal, ErrorResult& aRv,
91 PropagateUserInteraction aPropagateUserInteraction) {
92 if (!aGlobal) {
93 aRv.Throw(NS_ERROR_UNEXPECTED);
94 return nullptr;
96 RefPtr<Promise> p = new Promise(aGlobal);
97 p->CreateWrapper(nullptr, aRv, aPropagateUserInteraction);
98 if (aRv.Failed()) {
99 return nullptr;
101 return p.forget();
104 bool Promise::MaybePropagateUserInputEventHandling() {
105 JS::PromiseUserInputEventHandlingState state =
106 EventStateManager::IsHandlingUserInput()
107 ? JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
108 : JS::PromiseUserInputEventHandlingState::
109 DidntHaveUserInteractionAtCreation;
110 JS::Rooted<JSObject*> p(RootingCx(), mPromiseObj);
111 return JS::SetPromiseUserInputEventHandlingState(p, state);
114 // static
115 already_AddRefed<Promise> Promise::Resolve(
116 nsIGlobalObject* aGlobal, JSContext* aCx, JS::Handle<JS::Value> aValue,
117 ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
118 JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
119 JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseResolve(aCx, aValue));
120 if (!p) {
121 aRv.NoteJSContextException(aCx);
122 return nullptr;
125 return CreateFromExisting(aGlobal, p, aPropagateUserInteraction);
128 // static
129 already_AddRefed<Promise> Promise::Reject(nsIGlobalObject* aGlobal,
130 JSContext* aCx,
131 JS::Handle<JS::Value> aValue,
132 ErrorResult& aRv) {
133 JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
134 JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseReject(aCx, aValue));
135 if (!p) {
136 aRv.NoteJSContextException(aCx);
137 return nullptr;
140 // This promise will never be resolved, so we pass
141 // eDontPropagateUserInteraction for aPropagateUserInteraction
142 // unconditionally.
143 return CreateFromExisting(aGlobal, p, eDontPropagateUserInteraction);
146 // static
147 already_AddRefed<Promise> Promise::All(
148 JSContext* aCx, const nsTArray<RefPtr<Promise>>& aPromiseList,
149 ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
150 JS::Rooted<JSObject*> globalObj(aCx, JS::CurrentGlobalOrNull(aCx));
151 if (!globalObj) {
152 aRv.Throw(NS_ERROR_UNEXPECTED);
153 return nullptr;
156 nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(globalObj);
157 if (!global) {
158 aRv.Throw(NS_ERROR_UNEXPECTED);
159 return nullptr;
162 JS::RootedVector<JSObject*> promises(aCx);
163 if (!promises.reserve(aPromiseList.Length())) {
164 aRv.NoteJSContextException(aCx);
165 return nullptr;
168 for (auto& promise : aPromiseList) {
169 JS::Rooted<JSObject*> promiseObj(aCx, promise->PromiseObj());
170 // Just in case, make sure these are all in the context compartment.
171 if (!JS_WrapObject(aCx, &promiseObj)) {
172 aRv.NoteJSContextException(aCx);
173 return nullptr;
175 promises.infallibleAppend(promiseObj);
178 JS::Rooted<JSObject*> result(aCx, JS::GetWaitForAllPromise(aCx, promises));
179 if (!result) {
180 aRv.NoteJSContextException(aCx);
181 return nullptr;
184 return CreateFromExisting(global, result, aPropagateUserInteraction);
187 void Promise::Then(JSContext* aCx,
188 // aCalleeGlobal may not be in the compartment of aCx, when
189 // called over Xrays.
190 JS::Handle<JSObject*> aCalleeGlobal,
191 AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
192 JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
193 NS_ASSERT_OWNINGTHREAD(Promise);
195 // Let's hope this does the right thing with Xrays... Ensure everything is
196 // just in the caller compartment; that ought to do the trick. In theory we
197 // should consider aCalleeGlobal, but in practice our only caller is
198 // DOMRequest::Then, which is not working with a Promise subclass, so things
199 // should be OK.
200 JS::Rooted<JSObject*> promise(aCx, PromiseObj());
201 if (!JS_WrapObject(aCx, &promise)) {
202 aRv.NoteJSContextException(aCx);
203 return;
206 JS::Rooted<JSObject*> resolveCallback(aCx);
207 if (aResolveCallback) {
208 resolveCallback = aResolveCallback->CallbackOrNull();
209 if (!JS_WrapObject(aCx, &resolveCallback)) {
210 aRv.NoteJSContextException(aCx);
211 return;
215 JS::Rooted<JSObject*> rejectCallback(aCx);
216 if (aRejectCallback) {
217 rejectCallback = aRejectCallback->CallbackOrNull();
218 if (!JS_WrapObject(aCx, &rejectCallback)) {
219 aRv.NoteJSContextException(aCx);
220 return;
224 JS::Rooted<JSObject*> retval(aCx);
225 retval = JS::CallOriginalPromiseThen(aCx, promise, resolveCallback,
226 rejectCallback);
227 if (!retval) {
228 aRv.NoteJSContextException(aCx);
229 return;
232 aRetval.setObject(*retval);
235 void PromiseNativeThenHandlerBase::ResolvedCallback(
236 JSContext* aCx, JS::Handle<JS::Value> aValue) {
237 RefPtr<Promise> promise = CallResolveCallback(aCx, aValue);
238 if (promise) {
239 mPromise->MaybeResolve(promise);
240 } else {
241 mPromise->MaybeResolve(JS::UndefinedHandleValue);
245 void PromiseNativeThenHandlerBase::RejectedCallback(
246 JSContext* aCx, JS::Handle<JS::Value> aValue) {
247 mPromise->MaybeReject(aCx, aValue);
250 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeThenHandlerBase)
252 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeThenHandlerBase)
253 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
254 tmp->Traverse(cb);
255 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
257 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeThenHandlerBase)
258 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
259 tmp->Unlink();
260 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
262 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase)
263 NS_INTERFACE_MAP_ENTRY(nsISupports)
264 NS_INTERFACE_MAP_END
266 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeThenHandlerBase)
267 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeThenHandlerBase)
269 Result<RefPtr<Promise>, nsresult> Promise::ThenWithoutCycleCollection(
270 const std::function<already_AddRefed<Promise>(
271 JSContext* aCx, JS::HandleValue aValue)>& aCallback) {
272 return ThenWithCycleCollectedArgs(aCallback);
275 void Promise::CreateWrapper(
276 JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv,
277 PropagateUserInteraction aPropagateUserInteraction) {
278 AutoJSAPI jsapi;
279 if (!jsapi.Init(mGlobal)) {
280 aRv.Throw(NS_ERROR_UNEXPECTED);
281 return;
283 JSContext* cx = jsapi.cx();
284 mPromiseObj = JS::NewPromiseObject(cx, nullptr, aDesiredProto);
285 if (!mPromiseObj) {
286 JS_ClearPendingException(cx);
287 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
288 return;
290 if (aPropagateUserInteraction == ePropagateUserInteraction) {
291 Unused << MaybePropagateUserInputEventHandling();
295 void Promise::MaybeResolve(JSContext* aCx, JS::Handle<JS::Value> aValue) {
296 NS_ASSERT_OWNINGTHREAD(Promise);
298 JS::Rooted<JSObject*> p(aCx, PromiseObj());
299 if (!JS::ResolvePromise(aCx, p, aValue)) {
300 // Now what? There's nothing sane to do here.
301 JS_ClearPendingException(aCx);
305 void Promise::MaybeReject(JSContext* aCx, JS::Handle<JS::Value> aValue) {
306 NS_ASSERT_OWNINGTHREAD(Promise);
308 JS::Rooted<JSObject*> p(aCx, PromiseObj());
309 if (!JS::RejectPromise(aCx, p, aValue)) {
310 // Now what? There's nothing sane to do here.
311 JS_ClearPendingException(aCx);
315 #define SLOT_NATIVEHANDLER 0
316 #define SLOT_NATIVEHANDLER_TASK 1
318 enum class NativeHandlerTask : int32_t { Resolve, Reject };
320 MOZ_CAN_RUN_SCRIPT
321 static bool NativeHandlerCallback(JSContext* aCx, unsigned aArgc,
322 JS::Value* aVp) {
323 JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
325 JS::Value v =
326 js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER);
327 MOZ_ASSERT(v.isObject());
329 JS::Rooted<JSObject*> obj(aCx, &v.toObject());
330 PromiseNativeHandler* handler = nullptr;
331 if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &obj, handler))) {
332 return Throw(aCx, NS_ERROR_UNEXPECTED);
335 v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
336 NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
338 if (task == NativeHandlerTask::Resolve) {
339 // handler is kept alive by "obj" on the stack.
340 MOZ_KnownLive(handler)->ResolvedCallback(aCx, args.get(0));
341 } else {
342 MOZ_ASSERT(task == NativeHandlerTask::Reject);
343 // handler is kept alive by "obj" on the stack.
344 MOZ_KnownLive(handler)->RejectedCallback(aCx, args.get(0));
347 return true;
350 static JSObject* CreateNativeHandlerFunction(JSContext* aCx,
351 JS::Handle<JSObject*> aHolder,
352 NativeHandlerTask aTask) {
353 JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback,
354 /* nargs = */ 1,
355 /* flags = */ 0, nullptr);
356 if (!func) {
357 return nullptr;
360 JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
362 JS::AssertObjectIsNotGray(aHolder);
363 js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER,
364 JS::ObjectValue(*aHolder));
365 js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER_TASK,
366 JS::Int32Value(static_cast<int32_t>(aTask)));
368 return obj;
371 namespace {
373 class PromiseNativeHandlerShim final : public PromiseNativeHandler {
374 RefPtr<PromiseNativeHandler> mInner;
376 ~PromiseNativeHandlerShim() {}
378 public:
379 explicit PromiseNativeHandlerShim(PromiseNativeHandler* aInner)
380 : mInner(aInner) {
381 MOZ_ASSERT(mInner);
384 MOZ_CAN_RUN_SCRIPT
385 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
386 RefPtr<PromiseNativeHandler> inner = mInner.forget();
387 inner->ResolvedCallback(aCx, aValue);
388 MOZ_ASSERT(!mInner);
391 MOZ_CAN_RUN_SCRIPT
392 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
393 RefPtr<PromiseNativeHandler> inner = mInner.forget();
394 inner->RejectedCallback(aCx, aValue);
395 MOZ_ASSERT(!mInner);
398 bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
399 JS::MutableHandle<JSObject*> aWrapper) {
400 return PromiseNativeHandler_Binding::Wrap(aCx, this, aGivenProto, aWrapper);
403 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
404 NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim)
407 NS_IMPL_CYCLE_COLLECTION(PromiseNativeHandlerShim, mInner)
409 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeHandlerShim)
410 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeHandlerShim)
412 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeHandlerShim)
413 NS_INTERFACE_MAP_ENTRY(nsISupports)
414 NS_INTERFACE_MAP_END
416 } // anonymous namespace
418 void Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable) {
419 NS_ASSERT_OWNINGTHREAD(Promise);
421 AutoJSAPI jsapi;
422 if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
423 // Our API doesn't allow us to return a useful error. Not like this should
424 // happen anyway.
425 return;
428 // The self-hosted promise js may keep the object we pass to it alive
429 // for quite a while depending on when GC runs. Therefore, pass a shim
430 // object instead. The shim will free its inner PromiseNativeHandler
431 // after the promise has settled just like our previous c++ promises did.
432 RefPtr<PromiseNativeHandlerShim> shim =
433 new PromiseNativeHandlerShim(aRunnable);
435 JSContext* cx = jsapi.cx();
436 JS::Rooted<JSObject*> handlerWrapper(cx);
437 // Note: PromiseNativeHandler is NOT wrappercached. So we can't use
438 // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
439 if (NS_WARN_IF(!shim->WrapObject(cx, nullptr, &handlerWrapper))) {
440 // Again, no way to report errors.
441 jsapi.ClearException();
442 return;
445 JS::Rooted<JSObject*> resolveFunc(cx);
446 resolveFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
447 NativeHandlerTask::Resolve);
448 if (NS_WARN_IF(!resolveFunc)) {
449 jsapi.ClearException();
450 return;
453 JS::Rooted<JSObject*> rejectFunc(cx);
454 rejectFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
455 NativeHandlerTask::Reject);
456 if (NS_WARN_IF(!rejectFunc)) {
457 jsapi.ClearException();
458 return;
461 JS::Rooted<JSObject*> promiseObj(cx, PromiseObj());
462 if (NS_WARN_IF(
463 !JS::AddPromiseReactions(cx, promiseObj, resolveFunc, rejectFunc))) {
464 jsapi.ClearException();
465 return;
469 void Promise::HandleException(JSContext* aCx) {
470 JS::Rooted<JS::Value> exn(aCx);
471 if (JS_GetPendingException(aCx, &exn)) {
472 JS_ClearPendingException(aCx);
473 // This is only called from MaybeSomething, so it's OK to MaybeReject here.
474 MaybeReject(aCx, exn);
478 // static
479 already_AddRefed<Promise> Promise::CreateFromExisting(
480 nsIGlobalObject* aGlobal, JS::Handle<JSObject*> aPromiseObj,
481 PropagateUserInteraction aPropagateUserInteraction) {
482 MOZ_ASSERT(
483 js::GetObjectCompartment(aGlobal->GetGlobalJSObjectPreserveColor()) ==
484 js::GetObjectCompartment(aPromiseObj));
485 RefPtr<Promise> p = new Promise(aGlobal);
486 p->mPromiseObj = aPromiseObj;
487 if (aPropagateUserInteraction == ePropagateUserInteraction &&
488 !p->MaybePropagateUserInputEventHandling()) {
489 return nullptr;
491 return p.forget();
494 void Promise::MaybeResolveWithUndefined() {
495 NS_ASSERT_OWNINGTHREAD(Promise);
497 MaybeResolve(JS::UndefinedHandleValue);
500 void Promise::MaybeReject(const RefPtr<MediaStreamError>& aArg) {
501 NS_ASSERT_OWNINGTHREAD(Promise);
503 MaybeSomething(aArg, &Promise::MaybeReject);
506 void Promise::MaybeRejectWithUndefined() {
507 NS_ASSERT_OWNINGTHREAD(Promise);
509 MaybeSomething(JS::UndefinedHandleValue, &Promise::MaybeReject);
512 void Promise::ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise) {
513 MOZ_ASSERT(!js::IsWrapper(aPromise));
515 MOZ_ASSERT(JS::GetPromiseState(aPromise) == JS::PromiseState::Rejected);
517 JS::Rooted<JS::Value> result(aCx, JS::GetPromiseResult(aPromise));
519 RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
520 bool isMainThread = MOZ_LIKELY(NS_IsMainThread());
521 bool isChrome = isMainThread ? nsContentUtils::IsSystemPrincipal(
522 nsContentUtils::ObjectPrincipal(aPromise))
523 : IsCurrentThreadRunningChromeWorker();
524 nsGlobalWindowInner* win =
525 isMainThread ? xpc::WindowGlobalOrNull(aPromise) : nullptr;
527 js::ErrorReport report(aCx);
528 if (report.init(aCx, result, js::ErrorReport::NoSideEffects)) {
529 xpcReport->Init(report.report(), report.toStringResult().c_str(), isChrome,
530 win ? win->WindowID() : 0);
531 } else {
532 JS_ClearPendingException(aCx);
534 RefPtr<Exception> exn;
535 if (result.isObject() &&
536 (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, &result, exn)) ||
537 NS_SUCCEEDED(UNWRAP_OBJECT(Exception, &result, exn)))) {
538 xpcReport->Init(aCx, exn, isChrome, win ? win->WindowID() : 0);
539 } else {
540 return;
544 // Now post an event to do the real reporting async
545 RefPtr<nsIRunnable> event = new AsyncErrorReporter(xpcReport);
546 if (win) {
547 win->Dispatch(mozilla::TaskCategory::Other, event.forget());
548 } else {
549 NS_DispatchToMainThread(event);
553 void Promise::MaybeResolveWithClone(JSContext* aCx,
554 JS::Handle<JS::Value> aValue) {
555 JS::Rooted<JSObject*> sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
556 AutoEntryScript aes(GetParentObject(), "Promise resolution");
557 JSContext* cx = aes.cx();
558 JS::Rooted<JS::Value> value(cx, aValue);
560 xpc::StackScopedCloneOptions options;
561 options.wrapReflectors = true;
562 StackScopedClone(cx, options, sourceScope, &value);
563 MaybeResolve(aCx, value);
566 void Promise::MaybeRejectWithClone(JSContext* aCx,
567 JS::Handle<JS::Value> aValue) {
568 JS::Rooted<JSObject*> sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
569 AutoEntryScript aes(GetParentObject(), "Promise rejection");
570 JSContext* cx = aes.cx();
571 JS::Rooted<JS::Value> value(cx, aValue);
573 xpc::StackScopedCloneOptions options;
574 options.wrapReflectors = true;
575 StackScopedClone(cx, options, sourceScope, &value);
576 MaybeReject(aCx, value);
579 // A WorkerRunnable to resolve/reject the Promise on the worker thread.
580 // Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
581 class PromiseWorkerProxyRunnable : public WorkerRunnable {
582 public:
583 PromiseWorkerProxyRunnable(PromiseWorkerProxy* aPromiseWorkerProxy,
584 PromiseWorkerProxy::RunCallbackFunc aFunc)
585 : WorkerRunnable(aPromiseWorkerProxy->GetWorkerPrivate(),
586 WorkerThreadUnchangedBusyCount),
587 mPromiseWorkerProxy(aPromiseWorkerProxy),
588 mFunc(aFunc) {
589 MOZ_ASSERT(NS_IsMainThread());
590 MOZ_ASSERT(mPromiseWorkerProxy);
593 virtual bool WorkerRun(JSContext* aCx,
594 WorkerPrivate* aWorkerPrivate) override {
595 MOZ_ASSERT(aWorkerPrivate);
596 aWorkerPrivate->AssertIsOnWorkerThread();
597 MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
599 MOZ_ASSERT(mPromiseWorkerProxy);
600 RefPtr<Promise> workerPromise = mPromiseWorkerProxy->WorkerPromise();
602 // Here we convert the buffer to a JS::Value.
603 JS::Rooted<JS::Value> value(aCx);
604 if (!mPromiseWorkerProxy->Read(aCx, &value)) {
605 JS_ClearPendingException(aCx);
606 return false;
609 (workerPromise->*mFunc)(aCx, value);
611 // Release the Promise because it has been resolved/rejected for sure.
612 mPromiseWorkerProxy->CleanUp();
613 return true;
616 protected:
617 ~PromiseWorkerProxyRunnable() {}
619 private:
620 RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
622 // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
623 PromiseWorkerProxy::RunCallbackFunc mFunc;
626 /* static */
627 already_AddRefed<PromiseWorkerProxy> PromiseWorkerProxy::Create(
628 WorkerPrivate* aWorkerPrivate, Promise* aWorkerPromise,
629 const PromiseWorkerProxyStructuredCloneCallbacks* aCb) {
630 MOZ_ASSERT(aWorkerPrivate);
631 aWorkerPrivate->AssertIsOnWorkerThread();
632 MOZ_ASSERT(aWorkerPromise);
633 MOZ_ASSERT_IF(aCb, !!aCb->Write && !!aCb->Read);
635 RefPtr<PromiseWorkerProxy> proxy =
636 new PromiseWorkerProxy(aWorkerPromise, aCb);
638 // We do this to make sure the worker thread won't shut down before the
639 // promise is resolved/rejected on the worker thread.
640 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
641 aWorkerPrivate, "PromiseWorkerProxy", [proxy]() { proxy->CleanUp(); });
643 if (NS_WARN_IF(!workerRef)) {
644 // Probably the worker is terminating. We cannot complete the operation
645 // and we have to release all the resources.
646 proxy->CleanProperties();
647 return nullptr;
650 proxy->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
652 // Maintain a reference so that we have a valid object to clean up when
653 // removing the feature.
654 proxy.get()->AddRef();
656 return proxy.forget();
659 NS_IMPL_ISUPPORTS0(PromiseWorkerProxy)
661 PromiseWorkerProxy::PromiseWorkerProxy(
662 Promise* aWorkerPromise,
663 const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks)
664 : mWorkerPromise(aWorkerPromise),
665 mCleanedUp(false),
666 mCallbacks(aCallbacks),
667 mCleanUpLock("cleanUpLock") {}
669 PromiseWorkerProxy::~PromiseWorkerProxy() {
670 MOZ_ASSERT(mCleanedUp);
671 MOZ_ASSERT(!mWorkerPromise);
672 MOZ_ASSERT(!mWorkerRef);
675 void PromiseWorkerProxy::CleanProperties() {
676 MOZ_ASSERT(IsCurrentThreadRunningWorker());
678 // Ok to do this unprotected from Create().
679 // CleanUp() holds the lock before calling this.
680 mCleanedUp = true;
681 mWorkerPromise = nullptr;
682 mWorkerRef = nullptr;
684 // Clear the StructuredCloneHolderBase class.
685 Clear();
688 WorkerPrivate* PromiseWorkerProxy::GetWorkerPrivate() const {
689 #ifdef DEBUG
690 if (NS_IsMainThread()) {
691 mCleanUpLock.AssertCurrentThreadOwns();
693 #endif
694 // Safe to check this without a lock since we assert lock ownership on the
695 // main thread above.
696 MOZ_ASSERT(!mCleanedUp);
697 MOZ_ASSERT(mWorkerRef);
699 return mWorkerRef->Private();
702 Promise* PromiseWorkerProxy::WorkerPromise() const {
703 MOZ_ASSERT(IsCurrentThreadRunningWorker());
704 MOZ_ASSERT(mWorkerPromise);
705 return mWorkerPromise;
708 void PromiseWorkerProxy::RunCallback(JSContext* aCx,
709 JS::Handle<JS::Value> aValue,
710 RunCallbackFunc aFunc) {
711 MOZ_ASSERT(NS_IsMainThread());
713 MutexAutoLock lock(Lock());
714 // If the worker thread's been cancelled we don't need to resolve the Promise.
715 if (CleanedUp()) {
716 return;
719 // The |aValue| is written into the StructuredCloneHolderBase.
720 if (!Write(aCx, aValue)) {
721 JS_ClearPendingException(aCx);
722 MOZ_ASSERT(false,
723 "cannot serialize the value with the StructuredCloneAlgorithm!");
726 RefPtr<PromiseWorkerProxyRunnable> runnable =
727 new PromiseWorkerProxyRunnable(this, aFunc);
729 runnable->Dispatch();
732 void PromiseWorkerProxy::ResolvedCallback(JSContext* aCx,
733 JS::Handle<JS::Value> aValue) {
734 RunCallback(aCx, aValue, &Promise::MaybeResolve);
737 void PromiseWorkerProxy::RejectedCallback(JSContext* aCx,
738 JS::Handle<JS::Value> aValue) {
739 RunCallback(aCx, aValue, &Promise::MaybeReject);
742 void PromiseWorkerProxy::CleanUp() {
743 // Can't release Mutex while it is still locked, so scope the lock.
745 MutexAutoLock lock(Lock());
747 if (CleanedUp()) {
748 return;
751 MOZ_ASSERT(mWorkerRef);
752 mWorkerRef->Private()->AssertIsOnWorkerThread();
754 // Release the Promise and remove the PromiseWorkerProxy from the holders of
755 // the worker thread since the Promise has been resolved/rejected or the
756 // worker thread has been cancelled.
757 mWorkerRef = nullptr;
759 CleanProperties();
761 Release();
764 JSObject* PromiseWorkerProxy::CustomReadHandler(
765 JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
766 uint32_t aIndex) {
767 if (NS_WARN_IF(!mCallbacks)) {
768 return nullptr;
771 return mCallbacks->Read(aCx, aReader, this, aTag, aIndex);
774 bool PromiseWorkerProxy::CustomWriteHandler(JSContext* aCx,
775 JSStructuredCloneWriter* aWriter,
776 JS::Handle<JSObject*> aObj) {
777 if (NS_WARN_IF(!mCallbacks)) {
778 return false;
781 return mCallbacks->Write(aCx, aWriter, this, aObj);
784 // Specializations of MaybeRejectBrokenly we actually support.
785 template <>
786 void Promise::MaybeRejectBrokenly(const RefPtr<DOMException>& aArg) {
787 MaybeSomething(aArg, &Promise::MaybeReject);
789 template <>
790 void Promise::MaybeRejectBrokenly(const nsAString& aArg) {
791 MaybeSomething(aArg, &Promise::MaybeReject);
794 Promise::PromiseState Promise::State() const {
795 JS::Rooted<JSObject*> p(RootingCx(), PromiseObj());
796 const JS::PromiseState state = JS::GetPromiseState(p);
798 if (state == JS::PromiseState::Fulfilled) {
799 return PromiseState::Resolved;
802 if (state == JS::PromiseState::Rejected) {
803 return PromiseState::Rejected;
806 return PromiseState::Pending;
809 } // namespace dom
810 } // namespace mozilla