|
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 "PromiseCallback.h" |
|
8 #include "mozilla/dom/Promise.h" |
|
9 #include "mozilla/dom/PromiseNativeHandler.h" |
|
10 |
|
11 #include "js/OldDebugAPI.h" |
|
12 |
|
13 namespace mozilla { |
|
14 namespace dom { |
|
15 |
|
16 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseCallback) |
|
17 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseCallback) |
|
18 |
|
19 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseCallback) |
|
20 NS_INTERFACE_MAP_ENTRY(nsISupports) |
|
21 NS_INTERFACE_MAP_END |
|
22 |
|
23 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseCallback) |
|
24 |
|
25 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseCallback) |
|
26 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
27 |
|
28 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseCallback) |
|
29 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
30 |
|
31 PromiseCallback::PromiseCallback() |
|
32 { |
|
33 MOZ_COUNT_CTOR(PromiseCallback); |
|
34 } |
|
35 |
|
36 PromiseCallback::~PromiseCallback() |
|
37 { |
|
38 MOZ_COUNT_DTOR(PromiseCallback); |
|
39 } |
|
40 |
|
41 // ResolvePromiseCallback |
|
42 |
|
43 NS_IMPL_CYCLE_COLLECTION_CLASS(ResolvePromiseCallback) |
|
44 |
|
45 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ResolvePromiseCallback, |
|
46 PromiseCallback) |
|
47 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) |
|
48 tmp->mGlobal = nullptr; |
|
49 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
50 |
|
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ResolvePromiseCallback, |
|
52 PromiseCallback) |
|
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) |
|
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
56 |
|
57 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ResolvePromiseCallback) |
|
58 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal) |
|
59 NS_IMPL_CYCLE_COLLECTION_TRACE_END |
|
60 |
|
61 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ResolvePromiseCallback) |
|
62 NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) |
|
63 |
|
64 NS_IMPL_ADDREF_INHERITED(ResolvePromiseCallback, PromiseCallback) |
|
65 NS_IMPL_RELEASE_INHERITED(ResolvePromiseCallback, PromiseCallback) |
|
66 |
|
67 ResolvePromiseCallback::ResolvePromiseCallback(Promise* aPromise, |
|
68 JS::Handle<JSObject*> aGlobal) |
|
69 : mPromise(aPromise) |
|
70 , mGlobal(aGlobal) |
|
71 { |
|
72 MOZ_ASSERT(aPromise); |
|
73 MOZ_ASSERT(aGlobal); |
|
74 MOZ_COUNT_CTOR(ResolvePromiseCallback); |
|
75 HoldJSObjects(this); |
|
76 } |
|
77 |
|
78 ResolvePromiseCallback::~ResolvePromiseCallback() |
|
79 { |
|
80 MOZ_COUNT_DTOR(ResolvePromiseCallback); |
|
81 DropJSObjects(this); |
|
82 } |
|
83 |
|
84 void |
|
85 ResolvePromiseCallback::Call(JS::Handle<JS::Value> aValue) |
|
86 { |
|
87 // Run resolver's algorithm with value and the synchronous flag set. |
|
88 ThreadsafeAutoSafeJSContext cx; |
|
89 |
|
90 JSAutoCompartment ac(cx, mGlobal); |
|
91 JS::Rooted<JS::Value> value(cx, aValue); |
|
92 if (!JS_WrapValue(cx, &value)) { |
|
93 NS_WARNING("Failed to wrap value into the right compartment."); |
|
94 return; |
|
95 } |
|
96 |
|
97 mPromise->ResolveInternal(cx, value, Promise::SyncTask); |
|
98 } |
|
99 |
|
100 // RejectPromiseCallback |
|
101 |
|
102 NS_IMPL_CYCLE_COLLECTION_CLASS(RejectPromiseCallback) |
|
103 |
|
104 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(RejectPromiseCallback, |
|
105 PromiseCallback) |
|
106 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) |
|
107 tmp->mGlobal = nullptr; |
|
108 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
109 |
|
110 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(RejectPromiseCallback, |
|
111 PromiseCallback) |
|
112 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) |
|
113 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
114 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
115 |
|
116 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(RejectPromiseCallback) |
|
117 NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) |
|
118 |
|
119 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(RejectPromiseCallback, |
|
120 PromiseCallback) |
|
121 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal) |
|
122 NS_IMPL_CYCLE_COLLECTION_TRACE_END |
|
123 |
|
124 NS_IMPL_ADDREF_INHERITED(RejectPromiseCallback, PromiseCallback) |
|
125 NS_IMPL_RELEASE_INHERITED(RejectPromiseCallback, PromiseCallback) |
|
126 |
|
127 RejectPromiseCallback::RejectPromiseCallback(Promise* aPromise, |
|
128 JS::Handle<JSObject*> aGlobal) |
|
129 : mPromise(aPromise) |
|
130 , mGlobal(aGlobal) |
|
131 { |
|
132 MOZ_ASSERT(aPromise); |
|
133 MOZ_ASSERT(mGlobal); |
|
134 MOZ_COUNT_CTOR(RejectPromiseCallback); |
|
135 HoldJSObjects(this); |
|
136 } |
|
137 |
|
138 RejectPromiseCallback::~RejectPromiseCallback() |
|
139 { |
|
140 MOZ_COUNT_DTOR(RejectPromiseCallback); |
|
141 DropJSObjects(this); |
|
142 } |
|
143 |
|
144 void |
|
145 RejectPromiseCallback::Call(JS::Handle<JS::Value> aValue) |
|
146 { |
|
147 // Run resolver's algorithm with value and the synchronous flag set. |
|
148 ThreadsafeAutoSafeJSContext cx; |
|
149 |
|
150 JSAutoCompartment ac(cx, mGlobal); |
|
151 JS::Rooted<JS::Value> value(cx, aValue); |
|
152 if (!JS_WrapValue(cx, &value)) { |
|
153 NS_WARNING("Failed to wrap value into the right compartment."); |
|
154 return; |
|
155 } |
|
156 |
|
157 |
|
158 mPromise->RejectInternal(cx, value, Promise::SyncTask); |
|
159 } |
|
160 |
|
161 // WrapperPromiseCallback |
|
162 NS_IMPL_CYCLE_COLLECTION_CLASS(WrapperPromiseCallback) |
|
163 |
|
164 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WrapperPromiseCallback, |
|
165 PromiseCallback) |
|
166 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextPromise) |
|
167 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) |
|
168 tmp->mGlobal = nullptr; |
|
169 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
170 |
|
171 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WrapperPromiseCallback, |
|
172 PromiseCallback) |
|
173 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextPromise) |
|
174 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) |
|
175 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
176 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
177 |
|
178 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WrapperPromiseCallback) |
|
179 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal) |
|
180 NS_IMPL_CYCLE_COLLECTION_TRACE_END |
|
181 |
|
182 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WrapperPromiseCallback) |
|
183 NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) |
|
184 |
|
185 NS_IMPL_ADDREF_INHERITED(WrapperPromiseCallback, PromiseCallback) |
|
186 NS_IMPL_RELEASE_INHERITED(WrapperPromiseCallback, PromiseCallback) |
|
187 |
|
188 WrapperPromiseCallback::WrapperPromiseCallback(Promise* aNextPromise, |
|
189 JS::Handle<JSObject*> aGlobal, |
|
190 AnyCallback* aCallback) |
|
191 : mNextPromise(aNextPromise) |
|
192 , mGlobal(aGlobal) |
|
193 , mCallback(aCallback) |
|
194 { |
|
195 MOZ_ASSERT(aNextPromise); |
|
196 MOZ_ASSERT(aGlobal); |
|
197 MOZ_COUNT_CTOR(WrapperPromiseCallback); |
|
198 HoldJSObjects(this); |
|
199 } |
|
200 |
|
201 WrapperPromiseCallback::~WrapperPromiseCallback() |
|
202 { |
|
203 MOZ_COUNT_DTOR(WrapperPromiseCallback); |
|
204 DropJSObjects(this); |
|
205 } |
|
206 |
|
207 void |
|
208 WrapperPromiseCallback::Call(JS::Handle<JS::Value> aValue) |
|
209 { |
|
210 ThreadsafeAutoSafeJSContext cx; |
|
211 |
|
212 JSAutoCompartment ac(cx, mGlobal); |
|
213 JS::Rooted<JS::Value> value(cx, aValue); |
|
214 if (!JS_WrapValue(cx, &value)) { |
|
215 NS_WARNING("Failed to wrap value into the right compartment."); |
|
216 return; |
|
217 } |
|
218 |
|
219 ErrorResult rv; |
|
220 |
|
221 // If invoking callback threw an exception, run resolver's reject with the |
|
222 // thrown exception as argument and the synchronous flag set. |
|
223 JS::Rooted<JS::Value> retValue(cx); |
|
224 mCallback->Call(value, &retValue, rv, CallbackObject::eRethrowExceptions); |
|
225 |
|
226 rv.WouldReportJSException(); |
|
227 |
|
228 if (rv.Failed() && rv.IsJSException()) { |
|
229 JS::Rooted<JS::Value> value(cx); |
|
230 rv.StealJSException(cx, &value); |
|
231 |
|
232 if (!JS_WrapValue(cx, &value)) { |
|
233 NS_WARNING("Failed to wrap value into the right compartment."); |
|
234 return; |
|
235 } |
|
236 |
|
237 mNextPromise->RejectInternal(cx, value, Promise::SyncTask); |
|
238 return; |
|
239 } |
|
240 |
|
241 // If the return value is the same as the promise itself, throw TypeError. |
|
242 if (retValue.isObject()) { |
|
243 JS::Rooted<JSObject*> valueObj(cx, &retValue.toObject()); |
|
244 Promise* returnedPromise; |
|
245 nsresult r = UNWRAP_OBJECT(Promise, valueObj, returnedPromise); |
|
246 |
|
247 if (NS_SUCCEEDED(r) && returnedPromise == mNextPromise) { |
|
248 const char* fileName = nullptr; |
|
249 uint32_t lineNumber = 0; |
|
250 |
|
251 // Try to get some information about the callback to report a sane error, |
|
252 // but don't try too hard (only deals with scripted functions). |
|
253 JS::Rooted<JSObject*> unwrapped(cx, |
|
254 js::CheckedUnwrap(mCallback->Callback())); |
|
255 |
|
256 if (unwrapped) { |
|
257 JSAutoCompartment ac(cx, unwrapped); |
|
258 if (JS_ObjectIsFunction(cx, unwrapped)) { |
|
259 JS::Rooted<JS::Value> asValue(cx, JS::ObjectValue(*unwrapped)); |
|
260 JS::Rooted<JSFunction*> func(cx, JS_ValueToFunction(cx, asValue)); |
|
261 |
|
262 MOZ_ASSERT(func); |
|
263 JSScript* script = JS_GetFunctionScript(cx, func); |
|
264 if (script) { |
|
265 fileName = JS_GetScriptFilename(script); |
|
266 lineNumber = JS_GetScriptBaseLineNumber(cx, script); |
|
267 } |
|
268 } |
|
269 } |
|
270 |
|
271 // We're back in aValue's compartment here. |
|
272 JS::Rooted<JSString*> stack(cx, JS_GetEmptyString(JS_GetRuntime(cx))); |
|
273 JS::Rooted<JSString*> fn(cx, JS_NewStringCopyZ(cx, fileName)); |
|
274 if (!fn) { |
|
275 // Out of memory. Promise will stay unresolved. |
|
276 JS_ClearPendingException(cx); |
|
277 return; |
|
278 } |
|
279 |
|
280 JS::Rooted<JSString*> message(cx, |
|
281 JS_NewStringCopyZ(cx, |
|
282 "then() cannot return same Promise that it resolves.")); |
|
283 if (!message) { |
|
284 // Out of memory. Promise will stay unresolved. |
|
285 JS_ClearPendingException(cx); |
|
286 return; |
|
287 } |
|
288 |
|
289 JS::Rooted<JS::Value> typeError(cx); |
|
290 if (!JS::CreateTypeError(cx, stack, fn, lineNumber, 0, |
|
291 nullptr, message, &typeError)) { |
|
292 // Out of memory. Promise will stay unresolved. |
|
293 JS_ClearPendingException(cx); |
|
294 return; |
|
295 } |
|
296 |
|
297 mNextPromise->RejectInternal(cx, typeError, Promise::SyncTask); |
|
298 return; |
|
299 } |
|
300 } |
|
301 |
|
302 // Otherwise, run resolver's resolve with value and the synchronous flag |
|
303 // set. |
|
304 if (!JS_WrapValue(cx, &retValue)) { |
|
305 NS_WARNING("Failed to wrap value into the right compartment."); |
|
306 return; |
|
307 } |
|
308 |
|
309 mNextPromise->ResolveInternal(cx, retValue, Promise::SyncTask); |
|
310 } |
|
311 |
|
312 // NativePromiseCallback |
|
313 |
|
314 NS_IMPL_CYCLE_COLLECTION_INHERITED(NativePromiseCallback, |
|
315 PromiseCallback, mHandler) |
|
316 |
|
317 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(NativePromiseCallback) |
|
318 NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) |
|
319 |
|
320 NS_IMPL_ADDREF_INHERITED(NativePromiseCallback, PromiseCallback) |
|
321 NS_IMPL_RELEASE_INHERITED(NativePromiseCallback, PromiseCallback) |
|
322 |
|
323 NativePromiseCallback::NativePromiseCallback(PromiseNativeHandler* aHandler, |
|
324 Promise::PromiseState aState) |
|
325 : mHandler(aHandler) |
|
326 , mState(aState) |
|
327 { |
|
328 MOZ_ASSERT(aHandler); |
|
329 MOZ_COUNT_CTOR(NativePromiseCallback); |
|
330 } |
|
331 |
|
332 NativePromiseCallback::~NativePromiseCallback() |
|
333 { |
|
334 MOZ_COUNT_DTOR(NativePromiseCallback); |
|
335 } |
|
336 |
|
337 void |
|
338 NativePromiseCallback::Call(JS::Handle<JS::Value> aValue) |
|
339 { |
|
340 if (mState == Promise::Resolved) { |
|
341 mHandler->ResolvedCallback(aValue); |
|
342 return; |
|
343 } |
|
344 |
|
345 if (mState == Promise::Rejected) { |
|
346 mHandler->RejectedCallback(aValue); |
|
347 return; |
|
348 } |
|
349 |
|
350 NS_NOTREACHED("huh?"); |
|
351 } |
|
352 |
|
353 /* static */ PromiseCallback* |
|
354 PromiseCallback::Factory(Promise* aNextPromise, JS::Handle<JSObject*> aGlobal, |
|
355 AnyCallback* aCallback, Task aTask) |
|
356 { |
|
357 MOZ_ASSERT(aNextPromise); |
|
358 |
|
359 // If we have a callback and a next resolver, we have to exec the callback and |
|
360 // then propagate the return value to the next resolver->resolve(). |
|
361 if (aCallback) { |
|
362 return new WrapperPromiseCallback(aNextPromise, aGlobal, aCallback); |
|
363 } |
|
364 |
|
365 if (aTask == Resolve) { |
|
366 return new ResolvePromiseCallback(aNextPromise, aGlobal); |
|
367 } |
|
368 |
|
369 if (aTask == Reject) { |
|
370 return new RejectPromiseCallback(aNextPromise, aGlobal); |
|
371 } |
|
372 |
|
373 MOZ_ASSERT(false, "This should not happen"); |
|
374 return nullptr; |
|
375 } |
|
376 |
|
377 } // namespace dom |
|
378 } // namespace mozilla |