|
1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ |
|
2 /* vim: set ts=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 "jsfriendapi.h" |
|
10 #include "mozilla/dom/OwningNonNull.h" |
|
11 #include "mozilla/dom/PromiseBinding.h" |
|
12 #include "mozilla/CycleCollectedJSRuntime.h" |
|
13 #include "mozilla/Preferences.h" |
|
14 #include "PromiseCallback.h" |
|
15 #include "PromiseNativeHandler.h" |
|
16 #include "nsContentUtils.h" |
|
17 #include "WorkerPrivate.h" |
|
18 #include "WorkerRunnable.h" |
|
19 #include "nsJSPrincipals.h" |
|
20 #include "nsJSUtils.h" |
|
21 #include "nsPIDOMWindow.h" |
|
22 #include "nsJSEnvironment.h" |
|
23 |
|
24 namespace mozilla { |
|
25 namespace dom { |
|
26 |
|
27 using namespace workers; |
|
28 |
|
29 NS_IMPL_ISUPPORTS0(PromiseNativeHandler) |
|
30 |
|
31 // PromiseTask |
|
32 |
|
33 // This class processes the promise's callbacks with promise's result. |
|
34 class PromiseTask MOZ_FINAL : public nsRunnable |
|
35 { |
|
36 public: |
|
37 PromiseTask(Promise* aPromise) |
|
38 : mPromise(aPromise) |
|
39 { |
|
40 MOZ_ASSERT(aPromise); |
|
41 MOZ_COUNT_CTOR(PromiseTask); |
|
42 } |
|
43 |
|
44 ~PromiseTask() |
|
45 { |
|
46 MOZ_COUNT_DTOR(PromiseTask); |
|
47 } |
|
48 |
|
49 NS_IMETHOD Run() |
|
50 { |
|
51 mPromise->mTaskPending = false; |
|
52 mPromise->RunTask(); |
|
53 return NS_OK; |
|
54 } |
|
55 |
|
56 private: |
|
57 nsRefPtr<Promise> mPromise; |
|
58 }; |
|
59 |
|
60 class WorkerPromiseTask MOZ_FINAL : public WorkerSameThreadRunnable |
|
61 { |
|
62 public: |
|
63 WorkerPromiseTask(WorkerPrivate* aWorkerPrivate, Promise* aPromise) |
|
64 : WorkerSameThreadRunnable(aWorkerPrivate) |
|
65 , mPromise(aPromise) |
|
66 { |
|
67 MOZ_ASSERT(aPromise); |
|
68 MOZ_COUNT_CTOR(WorkerPromiseTask); |
|
69 } |
|
70 |
|
71 ~WorkerPromiseTask() |
|
72 { |
|
73 MOZ_COUNT_DTOR(WorkerPromiseTask); |
|
74 } |
|
75 |
|
76 bool |
|
77 WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) |
|
78 { |
|
79 mPromise->mTaskPending = false; |
|
80 mPromise->RunTask(); |
|
81 return true; |
|
82 } |
|
83 |
|
84 private: |
|
85 nsRefPtr<Promise> mPromise; |
|
86 }; |
|
87 |
|
88 class PromiseResolverMixin |
|
89 { |
|
90 public: |
|
91 PromiseResolverMixin(Promise* aPromise, |
|
92 JS::Handle<JS::Value> aValue, |
|
93 Promise::PromiseState aState) |
|
94 : mPromise(aPromise) |
|
95 , mValue(CycleCollectedJSRuntime::Get()->Runtime(), aValue) |
|
96 , mState(aState) |
|
97 { |
|
98 MOZ_ASSERT(aPromise); |
|
99 MOZ_ASSERT(mState != Promise::Pending); |
|
100 MOZ_COUNT_CTOR(PromiseResolverMixin); |
|
101 } |
|
102 |
|
103 virtual ~PromiseResolverMixin() |
|
104 { |
|
105 NS_ASSERT_OWNINGTHREAD(PromiseResolverMixin); |
|
106 MOZ_COUNT_DTOR(PromiseResolverMixin); |
|
107 } |
|
108 |
|
109 protected: |
|
110 void |
|
111 RunInternal() |
|
112 { |
|
113 NS_ASSERT_OWNINGTHREAD(PromiseResolverMixin); |
|
114 mPromise->RunResolveTask( |
|
115 JS::Handle<JS::Value>::fromMarkedLocation(mValue.address()), |
|
116 mState, Promise::SyncTask); |
|
117 } |
|
118 |
|
119 private: |
|
120 nsRefPtr<Promise> mPromise; |
|
121 JS::PersistentRooted<JS::Value> mValue; |
|
122 Promise::PromiseState mState; |
|
123 NS_DECL_OWNINGTHREAD; |
|
124 }; |
|
125 |
|
126 // This class processes the promise's callbacks with promise's result. |
|
127 class PromiseResolverTask MOZ_FINAL : public nsRunnable, |
|
128 public PromiseResolverMixin |
|
129 { |
|
130 public: |
|
131 PromiseResolverTask(Promise* aPromise, |
|
132 JS::Handle<JS::Value> aValue, |
|
133 Promise::PromiseState aState) |
|
134 : PromiseResolverMixin(aPromise, aValue, aState) |
|
135 {} |
|
136 |
|
137 ~PromiseResolverTask() |
|
138 {} |
|
139 |
|
140 NS_IMETHOD Run() |
|
141 { |
|
142 RunInternal(); |
|
143 return NS_OK; |
|
144 } |
|
145 }; |
|
146 |
|
147 class WorkerPromiseResolverTask MOZ_FINAL : public WorkerSameThreadRunnable, |
|
148 public PromiseResolverMixin |
|
149 { |
|
150 public: |
|
151 WorkerPromiseResolverTask(WorkerPrivate* aWorkerPrivate, |
|
152 Promise* aPromise, |
|
153 JS::Handle<JS::Value> aValue, |
|
154 Promise::PromiseState aState) |
|
155 : WorkerSameThreadRunnable(aWorkerPrivate), |
|
156 PromiseResolverMixin(aPromise, aValue, aState) |
|
157 {} |
|
158 |
|
159 ~WorkerPromiseResolverTask() |
|
160 {} |
|
161 |
|
162 bool |
|
163 WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) |
|
164 { |
|
165 RunInternal(); |
|
166 return true; |
|
167 } |
|
168 }; |
|
169 |
|
170 // Promise |
|
171 |
|
172 NS_IMPL_CYCLE_COLLECTION_CLASS(Promise) |
|
173 |
|
174 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise) |
|
175 tmp->MaybeReportRejectedOnce(); |
|
176 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) |
|
177 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveCallbacks) |
|
178 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectCallbacks) |
|
179 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER |
|
180 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
181 |
|
182 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise) |
|
183 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) |
|
184 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolveCallbacks) |
|
185 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRejectCallbacks) |
|
186 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
187 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
188 |
|
189 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise) |
|
190 NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mResult) |
|
191 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER |
|
192 NS_IMPL_CYCLE_COLLECTION_TRACE_END |
|
193 |
|
194 NS_IMPL_CYCLE_COLLECTING_ADDREF(Promise) |
|
195 NS_IMPL_CYCLE_COLLECTING_RELEASE(Promise) |
|
196 |
|
197 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Promise) |
|
198 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
|
199 NS_INTERFACE_MAP_ENTRY(nsISupports) |
|
200 NS_INTERFACE_MAP_END |
|
201 |
|
202 Promise::Promise(nsIGlobalObject* aGlobal) |
|
203 : mGlobal(aGlobal) |
|
204 , mResult(JS::UndefinedValue()) |
|
205 , mState(Pending) |
|
206 , mTaskPending(false) |
|
207 , mHadRejectCallback(false) |
|
208 , mResolvePending(false) |
|
209 { |
|
210 MOZ_ASSERT(mGlobal); |
|
211 |
|
212 mozilla::HoldJSObjects(this); |
|
213 SetIsDOMBinding(); |
|
214 } |
|
215 |
|
216 Promise::~Promise() |
|
217 { |
|
218 MaybeReportRejectedOnce(); |
|
219 mozilla::DropJSObjects(this); |
|
220 } |
|
221 |
|
222 JSObject* |
|
223 Promise::WrapObject(JSContext* aCx) |
|
224 { |
|
225 return PromiseBinding::Wrap(aCx, this); |
|
226 } |
|
227 |
|
228 JSObject* |
|
229 Promise::GetOrCreateWrapper(JSContext* aCx) |
|
230 { |
|
231 if (JSObject* wrapper = GetWrapper()) { |
|
232 return wrapper; |
|
233 } |
|
234 |
|
235 nsIGlobalObject* global = GetParentObject(); |
|
236 MOZ_ASSERT(global); |
|
237 |
|
238 JS::Rooted<JSObject*> scope(aCx, global->GetGlobalJSObject()); |
|
239 if (!scope) { |
|
240 JS_ReportError(aCx, "can't get scope"); |
|
241 return nullptr; |
|
242 } |
|
243 |
|
244 JSAutoCompartment ac(aCx, scope); |
|
245 |
|
246 JS::Rooted<JS::Value> val(aCx); |
|
247 if (!WrapNewBindingObject(aCx, this, &val)) { |
|
248 MOZ_ASSERT(JS_IsExceptionPending(aCx)); |
|
249 return nullptr; |
|
250 } |
|
251 |
|
252 return GetWrapper(); |
|
253 } |
|
254 |
|
255 void |
|
256 Promise::MaybeResolve(JSContext* aCx, |
|
257 JS::Handle<JS::Value> aValue) |
|
258 { |
|
259 MaybeResolveInternal(aCx, aValue); |
|
260 } |
|
261 |
|
262 void |
|
263 Promise::MaybeReject(JSContext* aCx, |
|
264 JS::Handle<JS::Value> aValue) |
|
265 { |
|
266 MaybeRejectInternal(aCx, aValue); |
|
267 } |
|
268 |
|
269 enum { |
|
270 SLOT_PROMISE = 0, |
|
271 SLOT_DATA |
|
272 }; |
|
273 |
|
274 /* static */ bool |
|
275 Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp) |
|
276 { |
|
277 JS::CallArgs args = CallArgsFromVp(aArgc, aVp); |
|
278 |
|
279 JS::Rooted<JS::Value> v(aCx, |
|
280 js::GetFunctionNativeReserved(&args.callee(), |
|
281 SLOT_PROMISE)); |
|
282 MOZ_ASSERT(v.isObject()); |
|
283 |
|
284 Promise* promise; |
|
285 if (NS_FAILED(UNWRAP_OBJECT(Promise, &v.toObject(), promise))) { |
|
286 return Throw(aCx, NS_ERROR_UNEXPECTED); |
|
287 } |
|
288 |
|
289 v = js::GetFunctionNativeReserved(&args.callee(), SLOT_DATA); |
|
290 PromiseCallback::Task task = static_cast<PromiseCallback::Task>(v.toInt32()); |
|
291 |
|
292 if (task == PromiseCallback::Resolve) { |
|
293 promise->MaybeResolveInternal(aCx, args.get(0)); |
|
294 } else { |
|
295 promise->MaybeRejectInternal(aCx, args.get(0)); |
|
296 } |
|
297 |
|
298 return true; |
|
299 } |
|
300 |
|
301 /* |
|
302 * Utilities for thenable callbacks. |
|
303 * |
|
304 * A thenable is a { then: function(resolve, reject) { } }. |
|
305 * `then` is called with a resolve and reject callback pair. |
|
306 * Since only one of these should be called at most once (first call wins), the |
|
307 * two keep a reference to each other in SLOT_DATA. When either of them is |
|
308 * called, the references are cleared. Further calls are ignored. |
|
309 */ |
|
310 namespace { |
|
311 void |
|
312 LinkThenableCallables(JSContext* aCx, JS::Handle<JSObject*> aResolveFunc, |
|
313 JS::Handle<JSObject*> aRejectFunc) |
|
314 { |
|
315 js::SetFunctionNativeReserved(aResolveFunc, SLOT_DATA, |
|
316 JS::ObjectValue(*aRejectFunc)); |
|
317 js::SetFunctionNativeReserved(aRejectFunc, SLOT_DATA, |
|
318 JS::ObjectValue(*aResolveFunc)); |
|
319 } |
|
320 |
|
321 /* |
|
322 * Returns false if callback was already called before, otherwise breaks the |
|
323 * links and returns true. |
|
324 */ |
|
325 bool |
|
326 MarkAsCalledIfNotCalledBefore(JSContext* aCx, JS::Handle<JSObject*> aFunc) |
|
327 { |
|
328 JS::Value otherFuncVal = js::GetFunctionNativeReserved(aFunc, SLOT_DATA); |
|
329 |
|
330 if (!otherFuncVal.isObject()) { |
|
331 return false; |
|
332 } |
|
333 |
|
334 JSObject* otherFuncObj = &otherFuncVal.toObject(); |
|
335 MOZ_ASSERT(js::GetFunctionNativeReserved(otherFuncObj, SLOT_DATA).isObject()); |
|
336 |
|
337 // Break both references. |
|
338 js::SetFunctionNativeReserved(aFunc, SLOT_DATA, JS::UndefinedValue()); |
|
339 js::SetFunctionNativeReserved(otherFuncObj, SLOT_DATA, JS::UndefinedValue()); |
|
340 |
|
341 return true; |
|
342 } |
|
343 |
|
344 Promise* |
|
345 GetPromise(JSContext* aCx, JS::Handle<JSObject*> aFunc) |
|
346 { |
|
347 JS::Value promiseVal = js::GetFunctionNativeReserved(aFunc, SLOT_PROMISE); |
|
348 |
|
349 MOZ_ASSERT(promiseVal.isObject()); |
|
350 |
|
351 Promise* promise; |
|
352 UNWRAP_OBJECT(Promise, &promiseVal.toObject(), promise); |
|
353 return promise; |
|
354 } |
|
355 }; |
|
356 |
|
357 /* |
|
358 * Common bits of (JSCallbackThenableResolver/JSCallbackThenableRejecter). |
|
359 * Resolves/rejects the Promise if it is ok to do so, based on whether either of |
|
360 * the callbacks have been called before or not. |
|
361 */ |
|
362 /* static */ bool |
|
363 Promise::ThenableResolverCommon(JSContext* aCx, uint32_t aTask, |
|
364 unsigned aArgc, JS::Value* aVp) |
|
365 { |
|
366 JS::CallArgs args = CallArgsFromVp(aArgc, aVp); |
|
367 JS::Rooted<JSObject*> thisFunc(aCx, &args.callee()); |
|
368 if (!MarkAsCalledIfNotCalledBefore(aCx, thisFunc)) { |
|
369 // A function from this pair has been called before. |
|
370 return true; |
|
371 } |
|
372 |
|
373 Promise* promise = GetPromise(aCx, thisFunc); |
|
374 MOZ_ASSERT(promise); |
|
375 |
|
376 if (aTask == PromiseCallback::Resolve) { |
|
377 promise->ResolveInternal(aCx, args.get(0), SyncTask); |
|
378 } else { |
|
379 promise->RejectInternal(aCx, args.get(0), SyncTask); |
|
380 } |
|
381 return true; |
|
382 } |
|
383 |
|
384 /* static */ bool |
|
385 Promise::JSCallbackThenableResolver(JSContext* aCx, |
|
386 unsigned aArgc, JS::Value* aVp) |
|
387 { |
|
388 return ThenableResolverCommon(aCx, PromiseCallback::Resolve, aArgc, aVp); |
|
389 } |
|
390 |
|
391 /* static */ bool |
|
392 Promise::JSCallbackThenableRejecter(JSContext* aCx, |
|
393 unsigned aArgc, JS::Value* aVp) |
|
394 { |
|
395 return ThenableResolverCommon(aCx, PromiseCallback::Reject, aArgc, aVp); |
|
396 } |
|
397 |
|
398 /* static */ JSObject* |
|
399 Promise::CreateFunction(JSContext* aCx, JSObject* aParent, Promise* aPromise, |
|
400 int32_t aTask) |
|
401 { |
|
402 JSFunction* func = js::NewFunctionWithReserved(aCx, JSCallback, |
|
403 1 /* nargs */, 0 /* flags */, |
|
404 aParent, nullptr); |
|
405 if (!func) { |
|
406 return nullptr; |
|
407 } |
|
408 |
|
409 JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func)); |
|
410 |
|
411 JS::Rooted<JS::Value> promiseObj(aCx); |
|
412 if (!dom::WrapNewBindingObject(aCx, aPromise, &promiseObj)) { |
|
413 return nullptr; |
|
414 } |
|
415 |
|
416 js::SetFunctionNativeReserved(obj, SLOT_PROMISE, promiseObj); |
|
417 js::SetFunctionNativeReserved(obj, SLOT_DATA, JS::Int32Value(aTask)); |
|
418 |
|
419 return obj; |
|
420 } |
|
421 |
|
422 /* static */ JSObject* |
|
423 Promise::CreateThenableFunction(JSContext* aCx, Promise* aPromise, uint32_t aTask) |
|
424 { |
|
425 JSNative whichFunc = |
|
426 aTask == PromiseCallback::Resolve ? JSCallbackThenableResolver : |
|
427 JSCallbackThenableRejecter ; |
|
428 |
|
429 JSFunction* func = js::NewFunctionWithReserved(aCx, whichFunc, |
|
430 1 /* nargs */, 0 /* flags */, |
|
431 nullptr, nullptr); |
|
432 if (!func) { |
|
433 return nullptr; |
|
434 } |
|
435 |
|
436 JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func)); |
|
437 |
|
438 JS::Rooted<JS::Value> promiseObj(aCx); |
|
439 if (!dom::WrapNewBindingObject(aCx, aPromise, &promiseObj)) { |
|
440 return nullptr; |
|
441 } |
|
442 |
|
443 js::SetFunctionNativeReserved(obj, SLOT_PROMISE, promiseObj); |
|
444 |
|
445 return obj; |
|
446 } |
|
447 |
|
448 /* static */ already_AddRefed<Promise> |
|
449 Promise::Constructor(const GlobalObject& aGlobal, |
|
450 PromiseInit& aInit, ErrorResult& aRv) |
|
451 { |
|
452 JSContext* cx = aGlobal.GetContext(); |
|
453 |
|
454 nsCOMPtr<nsIGlobalObject> global; |
|
455 global = do_QueryInterface(aGlobal.GetAsSupports()); |
|
456 if (!global) { |
|
457 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
458 return nullptr; |
|
459 } |
|
460 |
|
461 nsRefPtr<Promise> promise = new Promise(global); |
|
462 |
|
463 JS::Rooted<JSObject*> resolveFunc(cx, |
|
464 CreateFunction(cx, aGlobal.Get(), promise, |
|
465 PromiseCallback::Resolve)); |
|
466 if (!resolveFunc) { |
|
467 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
468 return nullptr; |
|
469 } |
|
470 |
|
471 JS::Rooted<JSObject*> rejectFunc(cx, |
|
472 CreateFunction(cx, aGlobal.Get(), promise, |
|
473 PromiseCallback::Reject)); |
|
474 if (!rejectFunc) { |
|
475 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
476 return nullptr; |
|
477 } |
|
478 |
|
479 aInit.Call(resolveFunc, rejectFunc, aRv, CallbackObject::eRethrowExceptions); |
|
480 aRv.WouldReportJSException(); |
|
481 |
|
482 if (aRv.IsJSException()) { |
|
483 JS::Rooted<JS::Value> value(cx); |
|
484 aRv.StealJSException(cx, &value); |
|
485 |
|
486 // we want the same behavior as this JS implementation: |
|
487 // function Promise(arg) { try { arg(a, b); } catch (e) { this.reject(e); }} |
|
488 if (!JS_WrapValue(cx, &value)) { |
|
489 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
490 return nullptr; |
|
491 } |
|
492 |
|
493 promise->MaybeRejectInternal(cx, value); |
|
494 } |
|
495 |
|
496 return promise.forget(); |
|
497 } |
|
498 |
|
499 /* static */ already_AddRefed<Promise> |
|
500 Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx, |
|
501 JS::Handle<JS::Value> aValue, ErrorResult& aRv) |
|
502 { |
|
503 // If a Promise was passed, just return it. |
|
504 if (aValue.isObject()) { |
|
505 JS::Rooted<JSObject*> valueObj(aCx, &aValue.toObject()); |
|
506 Promise* nextPromise; |
|
507 nsresult rv = UNWRAP_OBJECT(Promise, valueObj, nextPromise); |
|
508 |
|
509 if (NS_SUCCEEDED(rv)) { |
|
510 nsRefPtr<Promise> addRefed = nextPromise; |
|
511 return addRefed.forget(); |
|
512 } |
|
513 } |
|
514 |
|
515 nsCOMPtr<nsIGlobalObject> global = |
|
516 do_QueryInterface(aGlobal.GetAsSupports()); |
|
517 if (!global) { |
|
518 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
519 return nullptr; |
|
520 } |
|
521 |
|
522 return Resolve(global, aCx, aValue, aRv); |
|
523 } |
|
524 |
|
525 /* static */ already_AddRefed<Promise> |
|
526 Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx, |
|
527 JS::Handle<JS::Value> aValue, ErrorResult& aRv) |
|
528 { |
|
529 nsRefPtr<Promise> promise = new Promise(aGlobal); |
|
530 |
|
531 promise->MaybeResolveInternal(aCx, aValue); |
|
532 return promise.forget(); |
|
533 } |
|
534 |
|
535 /* static */ already_AddRefed<Promise> |
|
536 Promise::Reject(const GlobalObject& aGlobal, JSContext* aCx, |
|
537 JS::Handle<JS::Value> aValue, ErrorResult& aRv) |
|
538 { |
|
539 nsCOMPtr<nsIGlobalObject> global = |
|
540 do_QueryInterface(aGlobal.GetAsSupports()); |
|
541 if (!global) { |
|
542 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
543 return nullptr; |
|
544 } |
|
545 |
|
546 return Reject(global, aCx, aValue, aRv); |
|
547 } |
|
548 |
|
549 /* static */ already_AddRefed<Promise> |
|
550 Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx, |
|
551 JS::Handle<JS::Value> aValue, ErrorResult& aRv) |
|
552 { |
|
553 nsRefPtr<Promise> promise = new Promise(aGlobal); |
|
554 |
|
555 promise->MaybeRejectInternal(aCx, aValue); |
|
556 return promise.forget(); |
|
557 } |
|
558 |
|
559 already_AddRefed<Promise> |
|
560 Promise::Then(JSContext* aCx, AnyCallback* aResolveCallback, |
|
561 AnyCallback* aRejectCallback) |
|
562 { |
|
563 nsRefPtr<Promise> promise = new Promise(GetParentObject()); |
|
564 |
|
565 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); |
|
566 |
|
567 nsRefPtr<PromiseCallback> resolveCb = |
|
568 PromiseCallback::Factory(promise, global, aResolveCallback, |
|
569 PromiseCallback::Resolve); |
|
570 |
|
571 nsRefPtr<PromiseCallback> rejectCb = |
|
572 PromiseCallback::Factory(promise, global, aRejectCallback, |
|
573 PromiseCallback::Reject); |
|
574 |
|
575 AppendCallbacks(resolveCb, rejectCb); |
|
576 |
|
577 return promise.forget(); |
|
578 } |
|
579 |
|
580 already_AddRefed<Promise> |
|
581 Promise::Catch(JSContext* aCx, AnyCallback* aRejectCallback) |
|
582 { |
|
583 nsRefPtr<AnyCallback> resolveCb; |
|
584 return Then(aCx, resolveCb, aRejectCallback); |
|
585 } |
|
586 |
|
587 /** |
|
588 * The CountdownHolder class encapsulates Promise.all countdown functions and |
|
589 * the countdown holder parts of the Promises spec. It maintains the result |
|
590 * array and AllResolveHandlers use SetValue() to set the array indices. |
|
591 */ |
|
592 class CountdownHolder MOZ_FINAL : public nsISupports |
|
593 { |
|
594 public: |
|
595 NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
|
596 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CountdownHolder) |
|
597 |
|
598 CountdownHolder(const GlobalObject& aGlobal, Promise* aPromise, |
|
599 uint32_t aCountdown) |
|
600 : mPromise(aPromise), mCountdown(aCountdown) |
|
601 { |
|
602 MOZ_ASSERT(aCountdown != 0); |
|
603 JSContext* cx = aGlobal.GetContext(); |
|
604 |
|
605 // The only time aGlobal.GetContext() and aGlobal.Get() are not |
|
606 // same-compartment is when we're called via Xrays, and in that situation we |
|
607 // in fact want to create the array in the callee compartment |
|
608 |
|
609 JSAutoCompartment ac(cx, aGlobal.Get()); |
|
610 mValues = JS_NewArrayObject(cx, aCountdown); |
|
611 mozilla::HoldJSObjects(this); |
|
612 } |
|
613 |
|
614 ~CountdownHolder() |
|
615 { |
|
616 mozilla::DropJSObjects(this); |
|
617 } |
|
618 |
|
619 void SetValue(uint32_t index, const JS::Handle<JS::Value> aValue) |
|
620 { |
|
621 MOZ_ASSERT(mCountdown > 0); |
|
622 |
|
623 ThreadsafeAutoSafeJSContext cx; |
|
624 JSAutoCompartment ac(cx, mValues); |
|
625 { |
|
626 |
|
627 AutoDontReportUncaught silenceReporting(cx); |
|
628 JS::Rooted<JS::Value> value(cx, aValue); |
|
629 if (!JS_WrapValue(cx, &value) || |
|
630 !JS_DefineElement(cx, mValues, index, value, nullptr, nullptr, |
|
631 JSPROP_ENUMERATE)) { |
|
632 MOZ_ASSERT(JS_IsExceptionPending(cx)); |
|
633 JS::Rooted<JS::Value> exn(cx); |
|
634 JS_GetPendingException(cx, &exn); |
|
635 |
|
636 mPromise->MaybeReject(cx, exn); |
|
637 } |
|
638 } |
|
639 |
|
640 --mCountdown; |
|
641 if (mCountdown == 0) { |
|
642 JS::Rooted<JS::Value> result(cx, JS::ObjectValue(*mValues)); |
|
643 mPromise->MaybeResolve(cx, result); |
|
644 } |
|
645 } |
|
646 |
|
647 private: |
|
648 nsRefPtr<Promise> mPromise; |
|
649 uint32_t mCountdown; |
|
650 JS::Heap<JSObject*> mValues; |
|
651 }; |
|
652 |
|
653 NS_IMPL_CYCLE_COLLECTING_ADDREF(CountdownHolder) |
|
654 NS_IMPL_CYCLE_COLLECTING_RELEASE(CountdownHolder) |
|
655 NS_IMPL_CYCLE_COLLECTION_CLASS(CountdownHolder) |
|
656 |
|
657 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CountdownHolder) |
|
658 NS_INTERFACE_MAP_ENTRY(nsISupports) |
|
659 NS_INTERFACE_MAP_END |
|
660 |
|
661 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CountdownHolder) |
|
662 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mValues) |
|
663 NS_IMPL_CYCLE_COLLECTION_TRACE_END |
|
664 |
|
665 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CountdownHolder) |
|
666 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
667 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) |
|
668 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
669 |
|
670 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CountdownHolder) |
|
671 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) |
|
672 tmp->mValues = nullptr; |
|
673 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
674 |
|
675 /** |
|
676 * An AllResolveHandler is the per-promise part of the Promise.all() algorithm. |
|
677 * Every Promise in the handler is handed an instance of this as a resolution |
|
678 * handler and it sets the relevant index in the CountdownHolder. |
|
679 */ |
|
680 class AllResolveHandler MOZ_FINAL : public PromiseNativeHandler |
|
681 { |
|
682 public: |
|
683 NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
|
684 NS_DECL_CYCLE_COLLECTION_CLASS(AllResolveHandler) |
|
685 |
|
686 AllResolveHandler(CountdownHolder* aHolder, uint32_t aIndex) |
|
687 : mCountdownHolder(aHolder), mIndex(aIndex) |
|
688 { |
|
689 MOZ_ASSERT(aHolder); |
|
690 } |
|
691 |
|
692 ~AllResolveHandler() |
|
693 { |
|
694 } |
|
695 |
|
696 void |
|
697 ResolvedCallback(JS::Handle<JS::Value> aValue) |
|
698 { |
|
699 mCountdownHolder->SetValue(mIndex, aValue); |
|
700 } |
|
701 |
|
702 void |
|
703 RejectedCallback(JS::Handle<JS::Value> aValue) |
|
704 { |
|
705 // Should never be attached to Promise as a reject handler. |
|
706 MOZ_ASSERT(false, "AllResolveHandler should never be attached to a Promise's reject handler!"); |
|
707 } |
|
708 |
|
709 private: |
|
710 nsRefPtr<CountdownHolder> mCountdownHolder; |
|
711 uint32_t mIndex; |
|
712 }; |
|
713 |
|
714 NS_IMPL_CYCLE_COLLECTING_ADDREF(AllResolveHandler) |
|
715 NS_IMPL_CYCLE_COLLECTING_RELEASE(AllResolveHandler) |
|
716 |
|
717 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AllResolveHandler) |
|
718 NS_INTERFACE_MAP_END_INHERITING(PromiseNativeHandler) |
|
719 |
|
720 NS_IMPL_CYCLE_COLLECTION(AllResolveHandler, mCountdownHolder) |
|
721 |
|
722 /* static */ already_AddRefed<Promise> |
|
723 Promise::All(const GlobalObject& aGlobal, JSContext* aCx, |
|
724 const Sequence<JS::Value>& aIterable, ErrorResult& aRv) |
|
725 { |
|
726 nsCOMPtr<nsIGlobalObject> global = |
|
727 do_QueryInterface(aGlobal.GetAsSupports()); |
|
728 if (!global) { |
|
729 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
730 return nullptr; |
|
731 } |
|
732 |
|
733 if (aIterable.Length() == 0) { |
|
734 JS::Rooted<JSObject*> empty(aCx, JS_NewArrayObject(aCx, 0)); |
|
735 if (!empty) { |
|
736 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
|
737 return nullptr; |
|
738 } |
|
739 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*empty)); |
|
740 return Promise::Resolve(aGlobal, aCx, value, aRv); |
|
741 } |
|
742 |
|
743 nsRefPtr<Promise> promise = new Promise(global); |
|
744 nsRefPtr<CountdownHolder> holder = |
|
745 new CountdownHolder(aGlobal, promise, aIterable.Length()); |
|
746 |
|
747 JS::Rooted<JSObject*> obj(aCx, JS::CurrentGlobalOrNull(aCx)); |
|
748 if (!obj) { |
|
749 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
750 return nullptr; |
|
751 } |
|
752 |
|
753 nsRefPtr<PromiseCallback> rejectCb = new RejectPromiseCallback(promise, obj); |
|
754 |
|
755 for (uint32_t i = 0; i < aIterable.Length(); ++i) { |
|
756 JS::Rooted<JS::Value> value(aCx, aIterable.ElementAt(i)); |
|
757 nsRefPtr<Promise> nextPromise = Promise::Resolve(aGlobal, aCx, value, aRv); |
|
758 |
|
759 MOZ_ASSERT(!aRv.Failed()); |
|
760 |
|
761 nsRefPtr<PromiseNativeHandler> resolveHandler = |
|
762 new AllResolveHandler(holder, i); |
|
763 |
|
764 nsRefPtr<PromiseCallback> resolveCb = |
|
765 new NativePromiseCallback(resolveHandler, Resolved); |
|
766 // Every promise gets its own resolve callback, which will set the right |
|
767 // index in the array to the resolution value. |
|
768 nextPromise->AppendCallbacks(resolveCb, rejectCb); |
|
769 } |
|
770 |
|
771 return promise.forget(); |
|
772 } |
|
773 |
|
774 /* static */ already_AddRefed<Promise> |
|
775 Promise::Race(const GlobalObject& aGlobal, JSContext* aCx, |
|
776 const Sequence<JS::Value>& aIterable, ErrorResult& aRv) |
|
777 { |
|
778 nsCOMPtr<nsIGlobalObject> global = |
|
779 do_QueryInterface(aGlobal.GetAsSupports()); |
|
780 if (!global) { |
|
781 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
782 return nullptr; |
|
783 } |
|
784 |
|
785 JS::Rooted<JSObject*> obj(aCx, JS::CurrentGlobalOrNull(aCx)); |
|
786 if (!obj) { |
|
787 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
788 return nullptr; |
|
789 } |
|
790 |
|
791 nsRefPtr<Promise> promise = new Promise(global); |
|
792 |
|
793 nsRefPtr<PromiseCallback> resolveCb = |
|
794 new ResolvePromiseCallback(promise, obj); |
|
795 |
|
796 nsRefPtr<PromiseCallback> rejectCb = new RejectPromiseCallback(promise, obj); |
|
797 |
|
798 for (uint32_t i = 0; i < aIterable.Length(); ++i) { |
|
799 JS::Rooted<JS::Value> value(aCx, aIterable.ElementAt(i)); |
|
800 nsRefPtr<Promise> nextPromise = Promise::Resolve(aGlobal, aCx, value, aRv); |
|
801 // According to spec, Resolve can throw, but our implementation never does. |
|
802 // Well it does when window isn't passed on the main thread, but that is an |
|
803 // implementation detail which should never be reached since we are checking |
|
804 // for window above. Remove this when subclassing is supported. |
|
805 MOZ_ASSERT(!aRv.Failed()); |
|
806 nextPromise->AppendCallbacks(resolveCb, rejectCb); |
|
807 } |
|
808 |
|
809 return promise.forget(); |
|
810 } |
|
811 |
|
812 void |
|
813 Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable) |
|
814 { |
|
815 nsRefPtr<PromiseCallback> resolveCb = |
|
816 new NativePromiseCallback(aRunnable, Resolved); |
|
817 |
|
818 nsRefPtr<PromiseCallback> rejectCb = |
|
819 new NativePromiseCallback(aRunnable, Rejected); |
|
820 |
|
821 AppendCallbacks(resolveCb, rejectCb); |
|
822 } |
|
823 |
|
824 void |
|
825 Promise::AppendCallbacks(PromiseCallback* aResolveCallback, |
|
826 PromiseCallback* aRejectCallback) |
|
827 { |
|
828 if (aResolveCallback) { |
|
829 mResolveCallbacks.AppendElement(aResolveCallback); |
|
830 } |
|
831 |
|
832 if (aRejectCallback) { |
|
833 mHadRejectCallback = true; |
|
834 mRejectCallbacks.AppendElement(aRejectCallback); |
|
835 |
|
836 // Now that there is a callback, we don't need to report anymore. |
|
837 RemoveFeature(); |
|
838 } |
|
839 |
|
840 // If promise's state is resolved, queue a task to process our resolve |
|
841 // callbacks with promise's result. If promise's state is rejected, queue a |
|
842 // task to process our reject callbacks with promise's result. |
|
843 if (mState != Pending && !mTaskPending) { |
|
844 if (MOZ_LIKELY(NS_IsMainThread())) { |
|
845 nsRefPtr<PromiseTask> task = new PromiseTask(this); |
|
846 NS_DispatchToCurrentThread(task); |
|
847 } else { |
|
848 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); |
|
849 MOZ_ASSERT(worker); |
|
850 nsRefPtr<WorkerPromiseTask> task = new WorkerPromiseTask(worker, this); |
|
851 task->Dispatch(worker->GetJSContext()); |
|
852 } |
|
853 mTaskPending = true; |
|
854 } |
|
855 } |
|
856 |
|
857 void |
|
858 Promise::RunTask() |
|
859 { |
|
860 MOZ_ASSERT(mState != Pending); |
|
861 |
|
862 nsTArray<nsRefPtr<PromiseCallback>> callbacks; |
|
863 callbacks.SwapElements(mState == Resolved ? mResolveCallbacks |
|
864 : mRejectCallbacks); |
|
865 mResolveCallbacks.Clear(); |
|
866 mRejectCallbacks.Clear(); |
|
867 |
|
868 ThreadsafeAutoJSContext cx; // Just for rooting. |
|
869 JS::Rooted<JS::Value> value(cx, mResult); |
|
870 |
|
871 for (uint32_t i = 0; i < callbacks.Length(); ++i) { |
|
872 callbacks[i]->Call(value); |
|
873 } |
|
874 } |
|
875 |
|
876 void |
|
877 Promise::MaybeReportRejected() |
|
878 { |
|
879 if (mState != Rejected || mHadRejectCallback || mResult.isUndefined()) { |
|
880 return; |
|
881 } |
|
882 |
|
883 if (!mResult.isObject()) { |
|
884 return; |
|
885 } |
|
886 ThreadsafeAutoJSContext cx; |
|
887 JS::Rooted<JSObject*> obj(cx, &mResult.toObject()); |
|
888 JSAutoCompartment ac(cx, obj); |
|
889 JSErrorReport* report = JS_ErrorFromException(cx, obj); |
|
890 if (!report) { |
|
891 return; |
|
892 } |
|
893 |
|
894 // Remains null in case of worker. |
|
895 nsCOMPtr<nsPIDOMWindow> win; |
|
896 bool isChromeError = false; |
|
897 |
|
898 if (MOZ_LIKELY(NS_IsMainThread())) { |
|
899 win = |
|
900 do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(obj)); |
|
901 nsIPrincipal* principal = nsContentUtils::GetObjectPrincipal(obj); |
|
902 isChromeError = nsContentUtils::IsSystemPrincipal(principal); |
|
903 } else { |
|
904 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); |
|
905 MOZ_ASSERT(worker); |
|
906 isChromeError = worker->IsChromeWorker(); |
|
907 } |
|
908 |
|
909 // Now post an event to do the real reporting async |
|
910 // Since Promises preserve their wrapper, it is essential to nsRefPtr<> the |
|
911 // AsyncErrorReporter, otherwise if the call to DispatchToMainThread fails, it |
|
912 // will leak. See Bug 958684. |
|
913 nsRefPtr<AsyncErrorReporter> r = |
|
914 new AsyncErrorReporter(JS_GetObjectRuntime(obj), |
|
915 report, |
|
916 nullptr, |
|
917 isChromeError, |
|
918 win); |
|
919 NS_DispatchToMainThread(r); |
|
920 } |
|
921 |
|
922 void |
|
923 Promise::MaybeResolveInternal(JSContext* aCx, |
|
924 JS::Handle<JS::Value> aValue, |
|
925 PromiseTaskSync aAsynchronous) |
|
926 { |
|
927 if (mResolvePending) { |
|
928 return; |
|
929 } |
|
930 |
|
931 ResolveInternal(aCx, aValue, aAsynchronous); |
|
932 } |
|
933 |
|
934 void |
|
935 Promise::MaybeRejectInternal(JSContext* aCx, |
|
936 JS::Handle<JS::Value> aValue, |
|
937 PromiseTaskSync aAsynchronous) |
|
938 { |
|
939 if (mResolvePending) { |
|
940 return; |
|
941 } |
|
942 |
|
943 RejectInternal(aCx, aValue, aAsynchronous); |
|
944 } |
|
945 |
|
946 void |
|
947 Promise::HandleException(JSContext* aCx) |
|
948 { |
|
949 JS::Rooted<JS::Value> exn(aCx); |
|
950 if (JS_GetPendingException(aCx, &exn)) { |
|
951 JS_ClearPendingException(aCx); |
|
952 RejectInternal(aCx, exn, SyncTask); |
|
953 } |
|
954 } |
|
955 |
|
956 void |
|
957 Promise::ResolveInternal(JSContext* aCx, |
|
958 JS::Handle<JS::Value> aValue, |
|
959 PromiseTaskSync aAsynchronous) |
|
960 { |
|
961 mResolvePending = true; |
|
962 |
|
963 if (aValue.isObject()) { |
|
964 AutoDontReportUncaught silenceReporting(aCx); |
|
965 JS::Rooted<JSObject*> valueObj(aCx, &aValue.toObject()); |
|
966 |
|
967 // Thenables. |
|
968 JS::Rooted<JS::Value> then(aCx); |
|
969 if (!JS_GetProperty(aCx, valueObj, "then", &then)) { |
|
970 HandleException(aCx); |
|
971 return; |
|
972 } |
|
973 |
|
974 if (then.isObject() && JS_ObjectIsCallable(aCx, &then.toObject())) { |
|
975 JS::Rooted<JSObject*> resolveFunc(aCx, |
|
976 CreateThenableFunction(aCx, this, PromiseCallback::Resolve)); |
|
977 |
|
978 if (!resolveFunc) { |
|
979 HandleException(aCx); |
|
980 return; |
|
981 } |
|
982 |
|
983 JS::Rooted<JSObject*> rejectFunc(aCx, |
|
984 CreateThenableFunction(aCx, this, PromiseCallback::Reject)); |
|
985 if (!rejectFunc) { |
|
986 HandleException(aCx); |
|
987 return; |
|
988 } |
|
989 |
|
990 LinkThenableCallables(aCx, resolveFunc, rejectFunc); |
|
991 |
|
992 JS::Rooted<JSObject*> thenObj(aCx, &then.toObject()); |
|
993 nsRefPtr<PromiseInit> thenCallback = |
|
994 new PromiseInit(thenObj, mozilla::dom::GetIncumbentGlobal()); |
|
995 |
|
996 ErrorResult rv; |
|
997 thenCallback->Call(valueObj, resolveFunc, rejectFunc, |
|
998 rv, CallbackObject::eRethrowExceptions); |
|
999 rv.WouldReportJSException(); |
|
1000 |
|
1001 if (rv.IsJSException()) { |
|
1002 JS::Rooted<JS::Value> exn(aCx); |
|
1003 rv.StealJSException(aCx, &exn); |
|
1004 |
|
1005 bool couldMarkAsCalled = MarkAsCalledIfNotCalledBefore(aCx, resolveFunc); |
|
1006 |
|
1007 // If we could mark as called, neither of the callbacks had been called |
|
1008 // when the exception was thrown. So we can reject the Promise. |
|
1009 if (couldMarkAsCalled) { |
|
1010 bool ok = JS_WrapValue(aCx, &exn); |
|
1011 MOZ_ASSERT(ok); |
|
1012 if (!ok) { |
|
1013 NS_WARNING("Failed to wrap value into the right compartment."); |
|
1014 } |
|
1015 |
|
1016 RejectInternal(aCx, exn, Promise::SyncTask); |
|
1017 } |
|
1018 // At least one of resolveFunc or rejectFunc have been called, so ignore |
|
1019 // the exception. FIXME(nsm): This should be reported to the error |
|
1020 // console though, for debugging. |
|
1021 } |
|
1022 |
|
1023 return; |
|
1024 } |
|
1025 } |
|
1026 |
|
1027 // If the synchronous flag is set, process our resolve callbacks with |
|
1028 // value. Otherwise, the synchronous flag is unset, queue a task to process |
|
1029 // own resolve callbacks with value. Otherwise, the synchronous flag is |
|
1030 // unset, queue a task to process our resolve callbacks with value. |
|
1031 RunResolveTask(aValue, Resolved, aAsynchronous); |
|
1032 } |
|
1033 |
|
1034 void |
|
1035 Promise::RejectInternal(JSContext* aCx, |
|
1036 JS::Handle<JS::Value> aValue, |
|
1037 PromiseTaskSync aAsynchronous) |
|
1038 { |
|
1039 mResolvePending = true; |
|
1040 |
|
1041 // If the synchronous flag is set, process our reject callbacks with |
|
1042 // value. Otherwise, the synchronous flag is unset, queue a task to process |
|
1043 // promise's reject callbacks with value. |
|
1044 RunResolveTask(aValue, Rejected, aAsynchronous); |
|
1045 } |
|
1046 |
|
1047 void |
|
1048 Promise::RunResolveTask(JS::Handle<JS::Value> aValue, |
|
1049 PromiseState aState, |
|
1050 PromiseTaskSync aAsynchronous) |
|
1051 { |
|
1052 // If the synchronous flag is unset, queue a task to process our |
|
1053 // accept callbacks with value. |
|
1054 if (aAsynchronous == AsyncTask) { |
|
1055 if (MOZ_LIKELY(NS_IsMainThread())) { |
|
1056 nsRefPtr<PromiseResolverTask> task = |
|
1057 new PromiseResolverTask(this, aValue, aState); |
|
1058 NS_DispatchToCurrentThread(task); |
|
1059 } else { |
|
1060 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); |
|
1061 MOZ_ASSERT(worker); |
|
1062 nsRefPtr<WorkerPromiseResolverTask> task = |
|
1063 new WorkerPromiseResolverTask(worker, this, aValue, aState); |
|
1064 task->Dispatch(worker->GetJSContext()); |
|
1065 } |
|
1066 return; |
|
1067 } |
|
1068 |
|
1069 // Promise.all() or Promise.race() implementations will repeatedly call |
|
1070 // Resolve/RejectInternal rather than using the Maybe... forms. Stop SetState |
|
1071 // from asserting. |
|
1072 if (mState != Pending) { |
|
1073 return; |
|
1074 } |
|
1075 |
|
1076 SetResult(aValue); |
|
1077 SetState(aState); |
|
1078 |
|
1079 // If the Promise was rejected, and there is no reject handler already setup, |
|
1080 // watch for thread shutdown. |
|
1081 if (aState == PromiseState::Rejected && |
|
1082 !mHadRejectCallback && |
|
1083 !NS_IsMainThread()) { |
|
1084 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); |
|
1085 MOZ_ASSERT(worker); |
|
1086 worker->AssertIsOnWorkerThread(); |
|
1087 |
|
1088 mFeature = new PromiseReportRejectFeature(this); |
|
1089 if (NS_WARN_IF(!worker->AddFeature(worker->GetJSContext(), mFeature))) { |
|
1090 // To avoid a false RemoveFeature(). |
|
1091 mFeature = nullptr; |
|
1092 // Worker is shutting down, report rejection immediately since it is |
|
1093 // unlikely that reject callbacks will be added after this point. |
|
1094 MaybeReportRejectedOnce(); |
|
1095 } |
|
1096 } |
|
1097 |
|
1098 RunTask(); |
|
1099 } |
|
1100 |
|
1101 void |
|
1102 Promise::RemoveFeature() |
|
1103 { |
|
1104 if (mFeature) { |
|
1105 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); |
|
1106 MOZ_ASSERT(worker); |
|
1107 worker->RemoveFeature(worker->GetJSContext(), mFeature); |
|
1108 mFeature = nullptr; |
|
1109 } |
|
1110 } |
|
1111 |
|
1112 bool |
|
1113 PromiseReportRejectFeature::Notify(JSContext* aCx, workers::Status aStatus) |
|
1114 { |
|
1115 MOZ_ASSERT(aStatus > workers::Running); |
|
1116 mPromise->MaybeReportRejectedOnce(); |
|
1117 // After this point, `this` has been deleted by RemoveFeature! |
|
1118 return true; |
|
1119 } |
|
1120 |
|
1121 } // namespace dom |
|
1122 } // namespace mozilla |