Bug 1639230: Remove vendor prefix for printing with webdriver r=jgraham,webdriver...
[gecko.git] / dom / promise / PromiseDebugging.cpp
blob8c129a38744a7a45b189f5adb6f001d011f382b6
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/. */
7 #include "js/Value.h"
8 #include "nsThreadUtils.h"
10 #include "mozilla/CycleCollectedJSContext.h"
11 #include "mozilla/RefPtr.h"
12 #include "mozilla/SchedulerGroup.h"
13 #include "mozilla/ThreadLocal.h"
14 #include "mozilla/TimeStamp.h"
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"
23 namespace mozilla {
24 namespace dom {
26 class FlushRejections : public CancelableRunnable {
27 public:
28 FlushRejections() : CancelableRunnable("dom::FlushRejections") {}
30 static void Init() {
31 if (!sDispatched.init()) {
32 MOZ_CRASH("Could not initialize FlushRejections::sDispatched");
34 sDispatched.set(false);
37 static void DispatchNeeded() {
38 if (sDispatched.get()) {
39 // An instance of `FlushRejections` has already been dispatched
40 // and not run yet. No need to dispatch another one.
41 return;
43 sDispatched.set(true);
45 // Dispatch the runnable to the current thread where
46 // the Promise was rejected, e.g. workers or worklets.
47 NS_DispatchToCurrentThread(new FlushRejections());
50 static void FlushSync() {
51 sDispatched.set(false);
53 // Call the callbacks if necessary.
54 // Note that these callbacks may in turn cause Promise to turn
55 // uncaught or consumed. Since `sDispatched` is `false`,
56 // `FlushRejections` will be called once again, on an ulterior
57 // tick.
58 PromiseDebugging::FlushUncaughtRejectionsInternal();
61 NS_IMETHOD Run() override {
62 FlushSync();
63 return NS_OK;
66 private:
67 // `true` if an instance of `FlushRejections` is currently dispatched
68 // and has not been executed yet.
69 static MOZ_THREAD_LOCAL(bool) sDispatched;
72 /* static */ MOZ_THREAD_LOCAL(bool) FlushRejections::sDispatched;
74 /* static */
75 void PromiseDebugging::GetState(GlobalObject& aGlobal,
76 JS::Handle<JSObject*> aPromise,
77 PromiseDebuggingStateHolder& aState,
78 ErrorResult& aRv) {
79 JSContext* cx = aGlobal.Context();
80 // CheckedUnwrapStatic is fine, since we're looking for promises only.
81 JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrapStatic(aPromise));
82 if (!obj || !JS::IsPromiseObject(obj)) {
83 aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>();
84 return;
86 switch (JS::GetPromiseState(obj)) {
87 case JS::PromiseState::Pending:
88 aState.mState = PromiseDebuggingState::Pending;
89 break;
90 case JS::PromiseState::Fulfilled:
91 aState.mState = PromiseDebuggingState::Fulfilled;
92 aState.mValue = JS::GetPromiseResult(obj);
93 break;
94 case JS::PromiseState::Rejected:
95 aState.mState = PromiseDebuggingState::Rejected;
96 aState.mReason = JS::GetPromiseResult(obj);
97 break;
101 /* static */
102 void PromiseDebugging::GetPromiseID(GlobalObject& aGlobal,
103 JS::Handle<JSObject*> aPromise,
104 nsString& aID, ErrorResult& aRv) {
105 JSContext* cx = aGlobal.Context();
106 // CheckedUnwrapStatic is fine, since we're looking for promises only.
107 JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrapStatic(aPromise));
108 if (!obj || !JS::IsPromiseObject(obj)) {
109 aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>();
110 return;
112 uint64_t promiseID = JS::GetPromiseID(obj);
113 aID = sIDPrefix;
114 aID.AppendInt(promiseID);
117 /* static */
118 void PromiseDebugging::GetAllocationStack(GlobalObject& aGlobal,
119 JS::Handle<JSObject*> aPromise,
120 JS::MutableHandle<JSObject*> aStack,
121 ErrorResult& aRv) {
122 JSContext* cx = aGlobal.Context();
123 // CheckedUnwrapStatic is fine, since we're looking for promises only.
124 JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrapStatic(aPromise));
125 if (!obj || !JS::IsPromiseObject(obj)) {
126 aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>();
127 return;
129 aStack.set(JS::GetPromiseAllocationSite(obj));
132 /* static */
133 void PromiseDebugging::GetRejectionStack(GlobalObject& aGlobal,
134 JS::Handle<JSObject*> aPromise,
135 JS::MutableHandle<JSObject*> aStack,
136 ErrorResult& aRv) {
137 JSContext* cx = aGlobal.Context();
138 // CheckedUnwrapStatic is fine, since we're looking for promises only.
139 JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrapStatic(aPromise));
140 if (!obj || !JS::IsPromiseObject(obj)) {
141 aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>();
142 return;
144 aStack.set(JS::GetPromiseResolutionSite(obj));
147 /* static */
148 void PromiseDebugging::GetFullfillmentStack(GlobalObject& aGlobal,
149 JS::Handle<JSObject*> aPromise,
150 JS::MutableHandle<JSObject*> aStack,
151 ErrorResult& aRv) {
152 JSContext* cx = aGlobal.Context();
153 // CheckedUnwrapStatic is fine, since we're looking for promises only.
154 JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrapStatic(aPromise));
155 if (!obj || !JS::IsPromiseObject(obj)) {
156 aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>();
157 return;
159 aStack.set(JS::GetPromiseResolutionSite(obj));
162 /*static */
163 nsString PromiseDebugging::sIDPrefix;
165 /* static */
166 void PromiseDebugging::Init() {
167 FlushRejections::Init();
169 // Generate a prefix for identifiers: "PromiseDebugging.$processid."
170 sIDPrefix = NS_LITERAL_STRING("PromiseDebugging.");
171 if (XRE_IsContentProcess()) {
172 sIDPrefix.AppendInt(ContentChild::GetSingleton()->GetID());
173 sIDPrefix.Append('.');
174 } else {
175 sIDPrefix.AppendLiteral("0.");
179 /* static */
180 void PromiseDebugging::Shutdown() { sIDPrefix.SetIsVoid(true); }
182 /* static */
183 void PromiseDebugging::FlushUncaughtRejections() {
184 MOZ_ASSERT(!NS_IsMainThread());
185 FlushRejections::FlushSync();
188 /* static */
189 void PromiseDebugging::AddUncaughtRejectionObserver(
190 GlobalObject&, UncaughtRejectionObserver& aObserver) {
191 CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
192 nsTArray<nsCOMPtr<nsISupports>>& observers =
193 storage->mUncaughtRejectionObservers;
194 observers.AppendElement(&aObserver);
197 /* static */
198 bool PromiseDebugging::RemoveUncaughtRejectionObserver(
199 GlobalObject&, UncaughtRejectionObserver& aObserver) {
200 CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
201 nsTArray<nsCOMPtr<nsISupports>>& observers =
202 storage->mUncaughtRejectionObservers;
203 for (size_t i = 0; i < observers.Length(); ++i) {
204 UncaughtRejectionObserver* observer =
205 static_cast<UncaughtRejectionObserver*>(observers[i].get());
206 if (*observer == aObserver) {
207 observers.RemoveElementAt(i);
208 return true;
211 return false;
214 /* static */
215 void PromiseDebugging::AddUncaughtRejection(JS::HandleObject aPromise) {
216 // This might OOM, but won't set a pending exception, so we'll just ignore it.
217 if (CycleCollectedJSContext::Get()->mUncaughtRejections.append(aPromise)) {
218 FlushRejections::DispatchNeeded();
222 /* void */
223 void PromiseDebugging::AddConsumedRejection(JS::HandleObject aPromise) {
224 // If the promise is in our list of uncaught rejections, we haven't yet
225 // reported it as unhandled. In that case, just remove it from the list
226 // and don't add it to the list of consumed rejections.
227 auto& uncaughtRejections =
228 CycleCollectedJSContext::Get()->mUncaughtRejections;
229 for (size_t i = 0; i < uncaughtRejections.length(); i++) {
230 if (uncaughtRejections[i] == aPromise) {
231 // To avoid large amounts of memmoves, we don't shrink the vector here.
232 // Instead, we filter out nullptrs when iterating over the vector later.
233 uncaughtRejections[i].set(nullptr);
234 return;
237 // This might OOM, but won't set a pending exception, so we'll just ignore it.
238 if (CycleCollectedJSContext::Get()->mConsumedRejections.append(aPromise)) {
239 FlushRejections::DispatchNeeded();
243 /* static */
244 void PromiseDebugging::FlushUncaughtRejectionsInternal() {
245 CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
247 auto& uncaught = storage->mUncaughtRejections;
248 auto& consumed = storage->mConsumedRejections;
250 AutoJSAPI jsapi;
251 jsapi.Init();
252 JSContext* cx = jsapi.cx();
254 // Notify observers of uncaught Promise.
255 auto& observers = storage->mUncaughtRejectionObservers;
257 for (size_t i = 0; i < uncaught.length(); i++) {
258 JS::RootedObject promise(cx, uncaught[i]);
259 // Filter out nullptrs which might've been added by
260 // PromiseDebugging::AddConsumedRejection.
261 if (!promise) {
262 continue;
265 bool suppressReporting = false;
266 for (size_t j = 0; j < observers.Length(); ++j) {
267 RefPtr<UncaughtRejectionObserver> obs =
268 static_cast<UncaughtRejectionObserver*>(observers[j].get());
270 if (obs->OnLeftUncaught(promise, IgnoreErrors())) {
271 suppressReporting = true;
275 if (!suppressReporting) {
276 JSAutoRealm ar(cx, promise);
277 Promise::ReportRejectedPromise(cx, promise);
280 storage->mUncaughtRejections.clear();
282 // Notify observers of consumed Promise.
284 for (size_t i = 0; i < consumed.length(); i++) {
285 JS::RootedObject promise(cx, consumed[i]);
287 for (size_t j = 0; j < observers.Length(); ++j) {
288 RefPtr<UncaughtRejectionObserver> obs =
289 static_cast<UncaughtRejectionObserver*>(observers[j].get());
291 obs->OnConsumed(promise, IgnoreErrors());
294 storage->mConsumedRejections.clear();
297 } // namespace dom
298 } // namespace mozilla