Coverage Report

Created: 2018-09-25 14:53

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