Skip to main content

script/dom/abort/
abortsignal.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Cell, RefCell};
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use indexmap::IndexSet;
10use js::context::JSContext;
11use js::jsapi::{ExceptionStackBehavior, Heap};
12use js::jsval::{JSVal, UndefinedValue};
13use js::realm::CurrentRealm;
14use js::rust::{HandleObject, HandleValue, MutableHandleValue};
15use script_bindings::callback::TracedCallback;
16use script_bindings::cell::DomRefCell;
17use script_bindings::domstring::TracedDOMString;
18use script_bindings::inheritance::Castable;
19use script_bindings::js::JS_SetPendingException;
20use script_bindings::reflector::reflect_weak_referenceable_dom_object_with_proto;
21use script_bindings::weakref::WeakRef;
22
23use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
24use crate::dom::bindings::codegen::Bindings::EventListenerBinding::EventListener;
25use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventListenerOptions;
26use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible};
27use crate::dom::bindings::refcounted::Trusted;
28use crate::dom::bindings::reflector::DomGlobal;
29use crate::dom::bindings::root::{Dom, DomRoot};
30use crate::dom::bindings::str::DOMString;
31use crate::dom::eventtarget::EventTarget;
32use crate::dom::globalscope::GlobalScope;
33use crate::dom::readablestream::PipeTo;
34use crate::dom::request::Request;
35use crate::fetch::fetch::DeferredFetchRecordId;
36use crate::realms::enter_auto_realm;
37
38impl js::gc::Rootable for AbortAlgorithm {}
39
40/// <https://dom.spec.whatwg.org/#abortcontroller-api-integration>
41/// TODO: implement algorithms at call point,
42/// in order to integrate the abort signal with its various use cases.
43#[derive(Clone, JSTraceable, MallocSizeOf)]
44#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
45pub(crate) enum AbortAlgorithm {
46    /// <https://dom.spec.whatwg.org/#add-an-event-listener>
47    DomEventListener(RemovableDomEventListener),
48    /// <https://streams.spec.whatwg.org/#readable-stream-pipe-to>
49    StreamPiping(PipeTo),
50    /// <https://fetch.spec.whatwg.org/#dom-global-fetch>
51    Fetch(Dom<Request>),
52    /// <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
53    FetchLater(#[no_trace] DeferredFetchRecordId),
54}
55
56#[derive(Clone, JSTraceable, MallocSizeOf)]
57#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
58pub(crate) struct RemovableDomEventListener {
59    pub(crate) event_target: Dom<EventTarget>,
60    pub(crate) ty: TracedDOMString,
61    pub(crate) listener: Option<TracedCallback<EventListener>>,
62    pub(crate) options: EventListenerOptions,
63}
64
65/// <https://dom.spec.whatwg.org/#abortsignal>
66#[dom_struct]
67pub(crate) struct AbortSignal {
68    eventtarget: EventTarget,
69
70    /// <https://dom.spec.whatwg.org/#abortsignal-abort-reason>
71    #[ignore_malloc_size_of = "mozjs"]
72    abort_reason: Heap<JSVal>,
73
74    /// <https://dom.spec.whatwg.org/#abortsignal-abort-algorithms>
75    abort_algorithms: RefCell<Vec<AbortAlgorithm>>,
76
77    /// <https://dom.spec.whatwg.org/#abortsignal-dependent>
78    dependent: Cell<bool>,
79
80    /// <https://dom.spec.whatwg.org/#abortsignal-source-signals>
81    #[no_trace]
82    #[ignore_malloc_size_of = "WeakRef"]
83    source_signals: DomRefCell<IndexSet<WeakRef<AbortSignal>>>,
84
85    /// <https://dom.spec.whatwg.org/#abortsignal-dependent-signals>
86    #[no_trace]
87    #[ignore_malloc_size_of = "WeakRef"]
88    dependent_signals: DomRefCell<IndexSet<WeakRef<AbortSignal>>>,
89}
90
91impl AbortSignal {
92    fn new_inherited() -> AbortSignal {
93        AbortSignal {
94            eventtarget: EventTarget::new_inherited(),
95            abort_reason: Default::default(),
96            abort_algorithms: Default::default(),
97            dependent: Default::default(),
98            source_signals: Default::default(),
99            dependent_signals: Default::default(),
100        }
101    }
102
103    pub(crate) fn new_with_proto(
104        cx: &mut JSContext,
105        global: &GlobalScope,
106        proto: Option<HandleObject>,
107    ) -> DomRoot<AbortSignal> {
108        reflect_weak_referenceable_dom_object_with_proto(
109            cx,
110            Rc::new(AbortSignal::new_inherited()),
111            global,
112            proto,
113        )
114    }
115
116    /// <https://dom.spec.whatwg.org/#abortsignal-signal-abort>
117    pub(crate) fn signal_abort(&self, cx: &mut CurrentRealm, reason: HandleValue) {
118        let global = self.global();
119
120        // Step 1. If signal is aborted, then return.
121        if self.Aborted() {
122            return;
123        }
124
125        // Step 2. Set signal’s abort reason to reason if it is given;
126        // otherwise to a new "AbortError" DOMException.
127        let abort_reason = reason.get();
128        if !abort_reason.is_undefined() {
129            self.abort_reason.set(abort_reason);
130        } else {
131            rooted!(&in(cx) let mut rooted_error = UndefinedValue());
132            Error::Abort(None).to_jsval(cx, &global, rooted_error.handle_mut());
133            self.abort_reason.set(rooted_error.get())
134        }
135
136        // Step 3. Let dependentSignalsToAbort be a new list.
137        let mut dependent_signals_to_abort = vec![];
138
139        // Step 4. For each dependentSignal of signal’s dependent signals:
140        for weak in self.dependent_signals.borrow().iter() {
141            if let Some(dependent_signal) = weak.root() {
142                // Step 4.1. If dependentSignal is not aborted:
143                if !dependent_signal.aborted() {
144                    // Step 4.1.1. Set dependentSignal’s abort reason to signal’s abort reason.
145                    dependent_signal.abort_reason.set(self.abort_reason.get());
146                    // Step 4.1.2. Append dependentSignal to dependentSignalsToAbort.
147                    dependent_signals_to_abort.push(dependent_signal);
148                }
149            }
150        }
151
152        // Step 5. Run the abort steps for signal.
153        self.run_the_abort_steps(cx, &global);
154
155        // Step 6. For each dependentSignal of dependentSignalsToAbort, run the abort steps for dependentSignal.
156        for dependent_signal in dependent_signals_to_abort.iter() {
157            dependent_signal.run_the_abort_steps(cx, &global);
158        }
159    }
160
161    /// <https://dom.spec.whatwg.org/#abortsignal-add>
162    pub(crate) fn add(&self, algorithm: &AbortAlgorithm) {
163        // Step 1. If signal is aborted, then return.
164        if self.aborted() {
165            return;
166        }
167
168        // Step 2. Append algorithm to signal’s abort algorithms.
169        self.abort_algorithms.borrow_mut().push(algorithm.clone());
170
171        // if this is a dependent signal, it may now need to be kept alive.
172        if self.dependent.get() {
173            self.global().register_dependent_abort_signal(self);
174        }
175    }
176
177    /// Run a specific abort algorithm.
178    pub(crate) fn run_abort_algorithm(
179        &self,
180        cx: &mut CurrentRealm,
181        global: &GlobalScope,
182        algorithm: &AbortAlgorithm,
183    ) {
184        match algorithm {
185            AbortAlgorithm::StreamPiping(pipe) => {
186                rooted!(&in(cx) let mut reason = UndefinedValue());
187                reason.set(self.abort_reason.get());
188                pipe.abort_with_reason(cx, global, reason.handle());
189            },
190            AbortAlgorithm::Fetch(request) => {
191                rooted!(&in(cx) let mut reason = UndefinedValue());
192                reason.set(self.abort_reason.get());
193                request.abort_fetch(cx, reason.handle(), global);
194            },
195            AbortAlgorithm::FetchLater(deferred_fetch_record_id) => {
196                global
197                    .fetch_group()
198                    .deferred_fetch_record_for_id(deferred_fetch_record_id)
199                    .abort();
200            },
201            AbortAlgorithm::DomEventListener(removable_listener) => {
202                removable_listener.event_target.remove_event_listener(
203                    removable_listener.ty.clone().root(),
204                    removable_listener.listener.as_deref(),
205                    &removable_listener.options,
206                );
207            },
208        }
209    }
210
211    /// <https://dom.spec.whatwg.org/#run-the-abort-steps>
212    fn run_the_abort_steps(&self, cx: &mut CurrentRealm, global: &GlobalScope) {
213        // Step 1. For each algorithm of signal’s abort algorithms: run algorithm.
214        for algo in self.abort_algorithms.borrow().iter() {
215            self.run_abort_algorithm(cx, global, algo);
216        }
217        // Step 2. Empty signal’s abort algorithms.
218        self.abort_algorithms.borrow_mut().clear();
219
220        // Step 3. Fire an event named abort at signal.
221        self.upcast::<EventTarget>().fire_event(cx, atom!("abort"));
222    }
223
224    /// <https://dom.spec.whatwg.org/#abortsignal-aborted>
225    pub(crate) fn aborted(&self) -> bool {
226        // An AbortSignal object is aborted when its abort reason is not undefined.
227        !self.abort_reason.get().is_undefined()
228    }
229
230    /// <https://dom.spec.whatwg.org/#create-a-dependent-abort-signal>
231    pub(crate) fn create_dependent_abort_signal(
232        cx: &mut JSContext,
233        signals: Vec<DomRoot<AbortSignal>>,
234        global: &GlobalScope,
235    ) -> DomRoot<AbortSignal> {
236        // Step 1. Let resultSignal be a new object implementing signalInterface using realm.
237        let result_signal = Self::new_with_proto(cx, global, None);
238        // Step 2. For each signal of signals: if signal is aborted,
239        // then set resultSignal’s abort reason to signal’s abort reason and return resultSignal.
240        for signal in signals.iter() {
241            if signal.aborted() {
242                result_signal.abort_reason.set(signal.abort_reason.get());
243                return result_signal;
244            }
245        }
246        // Step 3. Set resultSignal’s dependent to true.
247        result_signal.dependent.set(true);
248        // Step 4. For each signal of signals:
249        for signal in signals.iter() {
250            // Step 4.1. If signal’s dependent is false:
251            if !signal.dependent.get() {
252                // Step 4.1.1. Append signal to resultSignal’s source signals.
253                result_signal
254                    .source_signals
255                    .safe_borrow_mut(cx.no_gc())
256                    .insert(WeakRef::new(signal));
257                // Step 4.1.2. Append resultSignal to signal’s dependent signals.
258                signal
259                    .dependent_signals
260                    .safe_borrow_mut(cx.no_gc())
261                    .insert(WeakRef::new(&*result_signal));
262            } else {
263                // Step 4.2. Otherwise, for each sourceSignal of signal’s source signals:
264                for source_signal_weak in signal.source_signals.borrow().iter() {
265                    if let Some(source_signal) = source_signal_weak.root() {
266                        // Step 4.2.1. Assert: sourceSignal is not aborted and not dependent.
267                        assert!(!source_signal.aborted() && !source_signal.dependent.get());
268                        // Step 4.2.2. Append sourceSignal to resultSignal’s source signals.
269                        result_signal
270                            .source_signals
271                            .safe_borrow_mut(cx.no_gc())
272                            .insert(WeakRef::new(&*source_signal));
273                        // Step 4.2.3. Append resultSignal to sourceSignal’s dependent signals.
274                        source_signal
275                            .dependent_signals
276                            .safe_borrow_mut(cx.no_gc())
277                            .insert(WeakRef::new(&*result_signal));
278                    }
279                }
280            }
281        }
282        // Step 5. Return resultSignal.
283        global.register_dependent_abort_signal(&result_signal);
284        result_signal
285    }
286
287    /// Remove weak references whose target has been collected.
288    fn prune_dead_weak_refs(&self) {
289        self.source_signals.borrow_mut().retain(|w| w.is_alive());
290        self.dependent_signals.borrow_mut().retain(|w| w.is_alive());
291    }
292
293    /// Whether this signal still has abort algorithms registered.
294    fn has_abort_algorithms(&self) -> bool {
295        !self.abort_algorithms.borrow().is_empty()
296    }
297
298    /// Whether this signal has any listeners for its "abort" event.
299    fn has_abort_listeners(&self) -> bool {
300        self.upcast::<EventTarget>()
301            .has_listeners_for(&atom!("abort"))
302    }
303
304    /// <https://dom.spec.whatwg.org/#abort-signal-garbage-collection>
305    /// A non-aborted dependent AbortSignal object must not be garbage
306    /// collected while its source signals is non-empty and it has
307    /// registered event listeners for its abort event or its abort
308    /// algorithms is non-empty.
309    pub(crate) fn must_keep_alive_for_gc(&self) -> bool {
310        // Treat dead weak entries as removed.
311        self.prune_dead_weak_refs();
312
313        // Only care about non-aborted signals.
314        if self.aborted() {
315            return false;
316        }
317
318        // If it has no live source signals left, it doesn’t need to be kept.
319        if self.source_signals.borrow().is_empty() {
320            return false;
321        }
322
323        let has_algos = self.has_abort_algorithms();
324        let has_listeners = self.has_abort_listeners();
325
326        has_algos || has_listeners
327    }
328}
329
330impl AbortSignalMethods<crate::DomTypeHolder> for AbortSignal {
331    /// <https://dom.spec.whatwg.org/#dom-abortsignal-aborted>
332    fn Aborted(&self) -> bool {
333        // The aborted getter steps are to return true if this is aborted; otherwise false.
334        self.aborted()
335    }
336
337    /// <https://dom.spec.whatwg.org/#dom-abortsignal-abort>
338    fn Abort(
339        cx: &mut JSContext,
340        global: &GlobalScope,
341        reason: HandleValue,
342    ) -> DomRoot<AbortSignal> {
343        // Step 1. Let signal be a new AbortSignal object.
344        let signal = AbortSignal::new_with_proto(cx, global, None);
345
346        // Step 2. Set signal’s abort reason to reason if it is given;
347        // otherwise to a new "AbortError" DOMException.
348        let abort_reason = reason.get();
349        if !abort_reason.is_undefined() {
350            signal.abort_reason.set(abort_reason);
351        } else {
352            rooted!(&in(cx) let mut rooted_error = UndefinedValue());
353            Error::Abort(None).to_jsval(cx, global, rooted_error.handle_mut());
354            signal.abort_reason.set(rooted_error.get())
355        }
356
357        // Step 3. Return signal.
358        signal
359    }
360
361    /// <https://dom.spec.whatwg.org/#dom-abortsignal-timeout>
362    fn Timeout(
363        cx: &mut JSContext,
364        global: &GlobalScope,
365        milliseconds: u64,
366    ) -> DomRoot<AbortSignal> {
367        // Step 1. Let signal be a new AbortSignal object.
368        let signal = AbortSignal::new_with_proto(cx, global, None);
369
370        // Step 2. Let global be signal’s relevant global object.
371        // We already have `global`.
372
373        let signal_keepalive: Trusted<AbortSignal> = Trusted::new(&signal);
374
375        let ms_i64 = if milliseconds > i64::MAX as u64 {
376            i64::MAX
377        } else {
378            milliseconds as i64
379        };
380
381        // Step 3. Run steps after a timeout given global, "AbortSignal-timeout", milliseconds, and the following step:
382        global.run_steps_after_a_timeout(
383            DOMString::from_static("AbortSignal-timeout"),
384            ms_i64,
385            move |_cx, global| {
386                let task_source = global.task_manager().timer_task_source().to_sendable();
387
388                // Step 3.1. Queue a global task on the timer task source given global to signal abort given signal and a new "TimeoutError" DOMException.
389                // For the duration of this timeout, if signal has any event listeners registered for its abort event,
390                // there must be a strong reference from global to signal.
391                task_source.queue(task!(abortsignal_timeout: move |cx| {
392                    let signal_for_task = signal_keepalive.root();
393
394                    rooted!(&in(cx) let mut reason = UndefinedValue());
395                    Error::Timeout(None).to_jsval(
396                        cx,
397                        &signal_for_task.global(),
398                        reason.handle_mut(),
399                    );
400
401                    let mut realm = enter_auto_realm(cx, &*signal_for_task.global());
402                    let mut realm = realm.current_realm();
403
404                    // “signal abort given signal and a new "TimeoutError" DOMException.”
405                    signal_for_task.signal_abort(
406                        &mut realm,
407                        reason.handle(),
408                    );
409                }));
410            },
411        );
412
413        // Step 4. Return signal.
414        signal
415    }
416
417    /// <https://dom.spec.whatwg.org/#dom-abortsignal-any>
418    fn Any(
419        cx: &mut JSContext,
420        global: &GlobalScope,
421        signals: Vec<DomRoot<AbortSignal>>,
422    ) -> DomRoot<AbortSignal> {
423        // The static any(signals) method steps are to return the result
424        // of creating a dependent abort signal from signals using AbortSignal and the current realm.
425        Self::create_dependent_abort_signal(cx, signals, global)
426    }
427
428    /// <https://dom.spec.whatwg.org/#dom-abortsignal-reason>
429    fn Reason(&self, mut rval: MutableHandleValue) {
430        // The reason getter steps are to return this’s abort reason.
431        rval.set(self.abort_reason.get());
432    }
433
434    /// <https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted>
435    #[expect(unsafe_code)]
436    fn ThrowIfAborted(&self, cx: &mut JSContext) -> Fallible<()> {
437        // The throwIfAborted() method steps are to throw this’s abort reason, if this is aborted.
438        if self.aborted() {
439            let reason = unsafe { HandleValue::from_raw(self.abort_reason.handle()) };
440            JS_SetPendingException(cx, reason, ExceptionStackBehavior::Capture);
441            return Err(Error::JSFailed);
442        }
443        Ok(())
444    }
445
446    // <https://dom.spec.whatwg.org/#dom-abortsignal-onabort>
447    event_handler!(abort, GetOnabort, SetOnabort);
448}