mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-26 19:45:54 +03:00
Implement WebIDL callback interfaces
This commit implements callback interfaces for WebIDL, the final WebIDL construct that we were unconditionally ignoring! Callback interfaces are implemented as dictionaries of callbacks. Single-operation callback interfaces are also expanded when flattening to accept a `Function` as well, in accordance with the WebIDL spec. New features have been added to `web-sys` for all the new callback interface types. Additionally the `EventTarget.webidl` was tweaked to not have `EventListener?` as this is required for all functional usage and there's no need to keep that sort of web browser compat here. Closes #258
This commit is contained in:
parent
2cf82bc0b3
commit
8181f7fa95
@ -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 = []
|
||||
|
@ -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);
|
||||
|
4
crates/webidl-tests/callbacks.js
Normal file
4
crates/webidl-tests/callbacks.js
Normal file
@ -0,0 +1,4 @@
|
||||
global.TakeCallbackInterface = class {
|
||||
a() {}
|
||||
b() {}
|
||||
};
|
38
crates/webidl-tests/callbacks.rs
Normal file
38
crates/webidl-tests/callbacks.rs
Normal file
@ -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(""));
|
||||
}
|
14
crates/webidl-tests/callbacks.webidl
vendored
Normal file
14
crates/webidl-tests/callbacks.webidl
vendored
Normal file
@ -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);
|
||||
};
|
@ -11,3 +11,4 @@ pub mod simple;
|
||||
pub mod throws;
|
||||
pub mod dictionary;
|
||||
pub mod global;
|
||||
pub mod callbacks;
|
||||
|
@ -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<OperationId<'src>, 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<Item = String> + 'me
|
||||
|
@ -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<IdlType<'a>>),
|
||||
FrozenArray(Box<IdlType<'a>>),
|
||||
@ -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()],
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user