Skip to content

Commit 7fce850

Browse files
authored
script: implement AbortController (#31361)
* Implement AbortController Signed-off-by: syvb <[email protected]> * Update WPT tests Signed-off-by: syvb <[email protected]> * Address review comments * Fix duplicate import generation * Update WPT test expectations * Change expectation to FAIL for flaky test --------- Signed-off-by: syvb <[email protected]>
1 parent 383607d commit 7fce850

File tree

88 files changed

+522
-1194
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+522
-1194
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
5+
use dom_struct::dom_struct;
6+
use js::jsapi::Value;
7+
use js::rust::{Handle, HandleObject};
8+
9+
use crate::dom::abortsignal::AbortSignal;
10+
use crate::dom::bindings::codegen::Bindings::AbortControllerBinding::AbortControllerMethods;
11+
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
12+
use crate::dom::bindings::root::{Dom, DomRoot};
13+
use crate::dom::globalscope::GlobalScope;
14+
use crate::script_runtime::JSContext;
15+
16+
#[dom_struct]
17+
pub struct AbortController {
18+
reflector_: Reflector,
19+
signal: Dom<AbortSignal>,
20+
}
21+
22+
impl AbortController {
23+
pub fn new_inherited(signal: &AbortSignal) -> AbortController {
24+
AbortController {
25+
reflector_: Reflector::new(),
26+
signal: Dom::from_ref(signal),
27+
}
28+
}
29+
30+
fn new_with_proto(
31+
global: &GlobalScope,
32+
proto: Option<HandleObject>,
33+
) -> DomRoot<AbortController> {
34+
reflect_dom_object_with_proto(
35+
Box::new(AbortController::new_inherited(&AbortSignal::new(global))),
36+
global,
37+
proto,
38+
)
39+
}
40+
41+
#[allow(non_snake_case)]
42+
pub fn Constructor(
43+
global: &GlobalScope,
44+
proto: Option<HandleObject>,
45+
) -> DomRoot<AbortController> {
46+
AbortController::new_with_proto(global, proto)
47+
}
48+
}
49+
50+
impl AbortControllerMethods for AbortController {
51+
/// <https://dom.spec.whatwg.org/#dom-abortcontroller-signal>
52+
fn Signal(&self) -> DomRoot<AbortSignal> {
53+
DomRoot::from_ref(&self.signal)
54+
}
55+
/// <https://dom.spec.whatwg.org/#dom-abortcontroller-abort>
56+
fn Abort(&self, _cx: JSContext, reason: Handle<'_, Value>) {
57+
self.signal.signal_abort(reason);
58+
}
59+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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+
5+
use std::rc::Rc;
6+
7+
use dom_struct::dom_struct;
8+
use js::conversions::ToJSValConvertible;
9+
use js::jsapi::{ExceptionStackBehavior, Heap, JS_IsExceptionPending};
10+
use js::jsval::{JSVal, UndefinedValue};
11+
use js::rust::wrappers::JS_SetPendingException;
12+
use js::rust::HandleValue;
13+
14+
use crate::dom::bindings::cell::DomRefCell;
15+
use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
16+
use crate::dom::bindings::codegen::Bindings::EventListenerBinding::EventListener;
17+
use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventListenerOptions;
18+
use crate::dom::bindings::inheritance::Castable;
19+
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
20+
use crate::dom::bindings::root::DomRoot;
21+
use crate::dom::bindings::str::DOMString;
22+
use crate::dom::domexception::DOMErrorName;
23+
use crate::dom::event::{Event, EventBubbles, EventCancelable};
24+
use crate::dom::eventtarget::EventTarget;
25+
use crate::dom::globalscope::GlobalScope;
26+
use crate::dom::types::DOMException;
27+
use crate::script_runtime::JSContext;
28+
29+
#[derive(JSTraceable, MallocSizeOf)]
30+
pub enum AbortAlgorithm {
31+
RemoveEventListener(
32+
DomRoot<EventTarget>,
33+
DOMString,
34+
#[ignore_malloc_size_of = "Rc"] Rc<EventListener>,
35+
#[ignore_malloc_size_of = "generated"] EventListenerOptions,
36+
),
37+
}
38+
impl AbortAlgorithm {
39+
fn exec(self) {
40+
match self {
41+
Self::RemoveEventListener(target, ty, listener, options) => {
42+
target.remove_event_listener(ty, Some(listener), options)
43+
},
44+
}
45+
}
46+
}
47+
48+
#[dom_struct]
49+
pub struct AbortSignal {
50+
event_target: EventTarget,
51+
#[ignore_malloc_size_of = "Defined in rust-mozjs"]
52+
reason: Heap<JSVal>,
53+
abort_algorithms: DomRefCell<Vec<AbortAlgorithm>>,
54+
}
55+
56+
impl AbortSignal {
57+
pub fn new_inherited() -> Self {
58+
Self {
59+
event_target: EventTarget::new_inherited(),
60+
reason: Heap::default(),
61+
abort_algorithms: DomRefCell::default(),
62+
}
63+
}
64+
pub fn new(global: &GlobalScope) -> DomRoot<Self> {
65+
reflect_dom_object(Box::new(Self::new_inherited()), global)
66+
}
67+
/// <https://dom.spec.whatwg.org/#abortsignal-add>
68+
pub fn add_abort_algorithm(&self, alg: AbortAlgorithm) {
69+
if !self.Aborted() {
70+
self.abort_algorithms.borrow_mut().push(alg);
71+
}
72+
}
73+
/// <https://dom.spec.whatwg.org/#abortsignal-signal-abort>
74+
#[allow(unsafe_code)]
75+
pub fn signal_abort(&self, reason: HandleValue) {
76+
// 1. If signal is aborted, then return.
77+
if self.Aborted() {
78+
return;
79+
}
80+
// 2. Set signal’s abort reason to reason if it is given; otherwise to a new "AbortError" DOMException.
81+
let cx = *GlobalScope::get_cx();
82+
rooted!(in(cx) let mut new_reason = UndefinedValue());
83+
let reason = if reason.is_undefined() {
84+
let exception = DOMException::new(&self.global(), DOMErrorName::AbortError);
85+
unsafe {
86+
exception.to_jsval(cx, new_reason.handle_mut());
87+
};
88+
new_reason.handle()
89+
} else {
90+
reason
91+
};
92+
self.reason.set(reason.get());
93+
94+
// 3. For each algorithm of signal’s abort algorithms: run algorithm.
95+
// 4. Empty signal’s abort algorithms.
96+
for algorithm in self.abort_algorithms.borrow_mut().drain(..) {
97+
algorithm.exec();
98+
}
99+
100+
// 5. Fire an event named abort at signal.
101+
let event = Event::new(
102+
&self.global(),
103+
atom!("abort"),
104+
EventBubbles::DoesNotBubble,
105+
EventCancelable::Cancelable,
106+
);
107+
event.fire(self.upcast());
108+
// 6. For each dependentSignal of signal’s dependent signals,
109+
// signal abort on dependentSignal with signal’s abort reason.
110+
// TODO
111+
}
112+
}
113+
114+
impl AbortSignalMethods for AbortSignal {
115+
// https://dom.spec.whatwg.org/#dom-abortsignal-onabort
116+
event_handler!(Abort, GetOnabort, SetOnabort);
117+
/// <https://dom.spec.whatwg.org/#dom-abortsignal-aborted>
118+
fn Aborted(&self) -> bool {
119+
!self.reason.get().is_undefined()
120+
}
121+
/// <https://dom.spec.whatwg.org/#dom-abortsignal-reason>
122+
fn Reason(&self, _cx: JSContext) -> JSVal {
123+
self.reason.get()
124+
}
125+
#[allow(unsafe_code)]
126+
/// <https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted>
127+
fn ThrowIfAborted(&self) {
128+
let reason = self.reason.get();
129+
if !reason.is_undefined() {
130+
let cx = *GlobalScope::get_cx();
131+
unsafe {
132+
assert!(!JS_IsExceptionPending(cx));
133+
rooted!(in(cx) let mut thrown = UndefinedValue());
134+
reason.to_jsval(cx, thrown.handle_mut());
135+
JS_SetPendingException(cx, thrown.handle(), ExceptionStackBehavior::Capture);
136+
}
137+
}
138+
}
139+
}

components/script/dom/bindings/codegen/CodegenRust.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,7 +2167,8 @@ class CGImports(CGWrapper):
21672167
"""
21682168
Generates the appropriate import/use statements.
21692169
"""
2170-
def __init__(self, child, descriptors, callbacks, dictionaries, enums, typedefs, imports, config):
2170+
def __init__(self, child, descriptors, callbacks, dictionaries, enums, typedefs, imports, config,
2171+
current_name=None):
21712172
"""
21722173
Adds a set of imports.
21732174
"""
@@ -2269,7 +2270,8 @@ def removeWrapperAndNullableTypes(types):
22692270
parentName = descriptor.getParentName()
22702271
while parentName:
22712272
descriptor = descriptorProvider.getDescriptor(parentName)
2272-
extras += [descriptor.path, descriptor.bindingPath]
2273+
if current_name != descriptor.ifaceName:
2274+
extras += [descriptor.path, descriptor.bindingPath]
22732275
parentName = descriptor.getParentName()
22742276
elif t.isType() and t.isRecord():
22752277
extras += ['crate::dom::bindings::record::Record']
@@ -6890,7 +6892,7 @@ class CGBindingRoot(CGThing):
68906892
DomRoot codegen class for binding generation. Instantiate the class, and call
68916893
declare or define to generate header or cpp code (respectively).
68926894
"""
6893-
def __init__(self, config, prefix, webIDLFile):
6895+
def __init__(self, config, prefix, webIDLFile, name):
68946896
descriptors = config.getDescriptors(webIDLFile=webIDLFile,
68956897
hasInterfaceObject=True)
68966898
# We also want descriptors that have an interface prototype object
@@ -6958,7 +6960,7 @@ def __init__(self, config, prefix, webIDLFile):
69586960
# These are the global imports (outside of the generated module)
69596961
curr = CGImports(curr, descriptors=callbackDescriptors, callbacks=mainCallbacks,
69606962
dictionaries=dictionaries, enums=enums, typedefs=typedefs,
6961-
imports=['crate::dom::bindings::import::base::*'], config=config)
6963+
imports=['crate::dom::bindings::import::base::*'], config=config, current_name=name)
69626964

69636965
# Add the auto-generated comment.
69646966
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT + ALLOWED_WARNINGS)

components/script/dom/bindings/codegen/Configuration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ def __init__(self, config, interface, desc):
232232
self.register = desc.get('register', True)
233233
self.path = desc.get('path', pathDefault)
234234
self.inRealmMethods = [name for name in desc.get('inRealms', [])]
235+
self.ifaceName = ifaceName
235236
self.bindingPath = f"crate::dom::bindings::codegen::Bindings::{ifaceName}Binding::{ifaceName}_Binding"
236237
self.outerObjectHook = desc.get('outerObjectHook', 'None')
237238
self.proxy = False

components/script/dom/bindings/codegen/run.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ def main():
5252

5353
for webidl in webidls:
5454
filename = os.path.join(webidls_dir, webidl)
55-
prefix = "Bindings/%sBinding" % webidl[:-len(".webidl")]
56-
module = CGBindingRoot(config, prefix, filename).define()
55+
name = webidl[:-len(".webidl")]
56+
prefix = "Bindings/%sBinding" % name
57+
module = CGBindingRoot(config, prefix, filename, name).define()
5758
if module:
5859
with open(os.path.join(out_dir, prefix + ".rs"), "wb") as f:
5960
f.write(module.encode("utf-8"))

components/script/dom/eventtarget.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use servo_atoms::Atom;
2323
use servo_url::ServoUrl;
2424

2525
use super::bindings::trace::HashMapTracedValues;
26+
use crate::dom::abortsignal::AbortAlgorithm;
2627
use crate::dom::beforeunloadevent::BeforeUnloadEvent;
2728
use crate::dom::bindings::callback::{CallbackContainer, CallbackFunction, ExceptionHandling};
2829
use crate::dom::bindings::cell::DomRefCell;
@@ -704,7 +705,7 @@ impl EventTarget {
704705
None => return,
705706
};
706707
let mut handlers = self.handlers.borrow_mut();
707-
let entry = match handlers.entry(Atom::from(ty)) {
708+
let entry = match handlers.entry(Atom::from(ty.clone())) {
708709
Occupied(entry) => entry.into_mut(),
709710
Vacant(entry) => entry.insert(EventListeners(vec![])),
710711
};
@@ -716,12 +717,20 @@ impl EventTarget {
716717
};
717718
let new_entry = EventListenerEntry {
718719
phase,
719-
listener: EventListenerType::Additive(listener),
720+
listener: EventListenerType::Additive(Rc::clone(&listener)),
720721
once: options.once,
721722
};
722723
if !entry.contains(&new_entry) {
723724
entry.push(new_entry);
724725
}
726+
if let Some(signal) = options.signal {
727+
signal.add_abort_algorithm(AbortAlgorithm::RemoveEventListener(
728+
DomRoot::from_ref(&self),
729+
ty,
730+
listener,
731+
options.parent,
732+
));
733+
};
725734
}
726735

727736
// https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener
@@ -801,6 +810,7 @@ impl From<AddEventListenerOptionsOrBoolean> for AddEventListenerOptions {
801810
AddEventListenerOptionsOrBoolean::Boolean(capture) => Self {
802811
parent: EventListenerOptions { capture },
803812
once: false,
813+
signal: None,
804814
},
805815
}
806816
}

components/script/dom/mediaquerylist.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ impl MediaQueryListMethods for MediaQueryList {
100100
AddEventListenerOptions {
101101
parent: EventListenerOptions { capture: false },
102102
once: false,
103+
signal: None,
103104
},
104105
);
105106
}

components/script/dom/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ pub mod types {
209209
include!(concat!(env!("OUT_DIR"), "/InterfaceTypes.rs"));
210210
}
211211

212+
pub mod abortcontroller;
213+
pub mod abortsignal;
212214
pub mod abstractrange;
213215
pub mod abstractworker;
214216
pub mod abstractworkerglobalscope;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
5+
// https://dom.spec.whatwg.org/#interface-abortcontroller
6+
[Exposed=*]
7+
interface AbortController {
8+
constructor();
9+
10+
[SameObject] readonly attribute AbortSignal signal;
11+
12+
undefined abort(optional any reason);
13+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
5+
// https://dom.spec.whatwg.org/#interface-AbortSignal
6+
[Exposed=*]
7+
interface AbortSignal : EventTarget {
8+
// [NewObject] static AbortSignal abort(optional any reason);
9+
// [Exposed=(Window,Worker), NewObject] static AbortSignal timeout([EnforceRange] unsigned long long milliseconds);
10+
// [NewObject] static AbortSignal _any(sequence<AbortSignal> signals);
11+
12+
readonly attribute boolean aborted;
13+
readonly attribute any reason;
14+
undefined throwIfAborted();
15+
16+
attribute EventHandler onabort;
17+
};

0 commit comments

Comments
 (0)