script/dom/
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;
7use std::sync::{Arc, Mutex};
8
9use dom_struct::dom_struct;
10use indexmap::IndexSet;
11use js::jsapi::{ExceptionStackBehavior, Heap, JS_SetPendingException};
12use js::jsval::{JSVal, UndefinedValue};
13use js::rust::{HandleObject, HandleValue, MutableHandleValue};
14use script_bindings::inheritance::Castable;
15use script_bindings::trace::CustomTraceable;
16
17use crate::dom::bindings::cell::DomRefCell;
18use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
19use crate::dom::bindings::codegen::Bindings::EventListenerBinding::EventListener;
20use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventListenerOptions;
21use crate::dom::bindings::error::{Error, ErrorToJsval};
22use crate::dom::bindings::refcounted::Trusted;
23use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto};
24use crate::dom::bindings::root::{Dom, DomRoot};
25use crate::dom::bindings::str::DOMString;
26use crate::dom::eventtarget::EventTarget;
27use crate::dom::globalscope::GlobalScope;
28use crate::dom::readablestream::PipeTo;
29use crate::fetch::{DeferredFetchRecord, FetchContext};
30use crate::realms::InRealm;
31use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
32
33impl js::gc::Rootable for AbortAlgorithm {}
34
35/// <https://dom.spec.whatwg.org/#abortcontroller-api-integration>
36/// TODO: implement algorithms at call point,
37/// in order to integrate the abort signal with its various use cases.
38#[derive(Clone, JSTraceable, MallocSizeOf)]
39#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
40#[allow(dead_code)]
41pub(crate) enum AbortAlgorithm {
42    /// <https://dom.spec.whatwg.org/#add-an-event-listener>
43    DomEventListener(RemovableDomEventListener),
44    /// <https://streams.spec.whatwg.org/#readable-stream-pipe-to>
45    StreamPiping(PipeTo),
46    /// <https://fetch.spec.whatwg.org/#dom-global-fetch>
47    Fetch(
48        #[no_trace]
49        #[conditional_malloc_size_of]
50        Arc<Mutex<FetchContext>>,
51    ),
52    /// <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
53    FetchLater(
54        #[no_trace]
55        #[conditional_malloc_size_of]
56        Arc<Mutex<DeferredFetchRecord>>,
57    ),
58}
59
60#[derive(Clone, JSTraceable, MallocSizeOf)]
61pub(crate) struct RemovableDomEventListener {
62    pub(crate) event_target: Trusted<EventTarget>,
63    pub(crate) ty: DOMString,
64    #[conditional_malloc_size_of]
65    pub(crate) listener: Option<Rc<EventListener>>,
66    pub(crate) options: EventListenerOptions,
67}
68
69/// <https://dom.spec.whatwg.org/#abortsignal>
70#[dom_struct]
71pub(crate) struct AbortSignal {
72    eventtarget: EventTarget,
73
74    /// <https://dom.spec.whatwg.org/#abortsignal-abort-reason>
75    #[ignore_malloc_size_of = "mozjs"]
76    abort_reason: Heap<JSVal>,
77
78    /// <https://dom.spec.whatwg.org/#abortsignal-abort-algorithms>
79    abort_algorithms: RefCell<Vec<AbortAlgorithm>>,
80
81    /// <https://dom.spec.whatwg.org/#abortsignal-dependent>
82    dependent: Cell<bool>,
83
84    /// <https://dom.spec.whatwg.org/#abortsignal-source-signals>
85    source_signals: DomRefCell<IndexSet<Dom<AbortSignal>>>,
86
87    /// <https://dom.spec.whatwg.org/#abortsignal-dependent-signals>
88    dependent_signals: DomRefCell<IndexSet<Dom<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        global: &GlobalScope,
105        proto: Option<HandleObject>,
106        can_gc: CanGc,
107    ) -> DomRoot<AbortSignal> {
108        reflect_dom_object_with_proto(
109            Box::new(AbortSignal::new_inherited()),
110            global,
111            proto,
112            can_gc,
113        )
114    }
115
116    /// <https://dom.spec.whatwg.org/#abortsignal-signal-abort>
117    #[cfg_attr(crown, allow(crown::unrooted_must_root))] // TODO(39333): Remove when all iterators are marked as safe
118    pub(crate) fn signal_abort(
119        &self,
120        cx: SafeJSContext,
121        reason: HandleValue,
122        realm: InRealm,
123        can_gc: CanGc,
124    ) {
125        let global = self.global();
126
127        // Step 1. If signal is aborted, then return.
128        if self.Aborted() {
129            return;
130        }
131
132        // Step 2. Set signal’s abort reason to reason if it is given;
133        // otherwise to a new "AbortError" DOMException.
134        let abort_reason = reason.get();
135        if !abort_reason.is_undefined() {
136            self.abort_reason.set(abort_reason);
137        } else {
138            rooted!(in(*cx) let mut rooted_error = UndefinedValue());
139            Error::Abort.to_jsval(cx, &global, rooted_error.handle_mut(), can_gc);
140            self.abort_reason.set(rooted_error.get())
141        }
142
143        // Step 3. Let dependentSignalsToAbort be a new list.
144        let mut dependent_signals_to_abort = vec![];
145        // Step 4. For each dependentSignal of signal’s dependent signals:
146        for dependent_signal in self.dependent_signals.borrow().iter() {
147            // Step 4.1. If dependentSignal is not aborted:
148            if !dependent_signal.aborted() {
149                // Step 4.1.1. Set dependentSignal’s abort reason to signal’s abort reason.
150                dependent_signal.abort_reason.set(self.abort_reason.get());
151                // Step 4.1.2. Append dependentSignal to dependentSignalsToAbort.
152                dependent_signals_to_abort.push(dependent_signal.as_rooted());
153            }
154        }
155
156        // Step 5. Run the abort steps for signal.
157        self.run_the_abort_steps(cx, &global, realm, can_gc);
158
159        // Step 6. For each dependentSignal of dependentSignalsToAbort, run the abort steps for dependentSignal.
160        for dependent_signal in dependent_signals_to_abort.iter() {
161            dependent_signal.run_the_abort_steps(cx, &global, realm, can_gc);
162        }
163    }
164
165    /// <https://dom.spec.whatwg.org/#abortsignal-add>
166    pub(crate) fn add(&self, algorithm: &AbortAlgorithm) {
167        // Step 1. If signal is aborted, then return.
168        if self.aborted() {
169            return;
170        }
171
172        // Step 2. Append algorithm to signal’s abort algorithms.
173        self.abort_algorithms.borrow_mut().push(algorithm.clone());
174    }
175
176    /// Run a specific abort algorithm.
177    pub(crate) fn run_abort_algorithm(
178        &self,
179        cx: SafeJSContext,
180        global: &GlobalScope,
181        algorithm: &AbortAlgorithm,
182        realm: InRealm,
183        can_gc: CanGc,
184    ) {
185        match algorithm {
186            AbortAlgorithm::StreamPiping(pipe) => {
187                rooted!(in(*cx) let mut reason = UndefinedValue());
188                reason.set(self.abort_reason.get());
189                pipe.abort_with_reason(cx, global, reason.handle(), realm, can_gc);
190            },
191            AbortAlgorithm::Fetch(fetch_context) => {
192                rooted!(in(*cx) let mut reason = UndefinedValue());
193                reason.set(self.abort_reason.get());
194                fetch_context
195                    .lock()
196                    .unwrap()
197                    .abort_fetch(reason.handle(), cx, can_gc);
198            },
199            AbortAlgorithm::FetchLater(deferred_fetch_record) => {
200                deferred_fetch_record.lock().unwrap().abort();
201            },
202            AbortAlgorithm::DomEventListener(removable_listener) => {
203                removable_listener
204                    .event_target
205                    .root()
206                    .remove_event_listener(
207                        removable_listener.ty.clone(),
208                        &removable_listener.listener,
209                        &removable_listener.options,
210                    );
211            },
212        }
213    }
214
215    /// <https://dom.spec.whatwg.org/#run-the-abort-steps>
216    fn run_the_abort_steps(
217        &self,
218        cx: SafeJSContext,
219        global: &GlobalScope,
220        realm: InRealm,
221        can_gc: CanGc,
222    ) {
223        // Step 1. For each algorithm of signal’s abort algorithms: run algorithm.
224        for algo in self.abort_algorithms.borrow().iter() {
225            self.run_abort_algorithm(cx, global, algo, realm, can_gc);
226        }
227        // Step 2. Empty signal’s abort algorithms.
228        self.abort_algorithms.borrow_mut().clear();
229
230        // Step 3. Fire an event named abort at signal.
231        self.upcast::<EventTarget>()
232            .fire_event(atom!("abort"), can_gc);
233    }
234
235    /// <https://dom.spec.whatwg.org/#abortsignal-aborted>
236    pub(crate) fn aborted(&self) -> bool {
237        // An AbortSignal object is aborted when its abort reason is not undefined.
238        !self.abort_reason.get().is_undefined()
239    }
240
241    /// <https://dom.spec.whatwg.org/#create-a-dependent-abort-signal>
242    #[cfg_attr(crown, allow(crown::unrooted_must_root))] // TODO(39333): Remove when all iterators are marked as safe
243    pub(crate) fn create_dependent_abort_signal(
244        signals: Vec<DomRoot<AbortSignal>>,
245        global: &GlobalScope,
246        can_gc: CanGc,
247    ) -> DomRoot<AbortSignal> {
248        // Step 1. Let resultSignal be a new object implementing signalInterface using realm.
249        let result_signal = Self::new_with_proto(global, None, can_gc);
250        // Step 2. For each signal of signals: if signal is aborted,
251        // then set resultSignal’s abort reason to signal’s abort reason and return resultSignal.
252        for signal in signals.iter() {
253            if signal.aborted() {
254                result_signal.abort_reason.set(signal.abort_reason.get());
255                return result_signal;
256            }
257        }
258        // Step 3. Set resultSignal’s dependent to true.
259        result_signal.dependent.set(true);
260        // Step 4. For each signal of signals:
261        for signal in signals.iter() {
262            // Step 4.1. If signal’s dependent is false:
263            if !signal.dependent.get() {
264                // Step 4.1.1. Append signal to resultSignal’s source signals.
265                result_signal
266                    .source_signals
267                    .borrow_mut()
268                    .insert(Dom::from_ref(signal));
269                // Step 4.1.2. Append resultSignal to signal’s dependent signals.
270                signal
271                    .dependent_signals
272                    .borrow_mut()
273                    .insert(Dom::from_ref(&result_signal));
274            } else {
275                // Step 4.2. Otherwise, for each sourceSignal of signal’s source signals:
276                for source_signal in signal.source_signals.borrow().iter() {
277                    // Step 4.2.1. Assert: sourceSignal is not aborted and not dependent.
278                    assert!(!source_signal.aborted() && !source_signal.dependent.get());
279                    // Step 4.2.2. Append sourceSignal to resultSignal’s source signals.
280                    result_signal
281                        .source_signals
282                        .borrow_mut()
283                        .insert(source_signal.clone());
284                    // Step 4.2.3. Append resultSignal to sourceSignal’s dependent signals.
285                    source_signal
286                        .dependent_signals
287                        .borrow_mut()
288                        .insert(Dom::from_ref(&result_signal));
289                }
290            }
291        }
292        // Step 5. Return resultSignal.
293        result_signal
294    }
295}
296
297impl AbortSignalMethods<crate::DomTypeHolder> for AbortSignal {
298    /// <https://dom.spec.whatwg.org/#dom-abortsignal-aborted>
299    fn Aborted(&self) -> bool {
300        // The aborted getter steps are to return true if this is aborted; otherwise false.
301        self.aborted()
302    }
303
304    /// <https://dom.spec.whatwg.org/#dom-abortsignal-abort>
305    fn Abort(
306        cx: SafeJSContext,
307        global: &GlobalScope,
308        reason: HandleValue,
309        can_gc: CanGc,
310    ) -> DomRoot<AbortSignal> {
311        // Step 1. Let signal be a new AbortSignal object.
312        let signal = AbortSignal::new_with_proto(global, None, can_gc);
313
314        // Step 2. Set signal’s abort reason to reason if it is given;
315        // otherwise to a new "AbortError" DOMException.
316        let abort_reason = reason.get();
317        if !abort_reason.is_undefined() {
318            signal.abort_reason.set(abort_reason);
319        } else {
320            rooted!(in(*cx) let mut rooted_error = UndefinedValue());
321            Error::Abort.to_jsval(cx, global, rooted_error.handle_mut(), can_gc);
322            signal.abort_reason.set(rooted_error.get())
323        }
324
325        // Step 3. Return signal.
326        signal
327    }
328
329    /// <https://dom.spec.whatwg.org/#dom-abortsignal-any>
330    fn Any(
331        global: &GlobalScope,
332        signals: Vec<DomRoot<AbortSignal>>,
333        can_gc: CanGc,
334    ) -> DomRoot<AbortSignal> {
335        // The static any(signals) method steps are to return the result
336        // of creating a dependent abort signal from signals using AbortSignal and the current realm.
337        Self::create_dependent_abort_signal(signals, global, can_gc)
338    }
339
340    /// <https://dom.spec.whatwg.org/#dom-abortsignal-reason>
341    fn Reason(&self, _cx: SafeJSContext, mut rval: MutableHandleValue) {
342        // The reason getter steps are to return this’s abort reason.
343        rval.set(self.abort_reason.get());
344    }
345
346    /// <https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted>
347    #[allow(unsafe_code)]
348    fn ThrowIfAborted(&self) {
349        // The throwIfAborted() method steps are to throw this’s abort reason, if this is aborted.
350        if self.aborted() {
351            let cx = GlobalScope::get_cx();
352            unsafe {
353                JS_SetPendingException(
354                    *cx,
355                    self.abort_reason.handle(),
356                    ExceptionStackBehavior::Capture,
357                )
358            };
359        }
360    }
361
362    // <https://dom.spec.whatwg.org/#dom-abortsignal-onabort>
363    event_handler!(abort, GetOnabort, SetOnabort);
364}