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