1use 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#[derive(Clone, JSTraceable, MallocSizeOf)]
44#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
45pub(crate) enum AbortAlgorithm {
46 DomEventListener(RemovableDomEventListener),
48 StreamPiping(PipeTo),
50 Fetch(Dom<Request>),
52 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#[dom_struct]
67pub(crate) struct AbortSignal {
68 eventtarget: EventTarget,
69
70 #[ignore_malloc_size_of = "mozjs"]
72 abort_reason: Heap<JSVal>,
73
74 abort_algorithms: RefCell<Vec<AbortAlgorithm>>,
76
77 dependent: Cell<bool>,
79
80 #[no_trace]
82 #[ignore_malloc_size_of = "WeakRef"]
83 source_signals: DomRefCell<IndexSet<WeakRef<AbortSignal>>>,
84
85 #[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 pub(crate) fn signal_abort(&self, cx: &mut CurrentRealm, reason: HandleValue) {
118 let global = self.global();
119
120 if self.Aborted() {
122 return;
123 }
124
125 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 let mut dependent_signals_to_abort = vec![];
138
139 for weak in self.dependent_signals.borrow().iter() {
141 if let Some(dependent_signal) = weak.root() {
142 if !dependent_signal.aborted() {
144 dependent_signal.abort_reason.set(self.abort_reason.get());
146 dependent_signals_to_abort.push(dependent_signal);
148 }
149 }
150 }
151
152 self.run_the_abort_steps(cx, &global);
154
155 for dependent_signal in dependent_signals_to_abort.iter() {
157 dependent_signal.run_the_abort_steps(cx, &global);
158 }
159 }
160
161 pub(crate) fn add(&self, algorithm: &AbortAlgorithm) {
163 if self.aborted() {
165 return;
166 }
167
168 self.abort_algorithms.borrow_mut().push(algorithm.clone());
170
171 if self.dependent.get() {
173 self.global().register_dependent_abort_signal(self);
174 }
175 }
176
177 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 fn run_the_abort_steps(&self, cx: &mut CurrentRealm, global: &GlobalScope) {
213 for algo in self.abort_algorithms.borrow().iter() {
215 self.run_abort_algorithm(cx, global, algo);
216 }
217 self.abort_algorithms.borrow_mut().clear();
219
220 self.upcast::<EventTarget>().fire_event(cx, atom!("abort"));
222 }
223
224 pub(crate) fn aborted(&self) -> bool {
226 !self.abort_reason.get().is_undefined()
228 }
229
230 pub(crate) fn create_dependent_abort_signal(
232 cx: &mut JSContext,
233 signals: Vec<DomRoot<AbortSignal>>,
234 global: &GlobalScope,
235 ) -> DomRoot<AbortSignal> {
236 let result_signal = Self::new_with_proto(cx, global, None);
238 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 result_signal.dependent.set(true);
248 for signal in signals.iter() {
250 if !signal.dependent.get() {
252 result_signal
254 .source_signals
255 .safe_borrow_mut(cx.no_gc())
256 .insert(WeakRef::new(signal));
257 signal
259 .dependent_signals
260 .safe_borrow_mut(cx.no_gc())
261 .insert(WeakRef::new(&*result_signal));
262 } else {
263 for source_signal_weak in signal.source_signals.borrow().iter() {
265 if let Some(source_signal) = source_signal_weak.root() {
266 assert!(!source_signal.aborted() && !source_signal.dependent.get());
268 result_signal
270 .source_signals
271 .safe_borrow_mut(cx.no_gc())
272 .insert(WeakRef::new(&*source_signal));
273 source_signal
275 .dependent_signals
276 .safe_borrow_mut(cx.no_gc())
277 .insert(WeakRef::new(&*result_signal));
278 }
279 }
280 }
281 }
282 global.register_dependent_abort_signal(&result_signal);
284 result_signal
285 }
286
287 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 fn has_abort_algorithms(&self) -> bool {
295 !self.abort_algorithms.borrow().is_empty()
296 }
297
298 fn has_abort_listeners(&self) -> bool {
300 self.upcast::<EventTarget>()
301 .has_listeners_for(&atom!("abort"))
302 }
303
304 pub(crate) fn must_keep_alive_for_gc(&self) -> bool {
310 self.prune_dead_weak_refs();
312
313 if self.aborted() {
315 return false;
316 }
317
318 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 fn Aborted(&self) -> bool {
333 self.aborted()
335 }
336
337 fn Abort(
339 cx: &mut JSContext,
340 global: &GlobalScope,
341 reason: HandleValue,
342 ) -> DomRoot<AbortSignal> {
343 let signal = AbortSignal::new_with_proto(cx, global, None);
345
346 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 signal
359 }
360
361 fn Timeout(
363 cx: &mut JSContext,
364 global: &GlobalScope,
365 milliseconds: u64,
366 ) -> DomRoot<AbortSignal> {
367 let signal = AbortSignal::new_with_proto(cx, global, None);
369
370 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 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 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_for_task.signal_abort(
406 &mut realm,
407 reason.handle(),
408 );
409 }));
410 },
411 );
412
413 signal
415 }
416
417 fn Any(
419 cx: &mut JSContext,
420 global: &GlobalScope,
421 signals: Vec<DomRoot<AbortSignal>>,
422 ) -> DomRoot<AbortSignal> {
423 Self::create_dependent_abort_signal(cx, signals, global)
426 }
427
428 fn Reason(&self, mut rval: MutableHandleValue) {
430 rval.set(self.abort_reason.get());
432 }
433
434 #[expect(unsafe_code)]
436 fn ThrowIfAborted(&self, cx: &mut JSContext) -> Fallible<()> {
437 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 event_handler!(abort, GetOnabort, SetOnabort);
448}