Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
7 #include "PromiseCallback.h"
8 #include "mozilla/dom/Promise.h"
9 #include "mozilla/dom/PromiseNativeHandler.h"
11 #include "js/OldDebugAPI.h"
13 namespace mozilla {
14 namespace dom {
16 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseCallback)
17 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseCallback)
19 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseCallback)
20 NS_INTERFACE_MAP_ENTRY(nsISupports)
21 NS_INTERFACE_MAP_END
23 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseCallback)
25 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseCallback)
26 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
28 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseCallback)
29 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
31 PromiseCallback::PromiseCallback()
32 {
33 MOZ_COUNT_CTOR(PromiseCallback);
34 }
36 PromiseCallback::~PromiseCallback()
37 {
38 MOZ_COUNT_DTOR(PromiseCallback);
39 }
41 // ResolvePromiseCallback
43 NS_IMPL_CYCLE_COLLECTION_CLASS(ResolvePromiseCallback)
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
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
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
61 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ResolvePromiseCallback)
62 NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)
64 NS_IMPL_ADDREF_INHERITED(ResolvePromiseCallback, PromiseCallback)
65 NS_IMPL_RELEASE_INHERITED(ResolvePromiseCallback, PromiseCallback)
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 }
78 ResolvePromiseCallback::~ResolvePromiseCallback()
79 {
80 MOZ_COUNT_DTOR(ResolvePromiseCallback);
81 DropJSObjects(this);
82 }
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;
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 }
97 mPromise->ResolveInternal(cx, value, Promise::SyncTask);
98 }
100 // RejectPromiseCallback
102 NS_IMPL_CYCLE_COLLECTION_CLASS(RejectPromiseCallback)
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
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
116 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(RejectPromiseCallback)
117 NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)
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
124 NS_IMPL_ADDREF_INHERITED(RejectPromiseCallback, PromiseCallback)
125 NS_IMPL_RELEASE_INHERITED(RejectPromiseCallback, PromiseCallback)
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 }
138 RejectPromiseCallback::~RejectPromiseCallback()
139 {
140 MOZ_COUNT_DTOR(RejectPromiseCallback);
141 DropJSObjects(this);
142 }
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;
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 }
158 mPromise->RejectInternal(cx, value, Promise::SyncTask);
159 }
161 // WrapperPromiseCallback
162 NS_IMPL_CYCLE_COLLECTION_CLASS(WrapperPromiseCallback)
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
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
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
182 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WrapperPromiseCallback)
183 NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)
185 NS_IMPL_ADDREF_INHERITED(WrapperPromiseCallback, PromiseCallback)
186 NS_IMPL_RELEASE_INHERITED(WrapperPromiseCallback, PromiseCallback)
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 }
201 WrapperPromiseCallback::~WrapperPromiseCallback()
202 {
203 MOZ_COUNT_DTOR(WrapperPromiseCallback);
204 DropJSObjects(this);
205 }
207 void
208 WrapperPromiseCallback::Call(JS::Handle<JS::Value> aValue)
209 {
210 ThreadsafeAutoSafeJSContext cx;
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 }
219 ErrorResult rv;
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);
226 rv.WouldReportJSException();
228 if (rv.Failed() && rv.IsJSException()) {
229 JS::Rooted<JS::Value> value(cx);
230 rv.StealJSException(cx, &value);
232 if (!JS_WrapValue(cx, &value)) {
233 NS_WARNING("Failed to wrap value into the right compartment.");
234 return;
235 }
237 mNextPromise->RejectInternal(cx, value, Promise::SyncTask);
238 return;
239 }
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);
247 if (NS_SUCCEEDED(r) && returnedPromise == mNextPromise) {
248 const char* fileName = nullptr;
249 uint32_t lineNumber = 0;
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()));
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));
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 }
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 }
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 }
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 }
297 mNextPromise->RejectInternal(cx, typeError, Promise::SyncTask);
298 return;
299 }
300 }
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 }
309 mNextPromise->ResolveInternal(cx, retValue, Promise::SyncTask);
310 }
312 // NativePromiseCallback
314 NS_IMPL_CYCLE_COLLECTION_INHERITED(NativePromiseCallback,
315 PromiseCallback, mHandler)
317 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(NativePromiseCallback)
318 NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)
320 NS_IMPL_ADDREF_INHERITED(NativePromiseCallback, PromiseCallback)
321 NS_IMPL_RELEASE_INHERITED(NativePromiseCallback, PromiseCallback)
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 }
332 NativePromiseCallback::~NativePromiseCallback()
333 {
334 MOZ_COUNT_DTOR(NativePromiseCallback);
335 }
337 void
338 NativePromiseCallback::Call(JS::Handle<JS::Value> aValue)
339 {
340 if (mState == Promise::Resolved) {
341 mHandler->ResolvedCallback(aValue);
342 return;
343 }
345 if (mState == Promise::Rejected) {
346 mHandler->RejectedCallback(aValue);
347 return;
348 }
350 NS_NOTREACHED("huh?");
351 }
353 /* static */ PromiseCallback*
354 PromiseCallback::Factory(Promise* aNextPromise, JS::Handle<JSObject*> aGlobal,
355 AnyCallback* aCallback, Task aTask)
356 {
357 MOZ_ASSERT(aNextPromise);
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 }
365 if (aTask == Resolve) {
366 return new ResolvePromiseCallback(aNextPromise, aGlobal);
367 }
369 if (aTask == Reject) {
370 return new RejectPromiseCallback(aNextPromise, aGlobal);
371 }
373 MOZ_ASSERT(false, "This should not happen");
374 return nullptr;
375 }
377 } // namespace dom
378 } // namespace mozilla