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/. */
6 
7 #include "mozilla/dom/Promise.h"
8 #include "mozilla/dom/Promise-inl.h"
9 
10 #include "js/Debug.h"
11 
12 #include "mozilla/Atomics.h"
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/CycleCollectedJSContext.h"
15 #include "mozilla/HoldDropJSObjects.h"
16 #include "mozilla/OwningNonNull.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/ResultExtensions.h"
19 #include "mozilla/Unused.h"
20 
21 #include "mozilla/dom/AutoEntryScript.h"
22 #include "mozilla/dom/BindingUtils.h"
23 #include "mozilla/dom/DOMException.h"
24 #include "mozilla/dom/DOMExceptionBinding.h"
25 #include "mozilla/dom/Exceptions.h"
26 #include "mozilla/dom/MediaStreamError.h"
27 #include "mozilla/dom/PromiseBinding.h"
28 #include "mozilla/dom/ScriptSettings.h"
29 #include "mozilla/dom/UserActivation.h"
30 #include "mozilla/dom/WorkerPrivate.h"
31 #include "mozilla/dom/WorkerRunnable.h"
32 #include "mozilla/dom/WorkerRef.h"
33 #include "mozilla/dom/WorkletImpl.h"
34 #include "mozilla/dom/WorkletGlobalScope.h"
35 
36 #include "jsfriendapi.h"
37 #include "js/Exception.h"  // JS::ExceptionStack
38 #include "js/Object.h"     // JS::GetCompartment
39 #include "js/StructuredClone.h"
40 #include "nsContentUtils.h"
41 #include "nsGlobalWindow.h"
42 #include "nsIScriptObjectPrincipal.h"
43 #include "nsJSEnvironment.h"
44 #include "nsJSPrincipals.h"
45 #include "nsJSUtils.h"
46 #include "nsPIDOMWindow.h"
47 #include "PromiseDebugging.h"
48 #include "PromiseNativeHandler.h"
49 #include "PromiseWorkerProxy.h"
50 #include "WrapperFactory.h"
51 #include "xpcpublic.h"
52 #include "xpcprivate.h"
53 
54 namespace mozilla {
55 namespace dom {
56 
57 // Promise
58 
59 NS_IMPL_CYCLE_COLLECTION_CLASS(Promise)
60 
61 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise)
62   NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
63   NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
64   tmp->mPromiseObj = nullptr;
65 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
66 
67 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise)
68   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
69 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
70 
71 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise)
72   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj);
73 NS_IMPL_CYCLE_COLLECTION_TRACE_END
74 
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Promise,AddRef)75 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Promise, AddRef)
76 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Promise, Release)
77 
78 Promise::Promise(nsIGlobalObject* aGlobal)
79     : mGlobal(aGlobal), mPromiseObj(nullptr) {
80   MOZ_ASSERT(mGlobal);
81 
82   mozilla::HoldJSObjects(this);
83 }
84 
~Promise()85 Promise::~Promise() { mozilla::DropJSObjects(this); }
86 
87 // static
Create(nsIGlobalObject * aGlobal,ErrorResult & aRv,PropagateUserInteraction aPropagateUserInteraction)88 already_AddRefed<Promise> Promise::Create(
89     nsIGlobalObject* aGlobal, ErrorResult& aRv,
90     PropagateUserInteraction aPropagateUserInteraction) {
91   if (!aGlobal) {
92     aRv.Throw(NS_ERROR_UNEXPECTED);
93     return nullptr;
94   }
95   RefPtr<Promise> p = new Promise(aGlobal);
96   p->CreateWrapper(aRv, aPropagateUserInteraction);
97   if (aRv.Failed()) {
98     return nullptr;
99   }
100   return p.forget();
101 }
102 
MaybePropagateUserInputEventHandling()103 bool Promise::MaybePropagateUserInputEventHandling() {
104   JS::PromiseUserInputEventHandlingState state =
105       UserActivation::IsHandlingUserInput()
106           ? JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
107           : JS::PromiseUserInputEventHandlingState::
108                 DidntHaveUserInteractionAtCreation;
109   JS::Rooted<JSObject*> p(RootingCx(), mPromiseObj);
110   return JS::SetPromiseUserInputEventHandlingState(p, state);
111 }
112 
113 // static
Resolve(nsIGlobalObject * aGlobal,JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv,PropagateUserInteraction aPropagateUserInteraction)114 already_AddRefed<Promise> Promise::Resolve(
115     nsIGlobalObject* aGlobal, JSContext* aCx, JS::Handle<JS::Value> aValue,
116     ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
117   JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
118   JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseResolve(aCx, aValue));
119   if (!p) {
120     aRv.NoteJSContextException(aCx);
121     return nullptr;
122   }
123 
124   return CreateFromExisting(aGlobal, p, aPropagateUserInteraction);
125 }
126 
127 // static
Reject(nsIGlobalObject * aGlobal,JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)128 already_AddRefed<Promise> Promise::Reject(nsIGlobalObject* aGlobal,
129                                           JSContext* aCx,
130                                           JS::Handle<JS::Value> aValue,
131                                           ErrorResult& aRv) {
132   JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
133   JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseReject(aCx, aValue));
134   if (!p) {
135     aRv.NoteJSContextException(aCx);
136     return nullptr;
137   }
138 
139   // This promise will never be resolved, so we pass
140   // eDontPropagateUserInteraction for aPropagateUserInteraction
141   // unconditionally.
142   return CreateFromExisting(aGlobal, p, eDontPropagateUserInteraction);
143 }
144 
145 // static
All(JSContext * aCx,const nsTArray<RefPtr<Promise>> & aPromiseList,ErrorResult & aRv,PropagateUserInteraction aPropagateUserInteraction)146 already_AddRefed<Promise> Promise::All(
147     JSContext* aCx, const nsTArray<RefPtr<Promise>>& aPromiseList,
148     ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
149   JS::Rooted<JSObject*> globalObj(aCx, JS::CurrentGlobalOrNull(aCx));
150   if (!globalObj) {
151     aRv.Throw(NS_ERROR_UNEXPECTED);
152     return nullptr;
153   }
154 
155   nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(globalObj);
156   if (!global) {
157     aRv.Throw(NS_ERROR_UNEXPECTED);
158     return nullptr;
159   }
160 
161   JS::RootedVector<JSObject*> promises(aCx);
162   if (!promises.reserve(aPromiseList.Length())) {
163     aRv.NoteJSContextException(aCx);
164     return nullptr;
165   }
166 
167   for (auto& promise : aPromiseList) {
168     JS::Rooted<JSObject*> promiseObj(aCx, promise->PromiseObj());
169     // Just in case, make sure these are all in the context compartment.
170     if (!JS_WrapObject(aCx, &promiseObj)) {
171       aRv.NoteJSContextException(aCx);
172       return nullptr;
173     }
174     promises.infallibleAppend(promiseObj);
175   }
176 
177   JS::Rooted<JSObject*> result(aCx, JS::GetWaitForAllPromise(aCx, promises));
178   if (!result) {
179     aRv.NoteJSContextException(aCx);
180     return nullptr;
181   }
182 
183   return CreateFromExisting(global, result, aPropagateUserInteraction);
184 }
185 
Then(JSContext * aCx,JS::Handle<JSObject * > aCalleeGlobal,AnyCallback * aResolveCallback,AnyCallback * aRejectCallback,JS::MutableHandle<JS::Value> aRetval,ErrorResult & aRv)186 void Promise::Then(JSContext* aCx,
187                    // aCalleeGlobal may not be in the compartment of aCx, when
188                    // called over Xrays.
189                    JS::Handle<JSObject*> aCalleeGlobal,
190                    AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
191                    JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
192   NS_ASSERT_OWNINGTHREAD(Promise);
193 
194   // Let's hope this does the right thing with Xrays...  Ensure everything is
195   // just in the caller compartment; that ought to do the trick.  In theory we
196   // should consider aCalleeGlobal, but in practice our only caller is
197   // DOMRequest::Then, which is not working with a Promise subclass, so things
198   // should be OK.
199   JS::Rooted<JSObject*> promise(aCx, PromiseObj());
200   if (!JS_WrapObject(aCx, &promise)) {
201     aRv.NoteJSContextException(aCx);
202     return;
203   }
204 
205   JS::Rooted<JSObject*> resolveCallback(aCx);
206   if (aResolveCallback) {
207     resolveCallback = aResolveCallback->CallbackOrNull();
208     if (!JS_WrapObject(aCx, &resolveCallback)) {
209       aRv.NoteJSContextException(aCx);
210       return;
211     }
212   }
213 
214   JS::Rooted<JSObject*> rejectCallback(aCx);
215   if (aRejectCallback) {
216     rejectCallback = aRejectCallback->CallbackOrNull();
217     if (!JS_WrapObject(aCx, &rejectCallback)) {
218       aRv.NoteJSContextException(aCx);
219       return;
220     }
221   }
222 
223   JS::Rooted<JSObject*> retval(aCx);
224   retval = JS::CallOriginalPromiseThen(aCx, promise, resolveCallback,
225                                        rejectCallback);
226   if (!retval) {
227     aRv.NoteJSContextException(aCx);
228     return;
229   }
230 
231   aRetval.setObject(*retval);
232 }
233 
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)234 void PromiseNativeThenHandlerBase::ResolvedCallback(
235     JSContext* aCx, JS::Handle<JS::Value> aValue) {
236   RefPtr<Promise> promise = CallResolveCallback(aCx, aValue);
237   if (promise) {
238     mPromise->MaybeResolve(promise);
239   } else {
240     mPromise->MaybeResolveWithUndefined();
241   }
242 }
243 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)244 void PromiseNativeThenHandlerBase::RejectedCallback(
245     JSContext* aCx, JS::Handle<JS::Value> aValue) {
246   mPromise->MaybeReject(aValue);
247 }
248 
249 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeThenHandlerBase)
250 
251 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeThenHandlerBase)
252   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
253   tmp->Traverse(cb);
254 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
255 
256 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeThenHandlerBase)
257   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
258   tmp->Unlink();
259 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
260 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase)261 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase)
262   NS_INTERFACE_MAP_ENTRY(nsISupports)
263 NS_INTERFACE_MAP_END
264 
265 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeThenHandlerBase)
266 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeThenHandlerBase)
267 
268 Result<RefPtr<Promise>, nsresult> Promise::ThenWithoutCycleCollection(
269     const std::function<already_AddRefed<Promise>(
270         JSContext* aCx, JS::HandleValue aValue)>& aCallback) {
271   return ThenWithCycleCollectedArgs(aCallback);
272 }
273 
CreateWrapper(ErrorResult & aRv,PropagateUserInteraction aPropagateUserInteraction)274 void Promise::CreateWrapper(
275     ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
276   AutoJSAPI jsapi;
277   if (!jsapi.Init(mGlobal)) {
278     aRv.Throw(NS_ERROR_UNEXPECTED);
279     return;
280   }
281   JSContext* cx = jsapi.cx();
282   mPromiseObj = JS::NewPromiseObject(cx, nullptr);
283   if (!mPromiseObj) {
284     JS_ClearPendingException(cx);
285     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
286     return;
287   }
288   if (aPropagateUserInteraction == ePropagateUserInteraction) {
289     Unused << MaybePropagateUserInputEventHandling();
290   }
291 }
292 
MaybeResolve(JSContext * aCx,JS::Handle<JS::Value> aValue)293 void Promise::MaybeResolve(JSContext* aCx, JS::Handle<JS::Value> aValue) {
294   NS_ASSERT_OWNINGTHREAD(Promise);
295 
296   JS::Rooted<JSObject*> p(aCx, PromiseObj());
297   if (!JS::ResolvePromise(aCx, p, aValue)) {
298     // Now what?  There's nothing sane to do here.
299     JS_ClearPendingException(aCx);
300   }
301 }
302 
MaybeReject(JSContext * aCx,JS::Handle<JS::Value> aValue)303 void Promise::MaybeReject(JSContext* aCx, JS::Handle<JS::Value> aValue) {
304   NS_ASSERT_OWNINGTHREAD(Promise);
305 
306   JS::Rooted<JSObject*> p(aCx, PromiseObj());
307   if (!JS::RejectPromise(aCx, p, aValue)) {
308     // Now what?  There's nothing sane to do here.
309     JS_ClearPendingException(aCx);
310   }
311 }
312 
313 #define SLOT_NATIVEHANDLER 0
314 #define SLOT_NATIVEHANDLER_TASK 1
315 
316 enum class NativeHandlerTask : int32_t { Resolve, Reject };
317 
318 MOZ_CAN_RUN_SCRIPT
NativeHandlerCallback(JSContext * aCx,unsigned aArgc,JS::Value * aVp)319 static bool NativeHandlerCallback(JSContext* aCx, unsigned aArgc,
320                                   JS::Value* aVp) {
321   JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
322 
323   JS::Value v =
324       js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER);
325   MOZ_ASSERT(v.isObject());
326 
327   JS::Rooted<JSObject*> obj(aCx, &v.toObject());
328   PromiseNativeHandler* handler = nullptr;
329   if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &obj, handler))) {
330     return Throw(aCx, NS_ERROR_UNEXPECTED);
331   }
332 
333   v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
334   NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
335 
336   if (task == NativeHandlerTask::Resolve) {
337     // handler is kept alive by "obj" on the stack.
338     MOZ_KnownLive(handler)->ResolvedCallback(aCx, args.get(0));
339   } else {
340     MOZ_ASSERT(task == NativeHandlerTask::Reject);
341     // handler is kept alive by "obj" on the stack.
342     MOZ_KnownLive(handler)->RejectedCallback(aCx, args.get(0));
343   }
344 
345   return true;
346 }
347 
CreateNativeHandlerFunction(JSContext * aCx,JS::Handle<JSObject * > aHolder,NativeHandlerTask aTask)348 static JSObject* CreateNativeHandlerFunction(JSContext* aCx,
349                                              JS::Handle<JSObject*> aHolder,
350                                              NativeHandlerTask aTask) {
351   JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback,
352                                                  /* nargs = */ 1,
353                                                  /* flags = */ 0, nullptr);
354   if (!func) {
355     return nullptr;
356   }
357 
358   JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
359 
360   JS::AssertObjectIsNotGray(aHolder);
361   js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER,
362                                 JS::ObjectValue(*aHolder));
363   js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER_TASK,
364                                 JS::Int32Value(static_cast<int32_t>(aTask)));
365 
366   return obj;
367 }
368 
369 namespace {
370 
371 class PromiseNativeHandlerShim final : public PromiseNativeHandler {
372   RefPtr<PromiseNativeHandler> mInner;
373 
374   ~PromiseNativeHandlerShim() = default;
375 
376  public:
PromiseNativeHandlerShim(PromiseNativeHandler * aInner)377   explicit PromiseNativeHandlerShim(PromiseNativeHandler* aInner)
378       : mInner(aInner) {
379     MOZ_ASSERT(mInner);
380   }
381 
382   MOZ_CAN_RUN_SCRIPT
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)383   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
384     RefPtr<PromiseNativeHandler> inner = std::move(mInner);
385     inner->ResolvedCallback(aCx, aValue);
386     MOZ_ASSERT(!mInner);
387   }
388 
389   MOZ_CAN_RUN_SCRIPT
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)390   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
391     RefPtr<PromiseNativeHandler> inner = std::move(mInner);
392     inner->RejectedCallback(aCx, aValue);
393     MOZ_ASSERT(!mInner);
394   }
395 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto,JS::MutableHandle<JSObject * > aWrapper)396   bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
397                   JS::MutableHandle<JSObject*> aWrapper) {
398     return PromiseNativeHandler_Binding::Wrap(aCx, this, aGivenProto, aWrapper);
399   }
400 
401   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
402   NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim)
403 };
404 
405 NS_IMPL_CYCLE_COLLECTION(PromiseNativeHandlerShim, mInner)
406 
407 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeHandlerShim)
408 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeHandlerShim)
409 
410 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeHandlerShim)
411   NS_INTERFACE_MAP_ENTRY(nsISupports)
412 NS_INTERFACE_MAP_END
413 
414 }  // anonymous namespace
415 
AppendNativeHandler(PromiseNativeHandler * aRunnable)416 void Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable) {
417   NS_ASSERT_OWNINGTHREAD(Promise);
418 
419   AutoJSAPI jsapi;
420   if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
421     // Our API doesn't allow us to return a useful error.  Not like this should
422     // happen anyway.
423     return;
424   }
425 
426   // The self-hosted promise js may keep the object we pass to it alive
427   // for quite a while depending on when GC runs.  Therefore, pass a shim
428   // object instead.  The shim will free its inner PromiseNativeHandler
429   // after the promise has settled just like our previous c++ promises did.
430   RefPtr<PromiseNativeHandlerShim> shim =
431       new PromiseNativeHandlerShim(aRunnable);
432 
433   JSContext* cx = jsapi.cx();
434   JS::Rooted<JSObject*> handlerWrapper(cx);
435   // Note: PromiseNativeHandler is NOT wrappercached.  So we can't use
436   // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
437   if (NS_WARN_IF(!shim->WrapObject(cx, nullptr, &handlerWrapper))) {
438     // Again, no way to report errors.
439     jsapi.ClearException();
440     return;
441   }
442 
443   JS::Rooted<JSObject*> resolveFunc(cx);
444   resolveFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
445                                             NativeHandlerTask::Resolve);
446   if (NS_WARN_IF(!resolveFunc)) {
447     jsapi.ClearException();
448     return;
449   }
450 
451   JS::Rooted<JSObject*> rejectFunc(cx);
452   rejectFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
453                                            NativeHandlerTask::Reject);
454   if (NS_WARN_IF(!rejectFunc)) {
455     jsapi.ClearException();
456     return;
457   }
458 
459   JS::Rooted<JSObject*> promiseObj(cx, PromiseObj());
460   if (NS_WARN_IF(
461           !JS::AddPromiseReactions(cx, promiseObj, resolveFunc, rejectFunc))) {
462     jsapi.ClearException();
463     return;
464   }
465 }
466 
HandleException(JSContext * aCx)467 void Promise::HandleException(JSContext* aCx) {
468   JS::Rooted<JS::Value> exn(aCx);
469   if (JS_GetPendingException(aCx, &exn)) {
470     JS_ClearPendingException(aCx);
471     // Always reject even if this was called in *Resolve.
472     MaybeReject(aCx, exn);
473   }
474 }
475 
476 // static
CreateFromExisting(nsIGlobalObject * aGlobal,JS::Handle<JSObject * > aPromiseObj,PropagateUserInteraction aPropagateUserInteraction)477 already_AddRefed<Promise> Promise::CreateFromExisting(
478     nsIGlobalObject* aGlobal, JS::Handle<JSObject*> aPromiseObj,
479     PropagateUserInteraction aPropagateUserInteraction) {
480   MOZ_ASSERT(JS::GetCompartment(aGlobal->GetGlobalJSObjectPreserveColor()) ==
481              JS::GetCompartment(aPromiseObj));
482   RefPtr<Promise> p = new Promise(aGlobal);
483   p->mPromiseObj = aPromiseObj;
484   if (aPropagateUserInteraction == ePropagateUserInteraction &&
485       !p->MaybePropagateUserInputEventHandling()) {
486     return nullptr;
487   }
488   return p.forget();
489 }
490 
MaybeResolveWithUndefined()491 void Promise::MaybeResolveWithUndefined() {
492   NS_ASSERT_OWNINGTHREAD(Promise);
493 
494   MaybeResolve(JS::UndefinedHandleValue);
495 }
496 
MaybeReject(const RefPtr<MediaStreamError> & aArg)497 void Promise::MaybeReject(const RefPtr<MediaStreamError>& aArg) {
498   NS_ASSERT_OWNINGTHREAD(Promise);
499 
500   MaybeSomething(aArg, &Promise::MaybeReject);
501 }
502 
MaybeRejectWithUndefined()503 void Promise::MaybeRejectWithUndefined() {
504   NS_ASSERT_OWNINGTHREAD(Promise);
505 
506   MaybeSomething(JS::UndefinedHandleValue, &Promise::MaybeReject);
507 }
508 
ReportRejectedPromise(JSContext * aCx,JS::HandleObject aPromise)509 void Promise::ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise) {
510   MOZ_ASSERT(!js::IsWrapper(aPromise));
511 
512   MOZ_ASSERT(JS::GetPromiseState(aPromise) == JS::PromiseState::Rejected);
513 
514   bool isChrome = false;
515   uint64_t innerWindowID = 0;
516   nsGlobalWindowInner* winForDispatch = nullptr;
517   if (MOZ_LIKELY(NS_IsMainThread())) {
518     isChrome = nsContentUtils::ObjectPrincipal(aPromise)->IsSystemPrincipal();
519 
520     if (nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aPromise)) {
521       winForDispatch = win;
522       innerWindowID = win->WindowID();
523     } else if (nsGlobalWindowInner* win = xpc::SandboxWindowOrNull(
524                    JS::GetNonCCWObjectGlobal(aPromise), aCx)) {
525       // Don't dispatch rejections from the sandbox to the associated DOM
526       // window.
527       innerWindowID = win->WindowID();
528     }
529   } else if (const WorkerPrivate* wp = GetCurrentThreadWorkerPrivate()) {
530     isChrome = wp->UsesSystemPrincipal();
531     innerWindowID = wp->WindowID();
532   } else if (nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(aPromise)) {
533     if (nsCOMPtr<WorkletGlobalScope> workletGlobal =
534             do_QueryInterface(global)) {
535       WorkletImpl* impl = workletGlobal->Impl();
536       isChrome = impl->PrincipalInfo().type() ==
537                  mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo;
538       innerWindowID = impl->LoadInfo().InnerWindowID();
539     }
540   }
541 
542   JS::Rooted<JS::Value> result(aCx, JS::GetPromiseResult(aPromise));
543   // resolutionSite can be null if async stacks are disabled.
544   JS::Rooted<JSObject*> resolutionSite(aCx,
545                                        JS::GetPromiseResolutionSite(aPromise));
546 
547   // We're inspecting the rejection value only to report it to the console, and
548   // we do so without side-effects, so we can safely unwrap it without regard to
549   // the privileges of the Promise object that holds it. If we don't unwrap
550   // before trying to create the error report, we wind up reporting any
551   // cross-origin objects as "uncaught exception: Object".
552   RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
553   {
554     Maybe<JSAutoRealm> ar;
555     JS::Rooted<JS::Value> unwrapped(aCx, result);
556     if (unwrapped.isObject()) {
557       unwrapped.setObject(*js::UncheckedUnwrap(&unwrapped.toObject()));
558       ar.emplace(aCx, &unwrapped.toObject());
559     }
560 
561     JS::ErrorReportBuilder report(aCx);
562     RefPtr<Exception> exn;
563     if (unwrapped.isObject() &&
564         (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, &unwrapped, exn)) ||
565          NS_SUCCEEDED(UNWRAP_OBJECT(Exception, &unwrapped, exn)))) {
566       xpcReport->Init(aCx, exn, isChrome, innerWindowID);
567     } else {
568       // Use the resolution site as the exception stack
569       JS::ExceptionStack exnStack(aCx, unwrapped, resolutionSite);
570       if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::NoSideEffects)) {
571         JS_ClearPendingException(aCx);
572         return;
573       }
574 
575       xpcReport->Init(report.report(), report.toStringResult().c_str(),
576                       isChrome, innerWindowID);
577     }
578   }
579 
580   // Used to initialize the similarly named nsISciptError attribute.
581   xpcReport->mIsPromiseRejection = true;
582 
583   // Now post an event to do the real reporting async
584   RefPtr<AsyncErrorReporter> event = new AsyncErrorReporter(xpcReport);
585   if (winForDispatch) {
586     if (!winForDispatch->IsDying()) {
587       // Exceptions from a dying window will cause the window to leak.
588       event->SetException(aCx, result);
589       if (resolutionSite) {
590         event->SerializeStack(aCx, resolutionSite);
591       }
592     }
593     winForDispatch->Dispatch(mozilla::TaskCategory::Other, event.forget());
594   } else {
595     NS_DispatchToMainThread(event);
596   }
597 }
598 
MaybeResolveWithClone(JSContext * aCx,JS::Handle<JS::Value> aValue)599 void Promise::MaybeResolveWithClone(JSContext* aCx,
600                                     JS::Handle<JS::Value> aValue) {
601   JS::Rooted<JSObject*> sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
602   AutoEntryScript aes(GetParentObject(), "Promise resolution");
603   JSContext* cx = aes.cx();
604   JS::Rooted<JS::Value> value(cx, aValue);
605 
606   xpc::StackScopedCloneOptions options;
607   options.wrapReflectors = true;
608   if (!StackScopedClone(cx, options, sourceScope, &value)) {
609     HandleException(cx);
610     return;
611   }
612   MaybeResolve(aCx, value);
613 }
614 
MaybeRejectWithClone(JSContext * aCx,JS::Handle<JS::Value> aValue)615 void Promise::MaybeRejectWithClone(JSContext* aCx,
616                                    JS::Handle<JS::Value> aValue) {
617   JS::Rooted<JSObject*> sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
618   AutoEntryScript aes(GetParentObject(), "Promise rejection");
619   JSContext* cx = aes.cx();
620   JS::Rooted<JS::Value> value(cx, aValue);
621 
622   xpc::StackScopedCloneOptions options;
623   options.wrapReflectors = true;
624   if (!StackScopedClone(cx, options, sourceScope, &value)) {
625     HandleException(cx);
626     return;
627   }
628   MaybeReject(aCx, value);
629 }
630 
631 // A WorkerRunnable to resolve/reject the Promise on the worker thread.
632 // Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
633 class PromiseWorkerProxyRunnable : public WorkerRunnable {
634  public:
PromiseWorkerProxyRunnable(PromiseWorkerProxy * aPromiseWorkerProxy,PromiseWorkerProxy::RunCallbackFunc aFunc)635   PromiseWorkerProxyRunnable(PromiseWorkerProxy* aPromiseWorkerProxy,
636                              PromiseWorkerProxy::RunCallbackFunc aFunc)
637       : WorkerRunnable(aPromiseWorkerProxy->GetWorkerPrivate(),
638                        WorkerThreadUnchangedBusyCount),
639         mPromiseWorkerProxy(aPromiseWorkerProxy),
640         mFunc(aFunc) {
641     MOZ_ASSERT(NS_IsMainThread());
642     MOZ_ASSERT(mPromiseWorkerProxy);
643   }
644 
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)645   virtual bool WorkerRun(JSContext* aCx,
646                          WorkerPrivate* aWorkerPrivate) override {
647     MOZ_ASSERT(aWorkerPrivate);
648     aWorkerPrivate->AssertIsOnWorkerThread();
649     MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
650 
651     MOZ_ASSERT(mPromiseWorkerProxy);
652     RefPtr<Promise> workerPromise = mPromiseWorkerProxy->WorkerPromise();
653 
654     // Here we convert the buffer to a JS::Value.
655     JS::Rooted<JS::Value> value(aCx);
656     if (!mPromiseWorkerProxy->Read(aCx, &value)) {
657       JS_ClearPendingException(aCx);
658       return false;
659     }
660 
661     (workerPromise->*mFunc)(aCx, value);
662 
663     // Release the Promise because it has been resolved/rejected for sure.
664     mPromiseWorkerProxy->CleanUp();
665     return true;
666   }
667 
668  protected:
669   ~PromiseWorkerProxyRunnable() = default;
670 
671  private:
672   RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
673 
674   // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
675   PromiseWorkerProxy::RunCallbackFunc mFunc;
676 };
677 
678 /* static */
Create(WorkerPrivate * aWorkerPrivate,Promise * aWorkerPromise,const PromiseWorkerProxyStructuredCloneCallbacks * aCb)679 already_AddRefed<PromiseWorkerProxy> PromiseWorkerProxy::Create(
680     WorkerPrivate* aWorkerPrivate, Promise* aWorkerPromise,
681     const PromiseWorkerProxyStructuredCloneCallbacks* aCb) {
682   MOZ_ASSERT(aWorkerPrivate);
683   aWorkerPrivate->AssertIsOnWorkerThread();
684   MOZ_ASSERT(aWorkerPromise);
685   MOZ_ASSERT_IF(aCb, !!aCb->Write && !!aCb->Read);
686 
687   RefPtr<PromiseWorkerProxy> proxy =
688       new PromiseWorkerProxy(aWorkerPromise, aCb);
689 
690   // We do this to make sure the worker thread won't shut down before the
691   // promise is resolved/rejected on the worker thread.
692   RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
693       aWorkerPrivate, "PromiseWorkerProxy", [proxy]() { proxy->CleanUp(); });
694 
695   if (NS_WARN_IF(!workerRef)) {
696     // Probably the worker is terminating. We cannot complete the operation
697     // and we have to release all the resources.
698     proxy->CleanProperties();
699     return nullptr;
700   }
701 
702   proxy->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
703 
704   // Maintain a reference so that we have a valid object to clean up when
705   // removing the feature.
706   proxy.get()->AddRef();
707 
708   return proxy.forget();
709 }
710 
NS_IMPL_ISUPPORTS0(PromiseWorkerProxy)711 NS_IMPL_ISUPPORTS0(PromiseWorkerProxy)
712 
713 PromiseWorkerProxy::PromiseWorkerProxy(
714     Promise* aWorkerPromise,
715     const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks)
716     : mWorkerPromise(aWorkerPromise),
717       mCleanedUp(false),
718       mCallbacks(aCallbacks),
719       mCleanUpLock("cleanUpLock") {}
720 
~PromiseWorkerProxy()721 PromiseWorkerProxy::~PromiseWorkerProxy() {
722   MOZ_ASSERT(mCleanedUp);
723   MOZ_ASSERT(!mWorkerPromise);
724   MOZ_ASSERT(!mWorkerRef);
725 }
726 
CleanProperties()727 void PromiseWorkerProxy::CleanProperties() {
728   MOZ_ASSERT(IsCurrentThreadRunningWorker());
729 
730   // Ok to do this unprotected from Create().
731   // CleanUp() holds the lock before calling this.
732   mCleanedUp = true;
733   mWorkerPromise = nullptr;
734   mWorkerRef = nullptr;
735 
736   // Clear the StructuredCloneHolderBase class.
737   Clear();
738 }
739 
GetWorkerPrivate() const740 WorkerPrivate* PromiseWorkerProxy::GetWorkerPrivate() const {
741 #ifdef DEBUG
742   if (NS_IsMainThread()) {
743     mCleanUpLock.AssertCurrentThreadOwns();
744   }
745 #endif
746   // Safe to check this without a lock since we assert lock ownership on the
747   // main thread above.
748   MOZ_ASSERT(!mCleanedUp);
749   MOZ_ASSERT(mWorkerRef);
750 
751   return mWorkerRef->Private();
752 }
753 
WorkerPromise() const754 Promise* PromiseWorkerProxy::WorkerPromise() const {
755   MOZ_ASSERT(IsCurrentThreadRunningWorker());
756   MOZ_ASSERT(mWorkerPromise);
757   return mWorkerPromise;
758 }
759 
RunCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,RunCallbackFunc aFunc)760 void PromiseWorkerProxy::RunCallback(JSContext* aCx,
761                                      JS::Handle<JS::Value> aValue,
762                                      RunCallbackFunc aFunc) {
763   MOZ_ASSERT(NS_IsMainThread());
764 
765   MutexAutoLock lock(Lock());
766   // If the worker thread's been cancelled we don't need to resolve the Promise.
767   if (CleanedUp()) {
768     return;
769   }
770 
771   // The |aValue| is written into the StructuredCloneHolderBase.
772   if (!Write(aCx, aValue)) {
773     JS_ClearPendingException(aCx);
774     MOZ_ASSERT(false,
775                "cannot serialize the value with the StructuredCloneAlgorithm!");
776   }
777 
778   RefPtr<PromiseWorkerProxyRunnable> runnable =
779       new PromiseWorkerProxyRunnable(this, aFunc);
780 
781   runnable->Dispatch();
782 }
783 
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)784 void PromiseWorkerProxy::ResolvedCallback(JSContext* aCx,
785                                           JS::Handle<JS::Value> aValue) {
786   RunCallback(aCx, aValue, &Promise::MaybeResolve);
787 }
788 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)789 void PromiseWorkerProxy::RejectedCallback(JSContext* aCx,
790                                           JS::Handle<JS::Value> aValue) {
791   RunCallback(aCx, aValue, &Promise::MaybeReject);
792 }
793 
CleanUp()794 void PromiseWorkerProxy::CleanUp() {
795   // Can't release Mutex while it is still locked, so scope the lock.
796   {
797     MutexAutoLock lock(Lock());
798 
799     if (CleanedUp()) {
800       return;
801     }
802 
803     MOZ_ASSERT(mWorkerRef);
804     mWorkerRef->Private()->AssertIsOnWorkerThread();
805 
806     // Release the Promise and remove the PromiseWorkerProxy from the holders of
807     // the worker thread since the Promise has been resolved/rejected or the
808     // worker thread has been cancelled.
809     CleanProperties();
810   }
811   Release();
812 }
813 
CustomReadHandler(JSContext * aCx,JSStructuredCloneReader * aReader,const JS::CloneDataPolicy & aCloneDataPolicy,uint32_t aTag,uint32_t aIndex)814 JSObject* PromiseWorkerProxy::CustomReadHandler(
815     JSContext* aCx, JSStructuredCloneReader* aReader,
816     const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag,
817     uint32_t aIndex) {
818   if (NS_WARN_IF(!mCallbacks)) {
819     return nullptr;
820   }
821 
822   return mCallbacks->Read(aCx, aReader, this, aTag, aIndex);
823 }
824 
CustomWriteHandler(JSContext * aCx,JSStructuredCloneWriter * aWriter,JS::Handle<JSObject * > aObj,bool * aSameProcessScopeRequired)825 bool PromiseWorkerProxy::CustomWriteHandler(JSContext* aCx,
826                                             JSStructuredCloneWriter* aWriter,
827                                             JS::Handle<JSObject*> aObj,
828                                             bool* aSameProcessScopeRequired) {
829   if (NS_WARN_IF(!mCallbacks)) {
830     return false;
831   }
832 
833   return mCallbacks->Write(aCx, aWriter, this, aObj);
834 }
835 
836 // Specializations of MaybeRejectBrokenly we actually support.
837 template <>
MaybeRejectBrokenly(const RefPtr<DOMException> & aArg)838 void Promise::MaybeRejectBrokenly(const RefPtr<DOMException>& aArg) {
839   MaybeSomething(aArg, &Promise::MaybeReject);
840 }
841 template <>
MaybeRejectBrokenly(const nsAString & aArg)842 void Promise::MaybeRejectBrokenly(const nsAString& aArg) {
843   MaybeSomething(aArg, &Promise::MaybeReject);
844 }
845 
State() const846 Promise::PromiseState Promise::State() const {
847   JS::Rooted<JSObject*> p(RootingCx(), PromiseObj());
848   const JS::PromiseState state = JS::GetPromiseState(p);
849 
850   if (state == JS::PromiseState::Fulfilled) {
851     return PromiseState::Resolved;
852   }
853 
854   if (state == JS::PromiseState::Rejected) {
855     return PromiseState::Rejected;
856   }
857 
858   return PromiseState::Pending;
859 }
860 
SetSettledPromiseIsHandled()861 void Promise::SetSettledPromiseIsHandled() {
862   AutoAllowLegacyScriptExecution exemption;
863   AutoEntryScript aes(mGlobal, "Set settled promise handled");
864   JSContext* cx = aes.cx();
865   JS::RootedObject promiseObj(cx, mPromiseObj);
866   JS::SetSettledPromiseIsHandled(cx, promiseObj);
867 }
868 
869 }  // namespace dom
870 }  // namespace mozilla
871 
872 extern "C" {
873 
874 // These functions are used in the implementation of ffi bindings for
875 // dom::Promise from Rust.
876 
DomPromise_AddRef(mozilla::dom::Promise * aPromise)877 void DomPromise_AddRef(mozilla::dom::Promise* aPromise) {
878   MOZ_ASSERT(aPromise);
879   aPromise->AddRef();
880 }
881 
DomPromise_Release(mozilla::dom::Promise * aPromise)882 void DomPromise_Release(mozilla::dom::Promise* aPromise) {
883   MOZ_ASSERT(aPromise);
884   aPromise->Release();
885 }
886 
887 #define DOM_PROMISE_FUNC_WITH_VARIANT(name, func)                         \
888   void name(mozilla::dom::Promise* aPromise, nsIVariant* aVariant) {      \
889     MOZ_ASSERT(aPromise);                                                 \
890     MOZ_ASSERT(aVariant);                                                 \
891     mozilla::dom::AutoEntryScript aes(aPromise->GetGlobalObject(),        \
892                                       "Promise resolution or rejection"); \
893     JSContext* cx = aes.cx();                                             \
894                                                                           \
895     JS::Rooted<JS::Value> val(cx);                                        \
896     nsresult rv = NS_OK;                                                  \
897     if (!XPCVariant::VariantDataToJS(cx, aVariant, &rv, &val)) {          \
898       aPromise->MaybeRejectWithTypeError(                                 \
899           "Failed to convert nsIVariant to JS");                          \
900       return;                                                             \
901     }                                                                     \
902     aPromise->func(val);                                                  \
903   }
904 
905 DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_RejectWithVariant, MaybeReject)
906 DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_ResolveWithVariant, MaybeResolve)
907 
908 #undef DOM_PROMISE_FUNC_WITH_VARIANT
909 }
910