LCOV - code coverage report
Current view: top level - dom/promise - PromiseDebugging.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 14 140 10.0 %
Date: 2017-07-14 16:53:18 Functions: 4 22 18.2 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13