/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AbortSignal.h" #include "mozilla/dom/DOMException.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/EventBinding.h" #include "mozilla/dom/AbortSignalBinding.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/RefPtr.h" #include "nsCycleCollectionParticipant.h" namespace mozilla::dom { // AbortSignalImpl // ---------------------------------------------------------------------------- AbortSignalImpl::AbortSignalImpl(bool aAborted, JS::Handle aReason) : mReason(aReason), mAborted(aAborted) { MOZ_ASSERT_IF(!mReason.isUndefined(), mAborted); } bool AbortSignalImpl::Aborted() const { return mAborted; } void AbortSignalImpl::GetReason(JSContext* aCx, JS::MutableHandle aReason) { if (!mAborted) { return; } MaybeAssignAbortError(aCx); aReason.set(mReason); } JS::Value AbortSignalImpl::RawReason() const { return mReason.get(); } // https://dom.spec.whatwg.org/#abortsignal-signal-abort steps 1-4 void AbortSignalImpl::SignalAbort(JS::Handle aReason) { // Step 1. if (mAborted) { return; } // Step 2. mAborted = true; mReason = aReason; // Step 3. // When there are multiple followers, the follower removal algorithm // https://dom.spec.whatwg.org/#abortsignal-remove could be invoked in an // earlier algorithm to remove a later algorithm, so |mFollowers| must be a // |nsTObserverArray| to defend against mutation. for (RefPtr& follower : mFollowers.ForwardRange()) { MOZ_ASSERT(follower->mFollowingSignal == this); follower->RunAbortAlgorithm(); } // Step 4. UnlinkFollowers(); } void AbortSignalImpl::Traverse(AbortSignalImpl* aSignal, nsCycleCollectionTraversalCallback& cb) { ImplCycleCollectionTraverse(cb, aSignal->mFollowers, "mFollowers", 0); } void AbortSignalImpl::Unlink(AbortSignalImpl* aSignal) { aSignal->mReason.setUndefined(); aSignal->UnlinkFollowers(); } void AbortSignalImpl::MaybeAssignAbortError(JSContext* aCx) { MOZ_ASSERT(mAborted); if (!mReason.isUndefined()) { return; } JS::Rooted exception(aCx); RefPtr dom = DOMException::Create(NS_ERROR_DOM_ABORT_ERR); if (NS_WARN_IF(!ToJSValue(aCx, dom, &exception))) { return; } mReason.set(exception); } void AbortSignalImpl::UnlinkFollowers() { // Manually unlink all followers before destructing the array, or otherwise // the array will be accessed by Unfollow() while being destructed. for (RefPtr& follower : mFollowers.ForwardRange()) { follower->mFollowingSignal = nullptr; } mFollowers.Clear(); } // AbortSignal // ---------------------------------------------------------------------------- NS_IMPL_CYCLE_COLLECTION_CLASS(AbortSignal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AbortSignal, DOMEventTargetHelper) AbortSignalImpl::Traverse(static_cast(tmp), cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AbortSignal, DOMEventTargetHelper) AbortSignalImpl::Unlink(static_cast(tmp)); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignal) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(AbortSignal, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReason) NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_ADDREF_INHERITED(AbortSignal, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(AbortSignal, DOMEventTargetHelper) AbortSignal::AbortSignal(nsIGlobalObject* aGlobalObject, bool aAborted, JS::Handle aReason) : DOMEventTargetHelper(aGlobalObject), AbortSignalImpl(aAborted, aReason) { mozilla::HoldJSObjects(this); } JSObject* AbortSignal::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return AbortSignal_Binding::Wrap(aCx, this, aGivenProto); } already_AddRefed AbortSignal::Abort(GlobalObject& aGlobal, JS::Handle aReason, ErrorResult& aRv) { nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); RefPtr abortSignal = new AbortSignal(global, true, aReason); return abortSignal.forget(); } // https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted void AbortSignal::ThrowIfAborted(JSContext* aCx, ErrorResult& aRv) { aRv.MightThrowJSException(); if (Aborted()) { JS::Rooted reason(aCx); GetReason(aCx, &reason); aRv.ThrowJSException(aCx, reason); } } // https://dom.spec.whatwg.org/#abortsignal-signal-abort void AbortSignal::SignalAbort(JS::Handle aReason) { // Step 1, in case "signal abort" algorithm is called directly if (Aborted()) { return; } // Steps 1-4. AbortSignalImpl::SignalAbort(aReason); // Step 5. EventInit init; init.mBubbles = false; init.mCancelable = false; RefPtr event = Event::Constructor(this, u"abort"_ns, init); event->SetTrusted(true); DispatchEvent(*event); } void AbortSignal::RunAbortAlgorithm() { JS::Rooted reason(RootingCx(), Signal()->RawReason()); SignalAbort(reason); } AbortSignal::~AbortSignal() { mozilla::DropJSObjects(this); } // AbortFollower // ---------------------------------------------------------------------------- AbortFollower::~AbortFollower() { Unfollow(); } // https://dom.spec.whatwg.org/#abortsignal-add void AbortFollower::Follow(AbortSignalImpl* aSignal) { // Step 1. if (aSignal->mAborted) { return; } MOZ_DIAGNOSTIC_ASSERT(aSignal); Unfollow(); // Step 2. mFollowingSignal = aSignal; MOZ_ASSERT(!aSignal->mFollowers.Contains(this)); aSignal->mFollowers.AppendElement(this); } // https://dom.spec.whatwg.org/#abortsignal-remove void AbortFollower::Unfollow() { if (mFollowingSignal) { // |Unfollow| is called by cycle-collection unlink code that runs in no // guaranteed order. So we can't, symmetric with |Follow| above, assert // that |this| will be found in |mFollowingSignal->mFollowers|. mFollowingSignal->mFollowers.RemoveElement(this); mFollowingSignal = nullptr; } } bool AbortFollower::IsFollowing() const { return !!mFollowingSignal; } } // namespace mozilla::dom