Line data Source code
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 "js/Value.h"
8 : #include "nsThreadUtils.h"
9 :
10 : #include "mozilla/CycleCollectedJSContext.h"
11 : #include "mozilla/RefPtr.h"
12 : #include "mozilla/SystemGroup.h"
13 : #include "mozilla/ThreadLocal.h"
14 : #include "mozilla/TimeStamp.h"
15 :
16 : #include "mozilla/dom/BindingDeclarations.h"
17 : #include "mozilla/dom/ContentChild.h"
18 : #include "mozilla/dom/Promise.h"
19 : #include "mozilla/dom/PromiseBinding.h"
20 : #include "mozilla/dom/PromiseDebugging.h"
21 : #include "mozilla/dom/PromiseDebuggingBinding.h"
22 :
23 : namespace mozilla {
24 : namespace dom {
25 :
26 0 : class FlushRejections: public CancelableRunnable
27 : {
28 : public:
29 0 : FlushRejections() : CancelableRunnable("dom::FlushRejections") {}
30 :
31 3 : static void Init() {
32 3 : if (!sDispatched.init()) {
33 0 : MOZ_CRASH("Could not initialize FlushRejections::sDispatched");
34 : }
35 3 : sDispatched.set(false);
36 3 : }
37 :
38 0 : static void DispatchNeeded() {
39 0 : if (sDispatched.get()) {
40 : // An instance of `FlushRejections` has already been dispatched
41 : // and not run yet. No need to dispatch another one.
42 0 : return;
43 : }
44 0 : sDispatched.set(true);
45 0 : SystemGroup::Dispatch("FlushRejections", TaskCategory::Other,
46 0 : do_AddRef(new FlushRejections()));
47 : }
48 :
49 0 : static void FlushSync() {
50 0 : sDispatched.set(false);
51 :
52 : // Call the callbacks if necessary.
53 : // Note that these callbacks may in turn cause Promise to turn
54 : // uncaught or consumed. Since `sDispatched` is `false`,
55 : // `FlushRejections` will be called once again, on an ulterior
56 : // tick.
57 0 : PromiseDebugging::FlushUncaughtRejectionsInternal();
58 0 : }
59 :
60 0 : NS_IMETHOD Run() override {
61 0 : FlushSync();
62 0 : return NS_OK;
63 : }
64 :
65 : private:
66 : // `true` if an instance of `FlushRejections` is currently dispatched
67 : // and has not been executed yet.
68 : static MOZ_THREAD_LOCAL(bool) sDispatched;
69 : };
70 :
71 : /* static */ MOZ_THREAD_LOCAL(bool)
72 : FlushRejections::sDispatched;
73 :
74 : /* static */ void
75 0 : PromiseDebugging::GetState(GlobalObject& aGlobal, JS::Handle<JSObject*> aPromise,
76 : PromiseDebuggingStateHolder& aState,
77 : ErrorResult& aRv)
78 : {
79 0 : JSContext* cx = aGlobal.Context();
80 0 : JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(aPromise));
81 0 : if (!obj || !JS::IsPromiseObject(obj)) {
82 0 : aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING(
83 0 : "Argument of PromiseDebugging.getState"));
84 0 : return;
85 : }
86 0 : switch (JS::GetPromiseState(obj)) {
87 : case JS::PromiseState::Pending:
88 0 : aState.mState = PromiseDebuggingState::Pending;
89 0 : break;
90 : case JS::PromiseState::Fulfilled:
91 0 : aState.mState = PromiseDebuggingState::Fulfilled;
92 0 : aState.mValue = JS::GetPromiseResult(obj);
93 0 : break;
94 : case JS::PromiseState::Rejected:
95 0 : aState.mState = PromiseDebuggingState::Rejected;
96 0 : aState.mReason = JS::GetPromiseResult(obj);
97 0 : break;
98 : }
99 : }
100 :
101 : /* static */ void
102 0 : PromiseDebugging::GetPromiseID(GlobalObject& aGlobal,
103 : JS::Handle<JSObject*> aPromise,
104 : nsString& aID,
105 : ErrorResult& aRv)
106 : {
107 0 : JSContext* cx = aGlobal.Context();
108 0 : JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(aPromise));
109 0 : if (!obj || !JS::IsPromiseObject(obj)) {
110 0 : aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING(
111 0 : "Argument of PromiseDebugging.getState"));
112 0 : return;
113 : }
114 0 : uint64_t promiseID = JS::GetPromiseID(obj);
115 0 : aID = sIDPrefix;
116 0 : aID.AppendInt(promiseID);
117 : }
118 :
119 : /* static */ void
120 0 : PromiseDebugging::GetAllocationStack(GlobalObject& aGlobal,
121 : JS::Handle<JSObject*> aPromise,
122 : JS::MutableHandle<JSObject*> aStack,
123 : ErrorResult& aRv)
124 : {
125 0 : JSContext* cx = aGlobal.Context();
126 0 : JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(aPromise));
127 0 : if (!obj || !JS::IsPromiseObject(obj)) {
128 0 : aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING(
129 0 : "Argument of PromiseDebugging.getAllocationStack"));
130 0 : return;
131 : }
132 0 : aStack.set(JS::GetPromiseAllocationSite(obj));
133 : }
134 :
135 : /* static */ void
136 0 : PromiseDebugging::GetRejectionStack(GlobalObject& aGlobal,
137 : JS::Handle<JSObject*> aPromise,
138 : JS::MutableHandle<JSObject*> aStack,
139 : ErrorResult& aRv)
140 : {
141 0 : JSContext* cx = aGlobal.Context();
142 0 : JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(aPromise));
143 0 : if (!obj || !JS::IsPromiseObject(obj)) {
144 0 : aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING(
145 0 : "Argument of PromiseDebugging.getRejectionStack"));
146 0 : return;
147 : }
148 0 : aStack.set(JS::GetPromiseResolutionSite(obj));
149 : }
150 :
151 : /* static */ void
152 0 : PromiseDebugging::GetFullfillmentStack(GlobalObject& aGlobal,
153 : JS::Handle<JSObject*> aPromise,
154 : JS::MutableHandle<JSObject*> aStack,
155 : ErrorResult& aRv)
156 : {
157 0 : JSContext* cx = aGlobal.Context();
158 0 : JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(aPromise));
159 0 : if (!obj || !JS::IsPromiseObject(obj)) {
160 0 : aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING(
161 0 : "Argument of PromiseDebugging.getFulfillmentStack"));
162 0 : return;
163 : }
164 0 : aStack.set(JS::GetPromiseResolutionSite(obj));
165 : }
166 :
167 : /*static */ nsString
168 3 : PromiseDebugging::sIDPrefix;
169 :
170 : /* static */ void
171 3 : PromiseDebugging::Init()
172 : {
173 3 : FlushRejections::Init();
174 :
175 : // Generate a prefix for identifiers: "PromiseDebugging.$processid."
176 3 : sIDPrefix = NS_LITERAL_STRING("PromiseDebugging.");
177 3 : if (XRE_IsContentProcess()) {
178 2 : sIDPrefix.AppendInt(ContentChild::GetSingleton()->GetID());
179 2 : sIDPrefix.Append('.');
180 : } else {
181 1 : sIDPrefix.AppendLiteral("0.");
182 : }
183 3 : }
184 :
185 : /* static */ void
186 0 : PromiseDebugging::Shutdown()
187 : {
188 0 : sIDPrefix.SetIsVoid(true);
189 0 : }
190 :
191 : /* static */ void
192 0 : PromiseDebugging::FlushUncaughtRejections()
193 : {
194 0 : MOZ_ASSERT(!NS_IsMainThread());
195 0 : FlushRejections::FlushSync();
196 0 : }
197 :
198 : /* static */ void
199 0 : PromiseDebugging::AddUncaughtRejectionObserver(GlobalObject&,
200 : UncaughtRejectionObserver& aObserver)
201 : {
202 0 : CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
203 0 : nsTArray<nsCOMPtr<nsISupports>>& observers = storage->mUncaughtRejectionObservers;
204 0 : observers.AppendElement(&aObserver);
205 0 : }
206 :
207 : /* static */ bool
208 0 : PromiseDebugging::RemoveUncaughtRejectionObserver(GlobalObject&,
209 : UncaughtRejectionObserver& aObserver)
210 : {
211 0 : CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
212 0 : nsTArray<nsCOMPtr<nsISupports>>& observers = storage->mUncaughtRejectionObservers;
213 0 : for (size_t i = 0; i < observers.Length(); ++i) {
214 0 : UncaughtRejectionObserver* observer = static_cast<UncaughtRejectionObserver*>(observers[i].get());
215 0 : if (*observer == aObserver) {
216 0 : observers.RemoveElementAt(i);
217 0 : return true;
218 : }
219 : }
220 0 : return false;
221 : }
222 :
223 : /* static */ void
224 0 : PromiseDebugging::AddUncaughtRejection(JS::HandleObject aPromise)
225 : {
226 : // This might OOM, but won't set a pending exception, so we'll just ignore it.
227 0 : if (CycleCollectedJSContext::Get()->mUncaughtRejections.append(aPromise)) {
228 0 : FlushRejections::DispatchNeeded();
229 : }
230 0 : }
231 :
232 : /* void */ void
233 0 : PromiseDebugging::AddConsumedRejection(JS::HandleObject aPromise)
234 : {
235 : // If the promise is in our list of uncaught rejections, we haven't yet
236 : // reported it as unhandled. In that case, just remove it from the list
237 : // and don't add it to the list of consumed rejections.
238 0 : auto& uncaughtRejections = CycleCollectedJSContext::Get()->mUncaughtRejections;
239 0 : for (size_t i = 0; i < uncaughtRejections.length(); i++) {
240 0 : if (uncaughtRejections[i] == aPromise) {
241 : // To avoid large amounts of memmoves, we don't shrink the vector here.
242 : // Instead, we filter out nullptrs when iterating over the vector later.
243 0 : uncaughtRejections[i].set(nullptr);
244 0 : return;
245 : }
246 : }
247 : // This might OOM, but won't set a pending exception, so we'll just ignore it.
248 0 : if (CycleCollectedJSContext::Get()->mConsumedRejections.append(aPromise)) {
249 0 : FlushRejections::DispatchNeeded();
250 : }
251 : }
252 :
253 : /* static */ void
254 0 : PromiseDebugging::FlushUncaughtRejectionsInternal()
255 : {
256 0 : CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
257 :
258 0 : auto& uncaught = storage->mUncaughtRejections;
259 0 : auto& consumed = storage->mConsumedRejections;
260 :
261 0 : AutoJSAPI jsapi;
262 0 : jsapi.Init();
263 0 : JSContext* cx = jsapi.cx();
264 :
265 : // Notify observers of uncaught Promise.
266 0 : auto& observers = storage->mUncaughtRejectionObservers;
267 :
268 0 : for (size_t i = 0; i < uncaught.length(); i++) {
269 0 : JS::RootedObject promise(cx, uncaught[i]);
270 : // Filter out nullptrs which might've been added by
271 : // PromiseDebugging::AddConsumedRejection.
272 0 : if (!promise) {
273 0 : continue;
274 : }
275 :
276 0 : for (size_t j = 0; j < observers.Length(); ++j) {
277 : RefPtr<UncaughtRejectionObserver> obs =
278 0 : static_cast<UncaughtRejectionObserver*>(observers[j].get());
279 :
280 0 : IgnoredErrorResult err;
281 0 : obs->OnLeftUncaught(promise, err);
282 : }
283 0 : JSAutoCompartment ac(cx, promise);
284 0 : Promise::ReportRejectedPromise(cx, promise);
285 : }
286 0 : storage->mUncaughtRejections.clear();
287 :
288 : // Notify observers of consumed Promise.
289 :
290 0 : for (size_t i = 0; i < consumed.length(); i++) {
291 0 : JS::RootedObject promise(cx, consumed[i]);
292 :
293 0 : for (size_t j = 0; j < observers.Length(); ++j) {
294 : RefPtr<UncaughtRejectionObserver> obs =
295 0 : static_cast<UncaughtRejectionObserver*>(observers[j].get());
296 :
297 0 : IgnoredErrorResult err;
298 0 : obs->OnConsumed(promise, err);
299 : }
300 : }
301 0 : storage->mConsumedRejections.clear();
302 0 : }
303 :
304 : } // namespace dom
305 9 : } // namespace mozilla
|