PromiseDebugging.cpp 9.4 KB

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