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