diff --git a/crates/web-sys/Cargo.toml b/crates/web-sys/Cargo.toml index 5ad947b0..51165e0b 100644 --- a/crates/web-sys/Cargo.toml +++ b/crates/web-sys/Cargo.toml @@ -38,7 +38,10 @@ wasm-bindgen-futures = { path = '../futures', version = '0.2.21' } AbortController = [] AbortSignal = [] AddEventListenerOptions = [] +AesCbcParams = [] +AesCtrParams = [] AesDerivedKeyParams = [] +AesGcmParams = [] AesKeyAlgorithm = [] AesKeyGenParams = [] Algorithm = [] @@ -289,10 +292,12 @@ Element = [] ElementCreationOptions = [] ElementDefinitionOptions = [] EndingTypes = [] +ErrorCallback = [] ErrorEvent = [] ErrorEventInit = [] Event = [] EventInit = [] +EventListener = [] EventListenerOptions = [] EventModifierInit = [] EventSource = [] @@ -310,6 +315,7 @@ FetchReadableStreamReadDataArray = [] FetchReadableStreamReadDataDone = [] FetchState = [] File = [] +FileCallback = [] FileList = [] FilePropertyBag = [] FileReader = [] @@ -317,7 +323,9 @@ FileReaderSync = [] FileSystem = [] FileSystemDirectoryEntry = [] FileSystemDirectoryReader = [] +FileSystemEntriesCallback = [] FileSystemEntry = [] +FileSystemEntryCallback = [] FileSystemFileEntry = [] FileSystemFlags = [] FillMode = [] @@ -602,6 +610,7 @@ NetworkCommandOptions = [] NetworkInformation = [] NetworkResultOptions = [] Node = [] +NodeFilter = [] NodeIterator = [] NodeList = [] Notification = [] @@ -611,6 +620,7 @@ NotificationEvent = [] NotificationEventInit = [] NotificationOptions = [] NotificationPermission = [] +ObserverCallback = [] OfflineAudioCompletionEvent = [] OfflineAudioCompletionEventInit = [] OfflineAudioContext = [] @@ -702,10 +712,13 @@ ProgressEventInit = [] PromiseRejectionEvent = [] PromiseRejectionEventInit = [] PublicKeyCredential = [] +PublicKeyCredentialDescriptor = [] PublicKeyCredentialEntity = [] PublicKeyCredentialParameters = [] +PublicKeyCredentialRequestOptions = [] PublicKeyCredentialRpEntity = [] PublicKeyCredentialType = [] +PublicKeyCredentialUserEntity = [] PushEncryptionKeyName = [] PushEvent = [] PushEventInit = [] @@ -768,6 +781,7 @@ RtcIceServer = [] RtcIceTransportPolicy = [] RtcIdentityAssertion = [] RtcIdentityAssertionResult = [] +RtcIdentityProvider = [] RtcIdentityProviderDetails = [] RtcIdentityProviderOptions = [] RtcIdentityValidationResult = [] @@ -1038,6 +1052,7 @@ VideoPlaybackQuality = [] VideoStreamTrack = [] VideoTrack = [] VideoTrackList = [] +VoidCallback = [] VrDisplay = [] VrDisplayCapabilities = [] VrEye = [] @@ -1170,6 +1185,7 @@ WorkerOptions = [] Worklet = [] WorkletGlobalScope = [] XPathExpression = [] +XPathNsResolver = [] XPathResult = [] XmlDocument = [] XmlHttpRequest = [] diff --git a/crates/web-sys/webidls/enabled/EventTarget.webidl b/crates/web-sys/webidls/enabled/EventTarget.webidl index c5fb9587..000f44bc 100644 --- a/crates/web-sys/webidls/enabled/EventTarget.webidl +++ b/crates/web-sys/webidls/enabled/EventTarget.webidl @@ -29,12 +29,12 @@ interface EventTarget { false. */ [Throws] void addEventListener(DOMString type, - EventListener? listener, + EventListener listener, optional (AddEventListenerOptions or boolean) options, optional boolean? wantsUntrusted = null); [Throws] void removeEventListener(DOMString type, - EventListener? listener, + EventListener listener, optional (EventListenerOptions or boolean) options); [Throws, NeedsCallerType] boolean dispatchEvent(Event event); diff --git a/crates/webidl-tests/callbacks.js b/crates/webidl-tests/callbacks.js new file mode 100644 index 00000000..b976bfd3 --- /dev/null +++ b/crates/webidl-tests/callbacks.js @@ -0,0 +1,4 @@ +global.TakeCallbackInterface = class { + a() {} + b() {} +}; diff --git a/crates/webidl-tests/callbacks.rs b/crates/webidl-tests/callbacks.rs new file mode 100644 index 00000000..baa36af9 --- /dev/null +++ b/crates/webidl-tests/callbacks.rs @@ -0,0 +1,38 @@ +use wasm_bindgen_test::*; +use js_sys::Function; + +include!(concat!(env!("OUT_DIR"), "/callbacks.rs")); + +#[wasm_bindgen_test] +fn multi_op_same_name() { + let a = CallbackInterface2::new(); + let b = TakeCallbackInterface::new().unwrap(); + b.b(&a); +} + +#[wasm_bindgen_test] +fn single_op_function() { + let a = Function::new_no_args(""); + let b = TakeCallbackInterface::new().unwrap(); + b.a_with_callback(&a); +} + +#[wasm_bindgen_test] +fn single_op_dict() { + let a = CallbackInterface1::new(); + let b = TakeCallbackInterface::new().unwrap(); + b.a_with_callback_interface1(&a); +} + +#[wasm_bindgen_test] +fn dict_methods() { + let mut a = CallbackInterface1::new(); + a.foo(&Function::new_no_args("")); +} + +#[wasm_bindgen_test] +fn dict_methods1() { + let mut a = CallbackInterface2::new(); + a.foo(&Function::new_no_args("")); + a.bar(&Function::new_no_args("")); +} diff --git a/crates/webidl-tests/callbacks.webidl b/crates/webidl-tests/callbacks.webidl new file mode 100644 index 00000000..26227afb --- /dev/null +++ b/crates/webidl-tests/callbacks.webidl @@ -0,0 +1,14 @@ +callback interface CallbackInterface1 { + void foo(); +}; + +callback interface CallbackInterface2 { + void foo(); + void bar(); +}; + +[Constructor()] +interface TakeCallbackInterface { + void a(CallbackInterface1 arg); + void b(CallbackInterface2 arg); +}; diff --git a/crates/webidl-tests/main.rs b/crates/webidl-tests/main.rs index 66266567..2e20392a 100644 --- a/crates/webidl-tests/main.rs +++ b/crates/webidl-tests/main.rs @@ -11,3 +11,4 @@ pub mod simple; pub mod throws; pub mod dictionary; pub mod global; +pub mod callbacks; diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 1b19d8a1..75a4ca11 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -12,6 +12,7 @@ use std::collections::{BTreeMap, BTreeSet}; use proc_macro2::Ident; use weedle::{DictionaryDefinition, PartialDictionaryDefinition}; +use weedle::CallbackInterfaceDefinition; use weedle::argument::Argument; use weedle::attribute::*; use weedle::interface::*; @@ -35,6 +36,7 @@ pub(crate) struct FirstPassRecord<'src> { pub(crate) includes: BTreeMap<&'src str, BTreeSet<&'src str>>, pub(crate) dictionaries: BTreeMap<&'src str, DictionaryData<'src>>, pub(crate) callbacks: BTreeSet<&'src str>, + pub(crate) callback_interfaces: BTreeMap<&'src str, CallbackInterfaceData<'src>>, } /// We need to collect interface data during the first pass, to be used later. @@ -64,17 +66,20 @@ pub(crate) struct MixinData<'src> { /// We need to collect namespace data during the first pass, to be used later. #[derive(Default)] pub(crate) struct NamespaceData<'src> { - /// Whether only partial namespaces were encountered pub(crate) operations: BTreeMap, OperationData<'src>>, } #[derive(Default)] pub(crate) struct DictionaryData<'src> { - /// Whether only partial namespaces were encountered pub(crate) partials: Vec<&'src PartialDictionaryDefinition<'src>>, pub(crate) definition: Option<&'src DictionaryDefinition<'src>>, } +pub(crate) struct CallbackInterfaceData<'src> { + pub(crate) definition: &'src CallbackInterfaceDefinition<'src>, + pub(crate) single_function: bool, +} + #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] pub(crate) enum OperationId<'src> { Constructor(IgnoreTraits<&'src str>), @@ -137,12 +142,8 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> { PartialNamespace(namespace) => namespace.first_pass(record, ()), Typedef(typedef) => typedef.first_pass(record, ()), Callback(callback) => callback.first_pass(record, ()), - + CallbackInterface(iface) => iface.first_pass(record, ()), Implements(_) => Ok(()), - CallbackInterface(..) => { - warn!("Unsupported WebIDL CallbackInterface definition: {:?}", self); - Ok(()) - } } } } @@ -690,6 +691,25 @@ impl<'src> FirstPass<'src, ()> for weedle::CallbackDefinition<'src> { } } +impl<'src> FirstPass<'src, ()> for weedle::CallbackInterfaceDefinition<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, _: ()) -> Result<()> { + if util::is_chrome_only(&self.attributes) { + return Ok(()) + } + if self.inheritance.is_some() { + warn!("skipping callback interface with inheritance: {}", + self.identifier.0); + return Ok(()) + } + let data = CallbackInterfaceData { + definition: self, + single_function: self.members.body.len() == 1, + }; + record.callback_interfaces.insert(self.identifier.0, data); + Ok(()) + } +} + impl<'a> FirstPassRecord<'a> { pub fn all_superclasses<'me>(&'me self, interface: &str) -> impl Iterator + 'me diff --git a/crates/webidl/src/idl_type.rs b/crates/webidl/src/idl_type.rs index ec525e91..5e03e96d 100644 --- a/crates/webidl/src/idl_type.rs +++ b/crates/webidl/src/idl_type.rs @@ -48,6 +48,7 @@ pub(crate) enum IdlType<'a> { Interface(&'a str), Dictionary(&'a str), Enum(&'a str), + CallbackInterface { name: &'a str, single_function: bool }, Nullable(Box>), FrozenArray(Box>), @@ -296,6 +297,11 @@ impl<'a> ToIdlType<'a> for Identifier<'a> { Some(IdlType::Enum(self.0)) } else if record.callbacks.contains(self.0) { Some(IdlType::Callback) + } else if let Some(data) = record.callback_interfaces.get(self.0) { + Some(IdlType::CallbackInterface { + name: self.0, + single_function: data.single_function, + }) } else { warn!("Unrecognized type: {}", self.0); None @@ -387,6 +393,9 @@ impl<'a> IdlType<'a> { IdlType::Interface(name) => dst.push_str(&snake_case_ident(name)), IdlType::Dictionary(name) => dst.push_str(&snake_case_ident(name)), IdlType::Enum(name) => dst.push_str(&snake_case_ident(name)), + IdlType::CallbackInterface { name, .. } => { + dst.push_str(&snake_case_ident(name)) + } IdlType::Nullable(idl_type) => { dst.push_str("opt_"); @@ -480,7 +489,8 @@ impl<'a> IdlType<'a> { IdlType::ArrayBufferView | IdlType::BufferSource => js_sys("Object"), IdlType::Interface(name) - | IdlType::Dictionary(name) => { + | IdlType::Dictionary(name) + | IdlType::CallbackInterface { name, .. } => { let ty = ident_ty(rust_ident(camel_case_ident(name).as_str())); if pos == TypePosition::Argument { Some(shared_ref(ty, false)) @@ -588,6 +598,17 @@ impl<'a> IdlType<'a> { IdlType::UnsignedLongLong => { vec![IdlType::UnsignedLong, IdlType::Double] } + IdlType::CallbackInterface { name, single_function: true } => { + // According to the webidl spec [1] single-function callback + // interfaces can also be replaced in arguments with simply a + // single callable function, which we map to a `Callback`. + // + // [1]: https://heycam.github.io/webidl/#es-user-objects + vec![ + IdlType::Callback, + IdlType::CallbackInterface { name, single_function: false }, + ] + } idl_type @ _ => vec![idl_type.clone()], } } diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 51a3c9c2..092ef92b 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -42,9 +42,10 @@ use backend::util::{ident_ty, rust_ident, raw_ident, wrap_import_function}; use proc_macro2::{Ident, Span}; use weedle::attribute::{ExtendedAttributeList}; use weedle::dictionary::DictionaryMember; +use weedle::interface::InterfaceMember; use first_pass::{FirstPass, FirstPassRecord, OperationId, InterfaceData}; -use first_pass::OperationData; +use first_pass::{OperationData, CallbackInterfaceData}; use util::{public, webidl_const_v_to_backend_const_v, TypePosition, camel_case_ident, shouty_snake_case_ident, snake_case_ident, mdn_doc}; use idl_type::ToIdlType; @@ -108,6 +109,11 @@ fn parse(webidl_source: &str, allowed_types: Option<&[&str]>) first_pass_record.append_interface(&mut program, name, d); } } + for (name, d) in first_pass_record.callback_interfaces.iter() { + if filter(name) { + first_pass_record.append_callback_interface(&mut program, d); + } + } // Prune out `extends` annotations that aren't defined as these shouldn't // prevent the type from being usable entirely. They're just there for @@ -644,4 +650,38 @@ impl<'src> FirstPassRecord<'src> { list, )); } + + fn append_callback_interface( + &self, + program: &mut backend::ast::Program, + item: &CallbackInterfaceData<'src>, + ) { + let mut fields = Vec::new(); + for member in item.definition.members.body.iter() { + match member { + InterfaceMember::Operation(op) => { + let identifier = match op.identifier { + Some(i) => i.0, + None => continue, + }; + let pos = TypePosition::Argument; + fields.push(ast::DictionaryField { + required: false, + name: rust_ident(&snake_case_ident(identifier)), + ty: idl_type::IdlType::Callback.to_syn_type(pos) + .unwrap(), + }); + } + _ => { + warn!("skipping callback interface member on {}", + item.definition.identifier.0); + } + } + } + + program.dictionaries.push(ast::Dictionary { + name: rust_ident(&camel_case_ident(item.definition.identifier.0)), + fields, + }); + } }