tor-browser

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

AbortSignal.cpp (17048B)


      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "AbortSignal.h"
      8 
      9 #include "mozilla/RefPtr.h"
     10 #include "mozilla/dom/AbortSignalBinding.h"
     11 #include "mozilla/dom/DOMException.h"
     12 #include "mozilla/dom/Event.h"
     13 #include "mozilla/dom/EventBinding.h"
     14 #include "mozilla/dom/TimeoutHandler.h"
     15 #include "mozilla/dom/TimeoutManager.h"
     16 #include "mozilla/dom/ToJSValue.h"
     17 #include "mozilla/dom/WorkerPrivate.h"
     18 #include "nsCycleCollectionParticipant.h"
     19 #include "nsGlobalWindowInner.h"
     20 #include "nsPIDOMWindow.h"
     21 
     22 namespace mozilla::dom {
     23 
     24 // AbortSignalImpl
     25 // ----------------------------------------------------------------------------
     26 
     27 AbortSignalImpl::AbortSignalImpl(SignalAborted aAborted,
     28                                 JS::Handle<JS::Value> aReason)
     29    : mReason(aReason), mAborted(aAborted) {
     30  MOZ_ASSERT_IF(!mReason.isUndefined(), Aborted());
     31 }
     32 
     33 bool AbortSignalImpl::Aborted() const { return mAborted == SignalAborted::Yes; }
     34 
     35 void AbortSignalImpl::GetReason(JSContext* aCx,
     36                                JS::MutableHandle<JS::Value> aReason) {
     37  if (!Aborted()) {
     38    return;
     39  }
     40  MaybeAssignAbortError(aCx);
     41  aReason.set(mReason);
     42 }
     43 
     44 JS::Value AbortSignalImpl::RawReason() const { return mReason.get(); }
     45 
     46 // https://dom.spec.whatwg.org/#abortsignal-signal-abort
     47 void AbortSignalImpl::SignalAbort(JS::Handle<JS::Value> aReason) {
     48  // Step 1: If signal is aborted, then return.
     49  if (Aborted()) {
     50    return;
     51  }
     52 
     53  // Step 2: Set signal’s abort reason to reason if it is given; otherwise to a
     54  // new "AbortError" DOMException.
     55  //
     56  // (But given AbortSignalImpl is supposed to run without JS context, the
     57  // DOMException creation is deferred to the getter.)
     58  SetAborted(aReason);
     59 
     60  // Step 3 - 6
     61  SignalAbortWithDependents();
     62 }
     63 
     64 void AbortSignalImpl::SignalAbortWithDependents() {
     65  // AbortSignalImpl cannot have dependents, so just run abort steps for itself.
     66  RunAbortSteps();
     67 }
     68 
     69 // https://dom.spec.whatwg.org/#run-the-abort-steps
     70 // This skips event firing as AbortSignalImpl is not supposed to be exposed to
     71 // JS. It's done instead in AbortSignal::RunAbortSteps.
     72 void AbortSignalImpl::RunAbortSteps() {
     73  // Step 1: For each algorithm of signal’s abort algorithms: run algorithm.
     74  //
     75  // When there are multiple followers, the follower removal algorithm
     76  // https://dom.spec.whatwg.org/#abortsignal-remove could be invoked in an
     77  // earlier algorithm to remove a later algorithm, so |mFollowers| must be a
     78  // |nsTObserverArray| to defend against mutation.
     79  for (RefPtr<AbortFollower>& follower : mFollowers.ForwardRange()) {
     80    MOZ_ASSERT(follower->mFollowingSignal == this);
     81    follower->RunAbortAlgorithm();
     82  }
     83 
     84  // Step 2: Empty signal’s abort algorithms.
     85  UnlinkFollowers();
     86 }
     87 
     88 void AbortSignalImpl::SetAborted(JS::Handle<JS::Value> aReason) {
     89  mAborted = SignalAborted::Yes;
     90  mReason = aReason;
     91 }
     92 
     93 void AbortSignalImpl::Traverse(AbortSignalImpl* aSignal,
     94                               nsCycleCollectionTraversalCallback& cb) {
     95  ImplCycleCollectionTraverse(cb, aSignal->mFollowers, "mFollowers", 0);
     96 }
     97 
     98 void AbortSignalImpl::Unlink(AbortSignalImpl* aSignal) {
     99  aSignal->mReason.setUndefined();
    100  aSignal->UnlinkFollowers();
    101 }
    102 
    103 void AbortSignalImpl::MaybeAssignAbortError(JSContext* aCx) {
    104  MOZ_ASSERT(Aborted());
    105  if (!mReason.isUndefined()) {
    106    return;
    107  }
    108 
    109  JS::Rooted<JS::Value> exception(aCx);
    110  RefPtr<DOMException> dom = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
    111 
    112  if (NS_WARN_IF(!ToJSValue(aCx, dom, &exception))) {
    113    return;
    114  }
    115 
    116  mReason.set(exception);
    117 }
    118 
    119 void AbortSignalImpl::UnlinkFollowers() {
    120  // Manually unlink all followers before destructing the array, or otherwise
    121  // the array will be accessed by Unfollow() while being destructed.
    122  for (RefPtr<AbortFollower>& follower : mFollowers.ForwardRange()) {
    123    follower->mFollowingSignal = nullptr;
    124  }
    125  mFollowers.Clear();
    126 }
    127 
    128 // AbortSignal
    129 // ----------------------------------------------------------------------------
    130 
    131 NS_IMPL_CYCLE_COLLECTION_CLASS(AbortSignal)
    132 
    133 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AbortSignal,
    134                                                  DOMEventTargetHelper)
    135  AbortSignalImpl::Traverse(static_cast<AbortSignalImpl*>(tmp), cb);
    136  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDependentSignals)
    137 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    138 
    139 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AbortSignal,
    140                                                DOMEventTargetHelper)
    141  AbortSignalImpl::Unlink(static_cast<AbortSignalImpl*>(tmp));
    142  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDependentSignals)
    143 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    144 
    145 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignal)
    146 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
    147 
    148 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(AbortSignal,
    149                                               DOMEventTargetHelper)
    150  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReason)
    151 NS_IMPL_CYCLE_COLLECTION_TRACE_END
    152 
    153 NS_IMPL_ADDREF_INHERITED(AbortSignal, DOMEventTargetHelper)
    154 NS_IMPL_RELEASE_INHERITED(AbortSignal, DOMEventTargetHelper)
    155 
    156 already_AddRefed<AbortSignal> AbortSignal::Create(
    157    nsIGlobalObject* aGlobalObject, SignalAborted aAborted,
    158    JS::Handle<JS::Value> aReason) {
    159  RefPtr<AbortSignal> signal =
    160      new AbortSignal(aGlobalObject, aAborted, aReason);
    161  signal->Init();
    162  return signal.forget();
    163 }
    164 
    165 void AbortSignal::Init() {
    166  // Init is use to separate this HoldJSObjects call to avoid calling
    167  // it in the constructor.
    168  //
    169  // We can't call HoldJSObjects in the constructor because it'll
    170  // addref `this` before the vtable is set up properly, so the parent
    171  // type gets stored in the CC participant table. This is problematic
    172  // for classes that inherit AbortSignal.
    173  mozilla::HoldJSObjects(this);
    174 }
    175 
    176 AbortSignal::AbortSignal(nsIGlobalObject* aGlobalObject, SignalAborted aAborted,
    177                         JS::Handle<JS::Value> aReason)
    178    : DOMEventTargetHelper(aGlobalObject),
    179      AbortSignalImpl(aAborted, aReason),
    180      mDependent(false) {}
    181 
    182 JSObject* AbortSignal::WrapObject(JSContext* aCx,
    183                                  JS::Handle<JSObject*> aGivenProto) {
    184  return AbortSignal_Binding::Wrap(aCx, this, aGivenProto);
    185 }
    186 
    187 already_AddRefed<AbortSignal> AbortSignal::Abort(
    188    GlobalObject& aGlobal, JS::Handle<JS::Value> aReason) {
    189  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    190 
    191  RefPtr<AbortSignal> abortSignal =
    192      AbortSignal::Create(global, SignalAborted::Yes, aReason);
    193  return abortSignal.forget();
    194 }
    195 
    196 class AbortSignalTimeoutHandler final : public TimeoutHandler {
    197 public:
    198  AbortSignalTimeoutHandler(JSContext* aCx, AbortSignal* aSignal)
    199      : TimeoutHandler(aCx), mSignal(aSignal) {}
    200 
    201  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
    202  NS_DECL_CYCLE_COLLECTION_CLASS(AbortSignalTimeoutHandler)
    203 
    204  // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
    205  // Step 3
    206  MOZ_CAN_RUN_SCRIPT bool Call(const char* /* unused */) override {
    207    AutoJSAPI jsapi;
    208    if (NS_WARN_IF(!jsapi.Init(mSignal->GetParentObject()))) {
    209      // (false is only for setInterval, see
    210      // nsGlobalWindowInner::RunTimeoutHandler)
    211      return true;
    212    }
    213 
    214    // Step 1. Queue a global task on the timer task source given global to
    215    // signal abort given signal and a new "TimeoutError" DOMException.
    216    JS::Rooted<JS::Value> exception(jsapi.cx());
    217    RefPtr<DOMException> dom = DOMException::Create(NS_ERROR_DOM_TIMEOUT_ERR);
    218    if (NS_WARN_IF(!ToJSValue(jsapi.cx(), dom, &exception))) {
    219      return true;
    220    }
    221 
    222    mSignal->SignalAbort(exception);
    223    return true;
    224  }
    225 
    226 private:
    227  ~AbortSignalTimeoutHandler() override = default;
    228 
    229  RefPtr<AbortSignal> mSignal;
    230 };
    231 
    232 NS_IMPL_CYCLE_COLLECTION(AbortSignalTimeoutHandler, mSignal)
    233 NS_IMPL_CYCLE_COLLECTING_ADDREF(AbortSignalTimeoutHandler)
    234 NS_IMPL_CYCLE_COLLECTING_RELEASE(AbortSignalTimeoutHandler)
    235 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignalTimeoutHandler)
    236  NS_INTERFACE_MAP_ENTRY(nsISupports)
    237 NS_INTERFACE_MAP_END
    238 
    239 static void SetTimeoutForGlobal(GlobalObject& aGlobal, TimeoutHandler& aHandler,
    240                                int32_t timeout, ErrorResult& aRv) {
    241  if (NS_IsMainThread()) {
    242    nsCOMPtr<nsPIDOMWindowInner> innerWindow =
    243        do_QueryInterface(aGlobal.GetAsSupports());
    244    if (!innerWindow) {
    245      aRv.ThrowInvalidStateError("Could not find window.");
    246      return;
    247    }
    248 
    249    int32_t handle;
    250    nsresult rv =
    251        nsGlobalWindowInner::Cast(innerWindow)
    252            ->GetTimeoutManager()
    253            ->SetTimeout(&aHandler, timeout, /* aIsInterval */ false,
    254                         Timeout::Reason::eAbortSignalTimeout, &handle);
    255    if (NS_FAILED(rv)) {
    256      aRv.Throw(rv);
    257      return;
    258    }
    259  } else {
    260    WorkerPrivate* workerPrivate =
    261        GetWorkerPrivateFromContext(aGlobal.Context());
    262    workerPrivate->SetTimeout(aGlobal.Context(), &aHandler, timeout,
    263                              /* aIsInterval */ false,
    264                              Timeout::Reason::eAbortSignalTimeout, aRv);
    265    if (aRv.Failed()) {
    266      return;
    267    }
    268  }
    269 }
    270 
    271 // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
    272 already_AddRefed<AbortSignal> AbortSignal::Timeout(GlobalObject& aGlobal,
    273                                                   uint64_t aMilliseconds,
    274                                                   ErrorResult& aRv) {
    275  // Step 2. Let global be signal’s relevant global object.
    276  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    277 
    278  // Step 1. Let signal be a new AbortSignal object.
    279  RefPtr<AbortSignal> signal =
    280      AbortSignal::Create(global, SignalAborted::No, JS::UndefinedHandleValue);
    281 
    282  // Step 3. Run steps after a timeout given global, "AbortSignal-timeout",
    283  // milliseconds, and the following step: ...
    284  RefPtr<TimeoutHandler> handler =
    285      new AbortSignalTimeoutHandler(aGlobal.Context(), signal);
    286 
    287  // Note: We only supports int32_t range intervals
    288  int32_t timeout =
    289      aMilliseconds > uint64_t(std::numeric_limits<int32_t>::max())
    290          ? std::numeric_limits<int32_t>::max()
    291          : static_cast<int32_t>(aMilliseconds);
    292 
    293  SetTimeoutForGlobal(aGlobal, *handler, timeout, aRv);
    294  if (aRv.Failed()) {
    295    return nullptr;
    296  }
    297 
    298  // Step 4. Return signal.
    299  return signal.forget();
    300 }
    301 
    302 // https://dom.spec.whatwg.org/#create-a-dependent-abort-signal
    303 already_AddRefed<AbortSignal> AbortSignal::Any(
    304    GlobalObject& aGlobal,
    305    const Sequence<OwningNonNull<AbortSignal>>& aSignals) {
    306  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    307  return Any(global, aSignals, [](nsIGlobalObject* aGlobal) {
    308    return AbortSignal::Create(aGlobal, SignalAborted::No,
    309                               JS::UndefinedHandleValue);
    310  });
    311 }
    312 
    313 already_AddRefed<AbortSignal> AbortSignal::Any(
    314    nsIGlobalObject* aGlobal,
    315    const Span<const OwningNonNull<AbortSignal>>& aSignals,
    316    FunctionRef<already_AddRefed<AbortSignal>(nsIGlobalObject* aGlobal)>
    317        aCreateResultSignal) {
    318  // Step 1. Let resultSignal be a new object implementing AbortSignal using
    319  // realm
    320  RefPtr<AbortSignal> resultSignal = aCreateResultSignal(aGlobal);
    321 
    322  if (!aSignals.IsEmpty()) {
    323    // (Prepare for step 2 which uses the reason of this. Cannot use
    324    // RawReason because that can cause constructing new DOMException for each
    325    // dependent signal instead of sharing the single one.)
    326    AutoJSAPI jsapi;
    327    if (!jsapi.Init(aGlobal)) {
    328      return nullptr;
    329    }
    330    JSContext* cx = jsapi.cx();
    331 
    332    // Step 2. For each signal of signals: if signal is aborted, then set
    333    // resultSignal's abort reason to signal's abort reason and return
    334    // resultSignal.
    335    for (const auto& signal : aSignals) {
    336      if (signal->Aborted()) {
    337        JS::Rooted<JS::Value> reason(cx);
    338        signal->GetReason(cx, &reason);
    339        resultSignal->SetAborted(reason);
    340        return resultSignal.forget();
    341      }
    342    }
    343  }
    344 
    345  // Step 3. Set resultSignal's dependent to true
    346  resultSignal->mDependent = true;
    347 
    348  // Step 4. For each signal of signals
    349  for (const auto& signal : aSignals) {
    350    if (!signal->Dependent()) {
    351      // Step 4.1. If signal is not dependent, make resultSignal dependent on it
    352      resultSignal->MakeDependentOn(signal);
    353    } else {
    354      // Step 4.2. Otherwise, make resultSignal dependent on its source signals
    355      for (const auto& sourceSignal : signal->mSourceSignals) {
    356        if (!sourceSignal) {
    357          // Bug 1908466, sourceSignal might have been garbage collected.
    358          // As signal is not aborted, sourceSignal also wasn't.
    359          // Thus do not depend on it, as it cannot be aborted anymore.
    360          continue;
    361        }
    362        MOZ_ASSERT(!sourceSignal->Aborted() && !sourceSignal->Dependent());
    363        resultSignal->MakeDependentOn(sourceSignal);
    364      }
    365    }
    366  }
    367 
    368  // Step 5. Return resultSignal.
    369  return resultSignal.forget();
    370 }
    371 
    372 void AbortSignal::MakeDependentOn(AbortSignal* aSignal) {
    373  MOZ_ASSERT(mDependent);
    374  MOZ_ASSERT(aSignal);
    375  // append only if not already contained in list
    376  // https://infra.spec.whatwg.org/#set-append
    377  if (!mSourceSignals.Contains(aSignal)) {
    378    mSourceSignals.AppendElement(aSignal);
    379  }
    380  if (!aSignal->mDependentSignals.Contains(this)) {
    381    aSignal->mDependentSignals.AppendElement(this);
    382  }
    383 }
    384 
    385 // https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted
    386 void AbortSignal::ThrowIfAborted(JSContext* aCx, ErrorResult& aRv) {
    387  aRv.MightThrowJSException();
    388 
    389  if (Aborted()) {
    390    JS::Rooted<JS::Value> reason(aCx);
    391    GetReason(aCx, &reason);
    392    aRv.ThrowJSException(aCx, reason);
    393  }
    394 }
    395 
    396 // Step 3 - 6 of https://dom.spec.whatwg.org/#abortsignal-signal-abort
    397 void AbortSignal::SignalAbortWithDependents() {
    398  // Step 3: Let dependentSignalsToAbort be a new list.
    399  nsTArray<RefPtr<AbortSignal>> dependentSignalsToAbort;
    400 
    401  // mDependentSignals can go away after this function.
    402  nsTArray<RefPtr<AbortSignal>> dependentSignals = std::move(mDependentSignals);
    403 
    404  if (!dependentSignals.IsEmpty()) {
    405    // (Prepare for step 4.1.1 which uses the reason of this. Cannot use
    406    // RawReason because that can cause constructing new DOMException for each
    407    // dependent signal instead of sharing the single one.)
    408    AutoJSAPI jsapi;
    409    if (!jsapi.Init(GetParentObject())) {
    410      return;
    411    }
    412    JSContext* cx = jsapi.cx();
    413    JS::Rooted<JS::Value> reason(cx);
    414    GetReason(cx, &reason);
    415 
    416    // Step 4. For each dependentSignal of signal’s dependent signals:
    417    for (const auto& dependentSignal : dependentSignals) {
    418      MOZ_ASSERT(dependentSignal->mSourceSignals.Contains(this));
    419      // Step 4.1: If dependentSignal is not aborted, then:
    420      if (!dependentSignal->Aborted()) {
    421        // Step 4.1.1: Set dependentSignal’s abort reason to signal’s abort
    422        // reason.
    423        dependentSignal->SetAborted(reason);
    424        // Step 4.1.2: Append dependentSignal to dependentSignalsToAbort.
    425        dependentSignalsToAbort.AppendElement(dependentSignal);
    426      }
    427    }
    428  }
    429 
    430  // Step 5: Run the abort steps for signal.
    431  RunAbortSteps();
    432 
    433  // Step 6: For each dependentSignal of dependentSignalsToAbort, run the abort
    434  // steps for dependentSignal.
    435  for (const auto& dependentSignal : dependentSignalsToAbort) {
    436    dependentSignal->RunAbortSteps();
    437  }
    438 }
    439 
    440 // https://dom.spec.whatwg.org/#run-the-abort-steps
    441 void AbortSignal::RunAbortSteps() {
    442  // Step 1 - 2:
    443  AbortSignalImpl::RunAbortSteps();
    444 
    445  // Step 3. Fire an event named abort at this signal.
    446  EventInit init;
    447  init.mBubbles = false;
    448  init.mCancelable = false;
    449 
    450  RefPtr<Event> event = Event::Constructor(this, u"abort"_ns, init);
    451  event->SetTrusted(true);
    452 
    453  DispatchEvent(*event);
    454 }
    455 
    456 bool AbortSignal::Dependent() const { return mDependent; }
    457 
    458 AbortSignal::~AbortSignal() { mozilla::DropJSObjects(this); }
    459 
    460 // AbortFollower
    461 // ----------------------------------------------------------------------------
    462 
    463 AbortFollower::~AbortFollower() { Unfollow(); }
    464 
    465 // https://dom.spec.whatwg.org/#abortsignal-add
    466 void AbortFollower::Follow(AbortSignalImpl* aSignal) {
    467  // Step 1.
    468  if (aSignal->Aborted()) {
    469    return;
    470  }
    471 
    472  MOZ_DIAGNOSTIC_ASSERT(aSignal);
    473 
    474  Unfollow();
    475 
    476  // Step 2.
    477  mFollowingSignal = aSignal;
    478  MOZ_ASSERT(!aSignal->mFollowers.Contains(this));
    479  aSignal->mFollowers.AppendElement(this);
    480 }
    481 
    482 // https://dom.spec.whatwg.org/#abortsignal-remove
    483 void AbortFollower::Unfollow() {
    484  if (mFollowingSignal) {
    485    // |Unfollow| is called by cycle-collection unlink code that runs in no
    486    // guaranteed order.  So we can't, symmetric with |Follow| above, assert
    487    // that |this| will be found in |mFollowingSignal->mFollowers|.
    488    mFollowingSignal->mFollowers.RemoveElement(this);
    489    mFollowingSignal = nullptr;
    490  }
    491 }
    492 
    493 bool AbortFollower::IsFollowing() const { return !!mFollowingSignal; }
    494 
    495 }  // namespace mozilla::dom