/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 |