tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PromiseDebugging.cpp (10011B)


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