Skip to content

Commit 248bbed

Browse files
authored
fix abortsignal event names (#5269)
Fixes #5260. This makes AbortSignal.addEventListener ignore non abort event names instead of throwing. It keeps the abort listener path the same and adds tests for ignored event names. Tests: - cargo test -p boa_runtime abort --lib - cargo test -p boa_runtime --lib - cargo clippy -p boa_runtime --all-features --all-targets -- -D warnings
1 parent a2acfa6 commit 248bbed

2 files changed

Lines changed: 149 additions & 22 deletions

File tree

core/runtime/src/abort/mod.rs

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use boa_engine::job::GenericJob;
55
use boa_engine::object::builtins::JsFunction;
66
use boa_engine::realm::Realm;
77
use boa_engine::{
8-
Context, Finalize, JsData, JsError, JsNativeError, JsObject, JsResult, JsValue, Trace,
9-
boa_class, boa_module, js_error, js_string,
8+
Context, Finalize, JsData, JsError, JsNativeError, JsObject, JsResult, JsString, JsValue,
9+
Trace, boa_class, boa_module, js_error, js_string,
1010
};
1111
use boa_gc::GcRefCell;
1212
use std::cell::Cell;
@@ -52,11 +52,17 @@ pub struct JsAbortSignal {
5252
#[unsafe_ignore_trace]
5353
aborted: Cell<bool>,
5454
reason: GcRefCell<Option<JsValue>>,
55-
listeners: GcRefCell<Vec<JsFunction>>,
55+
listeners: GcRefCell<Vec<AbortEventListener>>,
5656
#[unsafe_ignore_trace]
5757
cancel_token: CancellationToken,
5858
}
5959

60+
#[derive(Debug, Clone, Trace, Finalize)]
61+
struct AbortEventListener {
62+
event_type: JsString,
63+
callback: JsFunction,
64+
}
65+
6066
impl Default for JsAbortSignal {
6167
fn default() -> Self {
6268
Self {
@@ -79,7 +85,14 @@ impl JsAbortSignal {
7985
self.aborted.set(true);
8086
*self.reason.borrow_mut() = Some(reason);
8187

82-
let listeners: Vec<JsFunction> = self.listeners.borrow_mut().drain(..).collect();
88+
let abort = js_string!("abort");
89+
let listeners: Vec<JsFunction> = self
90+
.listeners
91+
.borrow()
92+
.iter()
93+
.filter(|listener| listener.event_type == abort)
94+
.map(|listener| listener.callback.clone())
95+
.collect();
8396

8497
let realm = context.realm().clone();
8598
for listener in listeners {
@@ -154,28 +167,28 @@ impl JsAbortSignal {
154167

155168
fn add_event_listener(
156169
&self,
157-
event_type: boa_engine::JsString,
170+
event_type: JsString,
158171
callback: JsFunction,
159-
context: &mut Context,
160-
) -> JsResult<()> {
161-
if event_type.to_std_string_escaped() != "abort" {
162-
return Err(js_error!(TypeError: "AbortSignal only supports the 'abort' event type"));
163-
}
164-
if self.aborted.get() {
165-
callback.call(&JsValue::undefined(), &[], context)?;
166-
} else {
167-
self.listeners.borrow_mut().push(callback);
172+
_context: &mut Context,
173+
) {
174+
{
175+
let listeners = self.listeners.borrow();
176+
if listeners.iter().any(|listener| {
177+
listener.event_type == event_type && JsObject::equals(&listener.callback, &callback)
178+
}) {
179+
return;
180+
}
168181
}
169-
Ok(())
182+
self.listeners.borrow_mut().push(AbortEventListener {
183+
event_type,
184+
callback,
185+
});
170186
}
171187

172-
fn remove_event_listener(&self, event_type: boa_engine::JsString, callback: JsFunction) {
173-
if event_type.to_std_string_escaped() != "abort" {
174-
return;
175-
}
176-
self.listeners
177-
.borrow_mut()
178-
.retain(|f| !JsObject::equals(f, &callback));
188+
fn remove_event_listener(&self, event_type: JsString, callback: JsFunction) {
189+
self.listeners.borrow_mut().retain(|listener| {
190+
listener.event_type != event_type || !JsObject::equals(&listener.callback, &callback)
191+
});
179192
}
180193
}
181194

core/runtime/src/abort/tests.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,120 @@ fn add_event_listener_fires_on_abort() {
117117
]);
118118
}
119119

120+
#[test]
121+
fn add_event_listener_ignores_unknown_event_names() {
122+
run_test_actions([
123+
TestAction::run(
124+
r"
125+
let ctrl = new AbortController();
126+
let called = false;
127+
ctrl.signal.addEventListener('nope', function() {
128+
called = true;
129+
});
130+
ctrl.abort();
131+
",
132+
),
133+
TestAction::inspect_context(|ctx| {
134+
ctx.run_jobs().unwrap();
135+
}),
136+
TestAction::run(
137+
r"
138+
if (called) {
139+
throw new Error('unknown event listener should not fire');
140+
}
141+
",
142+
),
143+
]);
144+
}
145+
146+
#[test]
147+
fn add_event_listener_does_not_fire_abort_listener_added_after_abort() {
148+
run_test_actions([
149+
TestAction::run(
150+
r"
151+
let ctrl = new AbortController();
152+
ctrl.abort();
153+
154+
let called = false;
155+
ctrl.signal.addEventListener('abort', function() {
156+
called = true;
157+
});
158+
",
159+
),
160+
TestAction::inspect_context(|ctx| {
161+
ctx.run_jobs().unwrap();
162+
}),
163+
TestAction::run(
164+
r"
165+
if (called) {
166+
throw new Error('abort listener should not fire when added after abort');
167+
}
168+
",
169+
),
170+
]);
171+
}
172+
173+
#[test]
174+
fn add_event_listener_deduplicates_same_type_and_callback() {
175+
run_test_actions([
176+
TestAction::run(
177+
r"
178+
let ctrl = new AbortController();
179+
let count = 0;
180+
181+
function onAbort() {
182+
count += 1;
183+
}
184+
185+
ctrl.signal.addEventListener('abort', onAbort);
186+
ctrl.signal.addEventListener('abort', onAbort);
187+
ctrl.abort();
188+
",
189+
),
190+
TestAction::inspect_context(|ctx| {
191+
ctx.run_jobs().unwrap();
192+
}),
193+
TestAction::run(
194+
r"
195+
if (count !== 1) {
196+
throw new Error('expected duplicate abort listener to fire once, got: ' + count);
197+
}
198+
",
199+
),
200+
]);
201+
}
202+
203+
#[test]
204+
fn remove_event_listener_uses_event_type() {
205+
run_test_actions([
206+
TestAction::run(
207+
r"
208+
let ctrl = new AbortController();
209+
let count = 0;
210+
211+
function listener() {
212+
count += 1;
213+
}
214+
215+
ctrl.signal.addEventListener('nope', listener);
216+
ctrl.signal.addEventListener('abort', listener);
217+
ctrl.signal.removeEventListener('nope', listener);
218+
ctrl.abort();
219+
",
220+
),
221+
TestAction::inspect_context(|ctx| {
222+
ctx.run_jobs().unwrap();
223+
}),
224+
TestAction::run(
225+
r"
226+
if (count !== 1) {
227+
throw new Error('removing a different event type should not remove the abort listener');
228+
}
229+
",
230+
),
231+
]);
232+
}
233+
120234
#[test]
121235
fn multiple_listeners() {
122236
run_test_actions([

0 commit comments

Comments
 (0)