/* 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 https://mozilla.org/MPL/2.0/. */ use std::cell::{Cell, RefCell}; use std::rc::Rc; use std::sync::{Arc, Mutex}; use dom_struct::dom_struct; use indexmap::IndexSet; use js::jsapi::{ExceptionStackBehavior, Heap, JS_SetPendingException}; use js::jsval::{JSVal, UndefinedValue}; use js::rust::{HandleObject, HandleValue, MutableHandleValue}; use script_bindings::inheritance::Castable; use script_bindings::trace::CustomTraceable; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods; use crate::dom::bindings::codegen::Bindings::EventListenerBinding::EventListener; use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventListenerOptions; use crate::dom::bindings::error::{Error, ErrorToJsval}; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::DOMString; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::readablestream::PipeTo; use crate::fetch::FetchContext; use crate::realms::InRealm; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; impl js::gc::Rootable for AbortAlgorithm {} /// /// TODO: implement algorithms at call point, /// in order to integrate the abort signal with its various use cases. #[derive(Clone, JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] #[allow(dead_code)] pub(crate) enum AbortAlgorithm { /// DomEventListener(RemovableDomEventListener), /// StreamPiping(PipeTo), /// Fetch( #[no_trace] #[conditional_malloc_size_of] Arc>, ), } #[derive(Clone, JSTraceable, MallocSizeOf)] pub(crate) struct RemovableDomEventListener { pub(crate) event_target: Trusted, pub(crate) ty: DOMString, #[conditional_malloc_size_of] pub(crate) listener: Option>, pub(crate) options: EventListenerOptions, } /// #[dom_struct] pub(crate) struct AbortSignal { eventtarget: EventTarget, /// #[ignore_malloc_size_of = "mozjs"] abort_reason: Heap, /// abort_algorithms: RefCell>, /// dependent: Cell, /// source_signals: DomRefCell>>, /// dependent_signals: DomRefCell>>, } impl AbortSignal { fn new_inherited() -> AbortSignal { AbortSignal { eventtarget: EventTarget::new_inherited(), abort_reason: Default::default(), abort_algorithms: Default::default(), dependent: Default::default(), source_signals: Default::default(), dependent_signals: Default::default(), } } pub(crate) fn new_with_proto( global: &GlobalScope, proto: Option, can_gc: CanGc, ) -> DomRoot { reflect_dom_object_with_proto( Box::new(AbortSignal::new_inherited()), global, proto, can_gc, ) } /// #[cfg_attr(crown, allow(crown::unrooted_must_root))] // TODO(39333): Remove when all iterators are marked as safe pub(crate) fn signal_abort( &self, cx: SafeJSContext, reason: HandleValue, realm: InRealm, can_gc: CanGc, ) { let global = self.global(); // Step 1. If signal is aborted, then return. if self.Aborted() { return; } // Step 2. Set signal’s abort reason to reason if it is given; // otherwise to a new "AbortError" DOMException. let abort_reason = reason.get(); if !abort_reason.is_undefined() { self.abort_reason.set(abort_reason); } else { rooted!(in(*cx) let mut rooted_error = UndefinedValue()); Error::Abort.to_jsval(cx, &global, rooted_error.handle_mut(), can_gc); self.abort_reason.set(rooted_error.get()) } // Step 3. Let dependentSignalsToAbort be a new list. let mut dependent_signals_to_abort = vec![]; // Step 4. For each dependentSignal of signal’s dependent signals: for dependent_signal in self.dependent_signals.borrow().iter() { // Step 4.1. If dependentSignal is not aborted: if !dependent_signal.aborted() { // Step 4.1.1. Set dependentSignal’s abort reason to signal’s abort reason. dependent_signal.abort_reason.set(self.abort_reason.get()); // Step 4.1.2. Append dependentSignal to dependentSignalsToAbort. dependent_signals_to_abort.push(dependent_signal.as_rooted()); } } // Step 5. Run the abort steps for signal. self.run_the_abort_steps(cx, &global, realm, can_gc); // Step 6. For each dependentSignal of dependentSignalsToAbort, run the abort steps for dependentSignal. for dependent_signal in dependent_signals_to_abort.iter() { dependent_signal.run_the_abort_steps(cx, &global, realm, can_gc); } } /// pub(crate) fn add(&self, algorithm: &AbortAlgorithm) { // Step 1. If signal is aborted, then return. if self.aborted() { return; } // Step 2. Append algorithm to signal’s abort algorithms. self.abort_algorithms.borrow_mut().push(algorithm.clone()); } /// Run a specific abort algorithm. pub(crate) fn run_abort_algorithm( &self, cx: SafeJSContext, global: &GlobalScope, algorithm: &AbortAlgorithm, realm: InRealm, can_gc: CanGc, ) { match algorithm { AbortAlgorithm::StreamPiping(pipe) => { rooted!(in(*cx) let mut reason = UndefinedValue()); reason.set(self.abort_reason.get()); pipe.abort_with_reason(cx, global, reason.handle(), realm, can_gc); }, AbortAlgorithm::Fetch(fetch_context) => { rooted!(in(*cx) let mut reason = UndefinedValue()); reason.set(self.abort_reason.get()); fetch_context .lock() .unwrap() .abort_fetch(reason.handle(), cx, can_gc); }, AbortAlgorithm::DomEventListener(removable_listener) => { removable_listener .event_target .root() .remove_event_listener( removable_listener.ty.clone(), &removable_listener.listener, &removable_listener.options, ); }, } } /// fn run_the_abort_steps( &self, cx: SafeJSContext, global: &GlobalScope, realm: InRealm, can_gc: CanGc, ) { // Step 1. For each algorithm of signal’s abort algorithms: run algorithm. for algo in self.abort_algorithms.borrow().iter() { self.run_abort_algorithm(cx, global, algo, realm, can_gc); } // Step 2. Empty signal’s abort algorithms. self.abort_algorithms.borrow_mut().clear(); // Step 3. Fire an event named abort at signal. self.upcast::() .fire_event(atom!("abort"), can_gc); } /// pub(crate) fn aborted(&self) -> bool { // An AbortSignal object is aborted when its abort reason is not undefined. !self.abort_reason.get().is_undefined() } /// #[cfg_attr(crown, allow(crown::unrooted_must_root))] // TODO(39333): Remove when all iterators are marked as safe pub(crate) fn create_dependent_abort_signal( signals: Vec>, global: &GlobalScope, can_gc: CanGc, ) -> DomRoot { // Step 1. Let resultSignal be a new object implementing signalInterface using realm. let result_signal = Self::new_with_proto(global, None, can_gc); // Step 2. For each signal of signals: if signal is aborted, // then set resultSignal’s abort reason to signal’s abort reason and return resultSignal. for signal in signals.iter() { if signal.aborted() { result_signal.abort_reason.set(signal.abort_reason.get()); return result_signal; } } // Step 3. Set resultSignal’s dependent to true. result_signal.dependent.set(true); // Step 4. For each signal of signals: for signal in signals.iter() { // Step 4.1. If signal’s dependent is false: if !signal.dependent.get() { // Step 4.1.1. Append signal to resultSignal’s source signals. result_signal .source_signals .borrow_mut() .insert(Dom::from_ref(signal)); // Step 4.1.2. Append resultSignal to signal’s dependent signals. signal .dependent_signals .borrow_mut() .insert(Dom::from_ref(&result_signal)); } else { // Step 4.2. Otherwise, for each sourceSignal of signal’s source signals: for source_signal in signal.source_signals.borrow().iter() { // Step 4.2.1. Assert: sourceSignal is not aborted and not dependent. assert!(!source_signal.aborted() && !source_signal.dependent.get()); // Step 4.2.2. Append sourceSignal to resultSignal’s source signals. result_signal .source_signals .borrow_mut() .insert(source_signal.clone()); // Step 4.2.3. Append resultSignal to sourceSignal’s dependent signals. source_signal .dependent_signals .borrow_mut() .insert(Dom::from_ref(&result_signal)); } } } // Step 5. Return resultSignal. result_signal } } impl AbortSignalMethods for AbortSignal { /// fn Aborted(&self) -> bool { // The aborted getter steps are to return true if this is aborted; otherwise false. self.aborted() } /// fn Abort( cx: SafeJSContext, global: &GlobalScope, reason: HandleValue, can_gc: CanGc, ) -> DomRoot { // Step 1. Let signal be a new AbortSignal object. let signal = AbortSignal::new_with_proto(global, None, can_gc); // Step 2. Set signal’s abort reason to reason if it is given; // otherwise to a new "AbortError" DOMException. let abort_reason = reason.get(); if !abort_reason.is_undefined() { signal.abort_reason.set(abort_reason); } else { rooted!(in(*cx) let mut rooted_error = UndefinedValue()); Error::Abort.to_jsval(cx, global, rooted_error.handle_mut(), can_gc); signal.abort_reason.set(rooted_error.get()) } // Step 3. Return signal. signal } /// fn Any( global: &GlobalScope, signals: Vec>, can_gc: CanGc, ) -> DomRoot { // The static any(signals) method steps are to return the result // of creating a dependent abort signal from signals using AbortSignal and the current realm. Self::create_dependent_abort_signal(signals, global, can_gc) } /// fn Reason(&self, _cx: SafeJSContext, mut rval: MutableHandleValue) { // The reason getter steps are to return this’s abort reason. rval.set(self.abort_reason.get()); } /// #[allow(unsafe_code)] fn ThrowIfAborted(&self) { // The throwIfAborted() method steps are to throw this’s abort reason, if this is aborted. if self.aborted() { let cx = GlobalScope::get_cx(); unsafe { JS_SetPendingException( *cx, self.abort_reason.handle(), ExceptionStackBehavior::Capture, ) }; } } // event_handler!(abort, GetOnabort, SetOnabort); }