761 lines
28 KiB
Rust
Raw Normal View History

use std::iter::FromIterator;
use std::ptr;
use backend;
2018-08-04 15:16:02 -07:00
use backend::util::{ident_ty, leading_colon_path_ty, raw_ident, rust_ident};
use heck::{CamelCase, ShoutySnakeCase, SnakeCase};
use proc_macro2::{Ident, Span};
use syn;
use weedle;
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList, IdentifierOrString};
use weedle::literal::{ConstValue, FloatLit, IntegerLit};
use first_pass::{FirstPassRecord, OperationData, OperationId, Signature};
2018-08-29 18:32:47 -07:00
use idl_type::{IdlType, ToIdlType};
use std::collections::HashSet;
2018-09-11 23:10:03 +03:00
/// For variadic operations an overload with a `js_sys::Array` argument is generated alongside with
/// `operation_name_0`, `operation_name_1`, `operation_name_2`, ..., `operation_name_n` overloads
/// which have the count of arguments for passing values to the variadic argument
/// in their names, where `n` is this constant.
const MAX_VARIADIC_ARGUMENTS_COUNT: usize = 7;
/// Take a type and create an immutable shared reference to that type.
2018-09-03 13:27:24 +02:00
pub(crate) fn shared_ref(ty: syn::Type, mutable: bool) -> syn::Type {
syn::TypeReference {
and_token: Default::default(),
lifetime: None,
mutability: if mutable {
Some(syn::token::Mut::default())
} else {
None
},
elem: Box::new(ty),
2018-11-27 12:07:59 -08:00
}
.into()
}
/// Fix case of identifiers like `HTMLBRElement` or `texImage2D`
fn fix_ident(identifier: &str) -> String {
identifier
.replace("HTML", "HTML_")
.replace("1D", "_1d")
.replace("2D", "_2d")
.replace("3D", "_3d")
}
/// Convert an identifier to camel case
pub fn camel_case_ident(identifier: &str) -> String {
fix_ident(identifier).to_camel_case()
}
/// Convert an identifier to shouty snake case
pub fn shouty_snake_case_ident(identifier: &str) -> String {
fix_ident(identifier).to_shouty_snake_case()
}
/// Convert an identifier to snake case
pub fn snake_case_ident(identifier: &str) -> String {
fix_ident(identifier).to_snake_case()
}
// Returns a link to MDN
pub fn mdn_doc(class: &str, method: Option<&str>) -> String {
let mut link = format!("https://developer.mozilla.org/en-US/docs/Web/API/{}", class);
if let Some(method) = method {
link.push_str(&format!("/{}", method));
}
format!("[MDN Documentation]({})", link).into()
}
// Array type is borrowed for arguments (`&mut [T]` or `&[T]`) and owned for return value (`Vec<T>`).
pub(crate) fn array(base_ty: &str, pos: TypePosition, immutable: bool) -> syn::Type {
match pos {
TypePosition::Argument => {
shared_ref(
slice_ty(ident_ty(raw_ident(base_ty))),
/*mutable =*/ !immutable,
)
}
TypePosition::Return => vec_ty(ident_ty(raw_ident(base_ty))),
}
}
/// Map a webidl const value to the correct wasm-bindgen const value
pub fn webidl_const_v_to_backend_const_v(v: &ConstValue) -> backend::ast::ConstValue {
use backend::ast;
use std::f64::{INFINITY, NAN, NEG_INFINITY};
match *v {
ConstValue::Boolean(b) => ast::ConstValue::BooleanLiteral(b.0),
ConstValue::Float(FloatLit::NegInfinity(_)) => ast::ConstValue::FloatLiteral(NEG_INFINITY),
ConstValue::Float(FloatLit::Infinity(_)) => ast::ConstValue::FloatLiteral(INFINITY),
ConstValue::Float(FloatLit::NaN(_)) => ast::ConstValue::FloatLiteral(NAN),
ConstValue::Float(FloatLit::Value(s)) => {
ast::ConstValue::FloatLiteral(s.0.parse().unwrap())
}
ConstValue::Integer(lit) => {
let mklit = |orig_text: &str, base: u32, offset: usize| {
let (negative, text) = if orig_text.starts_with("-") {
(true, &orig_text[1..])
} else {
(false, orig_text)
};
if text == "0" {
return ast::ConstValue::SignedIntegerLiteral(0);
}
let text = &text[offset..];
let n = u64::from_str_radix(text, base)
.unwrap_or_else(|_| panic!("literal too big: {}", orig_text));
if negative {
let n = if n > (i64::min_value() as u64).wrapping_neg() {
panic!("literal too big: {}", orig_text)
} else {
n.wrapping_neg() as i64
};
ast::ConstValue::SignedIntegerLiteral(n)
} else {
ast::ConstValue::UnsignedIntegerLiteral(n)
}
};
match lit {
IntegerLit::Hex(h) => mklit(h.0, 16, 2), // leading 0x
IntegerLit::Oct(h) => mklit(h.0, 8, 1), // leading 0
IntegerLit::Dec(h) => mklit(h.0, 10, 0),
}
}
ConstValue::Null(_) => ast::ConstValue::Null,
}
}
/// From `ident` and `Ty`, create `ident: Ty` for use in e.g. `fn(ident: Ty)`.
fn simple_fn_arg(ident: Ident, ty: syn::Type) -> syn::ArgCaptured {
syn::ArgCaptured {
pat: syn::Pat::Ident(syn::PatIdent {
by_ref: None,
mutability: None,
ident,
subpat: None,
}),
colon_token: Default::default(),
ty,
}
}
/// Create `()`.
fn unit_ty() -> syn::Type {
syn::Type::Tuple(syn::TypeTuple {
paren_token: Default::default(),
elems: syn::punctuated::Punctuated::new(),
})
}
/// From `T` create `Result<T, ::wasm_bindgen::JsValue>`.
fn result_ty(t: syn::Type) -> syn::Type {
let js_value = leading_colon_path_ty(vec![rust_ident("wasm_bindgen"), rust_ident("JsValue")]);
let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
colon2_token: None,
lt_token: Default::default(),
args: FromIterator::from_iter(vec![
syn::GenericArgument::Type(t),
syn::GenericArgument::Type(js_value),
]),
gt_token: Default::default(),
});
let ident = raw_ident("Result");
let seg = syn::PathSegment { ident, arguments };
let path: syn::Path = seg.into();
let ty = syn::TypePath { qself: None, path };
ty.into()
}
/// From `T` create `[T]`.
2018-08-11 23:46:33 +03:00
pub(crate) fn slice_ty(t: syn::Type) -> syn::Type {
syn::TypeSlice {
bracket_token: Default::default(),
elem: Box::new(t),
2018-11-27 12:07:59 -08:00
}
.into()
}
/// From `T` create `Vec<T>`.
2018-08-11 23:46:33 +03:00
pub(crate) fn vec_ty(t: syn::Type) -> syn::Type {
let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
colon2_token: None,
lt_token: Default::default(),
args: FromIterator::from_iter(vec![syn::GenericArgument::Type(t)]),
gt_token: Default::default(),
});
let ident = raw_ident("Vec");
let seg = syn::PathSegment { ident, arguments };
let path: syn::Path = seg.into();
let ty = syn::TypePath { qself: None, path };
ty.into()
}
/// From `T` create `Option<T>`
2018-08-11 23:46:33 +03:00
pub(crate) fn option_ty(t: syn::Type) -> syn::Type {
let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
colon2_token: None,
lt_token: Default::default(),
args: FromIterator::from_iter(vec![syn::GenericArgument::Type(t)]),
gt_token: Default::default(),
});
let ident = raw_ident("Option");
let seg = syn::PathSegment { ident, arguments };
let path: syn::Path = seg.into();
let ty = syn::TypePath { qself: None, path };
ty.into()
}
/// Possible positions for a type in a function signature.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TypePosition {
Argument,
Return,
}
impl<'src> FirstPassRecord<'src> {
pub fn create_one_function<'a>(
&self,
js_name: &str,
rust_name: &str,
idl_arguments: impl Iterator<Item = (&'a str, &'a IdlType<'src>)>,
ret: &IdlType<'src>,
kind: backend::ast::ImportFunctionKind,
structural: bool,
catch: bool,
variadic: bool,
doc_comment: Option<String>,
) -> Option<backend::ast::ImportFunction>
where
'src: 'a,
{
// Convert all of the arguments from their IDL type to a `syn` type,
// ready to pass to the backend.
//
// Note that for non-static methods we add a `&self` type placeholder,
// but this type isn't actually used so it's just here for show mostly.
let mut arguments = if let &backend::ast::ImportFunctionKind::Method {
ref ty,
kind:
backend::ast::MethodKind::Operation(backend::ast::Operation {
is_static: false, ..
}),
..
} = &kind
{
let mut res = Vec::with_capacity(idl_arguments.size_hint().0 + 1);
res.push(simple_fn_arg(
raw_ident("self_"),
shared_ref(ty.clone(), false),
));
res
} else {
Vec::with_capacity(idl_arguments.size_hint().0)
};
let idl_arguments: Vec<_> = idl_arguments.collect();
let arguments_count = idl_arguments.len();
for (i, (argument_name, idl_type)) in idl_arguments.into_iter().enumerate() {
let syn_type = match idl_type.to_syn_type(TypePosition::Argument) {
Some(t) => t,
None => {
warn!(
"Unsupported argument type: {:?} on {:?}",
idl_type, rust_name
);
return None;
}
};
let syn_type = if variadic && i == arguments_count - 1 {
let path = vec![rust_ident("js_sys"), rust_ident("Array")];
shared_ref(leading_colon_path_ty(path), false)
} else {
syn_type
};
let argument_name = rust_ident(&argument_name.to_snake_case());
arguments.push(simple_fn_arg(argument_name, syn_type));
}
// Convert the return type to a `syn` type, handling the `catch`
// attribute here to use a `Result` in Rust.
let ret = match ret {
IdlType::Void => None,
ret @ _ => match ret.to_syn_type(TypePosition::Return) {
Some(ret) => Some(ret),
None => {
warn!("Unsupported return type: {:?} on {:?}", ret, rust_name);
return None;
}
},
};
let js_ret = ret.clone();
let ret = if catch {
Some(ret.map_or_else(|| result_ty(unit_ty()), result_ty))
} else {
ret
};
Some(backend::ast::ImportFunction {
function: backend::ast::Function {
name: js_name.to_string(),
name_span: Span::call_site(),
renamed_via_js_name: false,
arguments,
ret: ret.clone(),
rust_attrs: vec![],
rust_vis: public(),
},
rust_name: rust_ident(rust_name),
js_ret: js_ret.clone(),
variadic,
catch,
structural,
shim: {
let ns = match kind {
backend::ast::ImportFunctionKind::Normal => "",
backend::ast::ImportFunctionKind::Method { ref class, .. } => class,
};
raw_ident(&format!("__widl_f_{}_{}", rust_name, ns))
},
kind,
doc_comment,
})
2018-08-11 23:46:33 +03:00
}
/// Create a wasm-bindgen getter method, if possible.
pub fn create_getter(
&self,
name: &str,
ty: &weedle::types::Type<'src>,
self_name: &str,
is_static: bool,
attrs: &Option<ExtendedAttributeList>,
container_attrs: Option<&ExtendedAttributeList>,
) -> Option<backend::ast::ImportFunction> {
let kind = backend::ast::OperationKind::Getter(Some(raw_ident(name)));
let kind = self.import_function_kind(self_name, is_static, kind);
let ret = ty.to_idl_type(self);
self.create_one_function(
&name,
&snake_case_ident(name),
None.into_iter(),
&ret,
kind,
is_structural(attrs.as_ref(), container_attrs),
throws(attrs),
false,
Some(format!(
"The `{}` getter\n\n{}",
name,
mdn_doc(self_name, Some(name))
)),
)
}
/// Create a wasm-bindgen setter method, if possible.
pub fn create_setter(
&self,
name: &str,
field_ty: &weedle::types::Type<'src>,
self_name: &str,
is_static: bool,
attrs: &Option<ExtendedAttributeList>,
container_attrs: Option<&ExtendedAttributeList>,
) -> Option<backend::ast::ImportFunction> {
let kind = backend::ast::OperationKind::Setter(Some(raw_ident(name)));
let kind = self.import_function_kind(self_name, is_static, kind);
let field_ty = field_ty.to_idl_type(self);
self.create_one_function(
&name,
&format!("set_{}", name).to_snake_case(),
Some((name, &field_ty)).into_iter(),
&IdlType::Void,
kind,
is_structural(attrs.as_ref(), container_attrs),
throws(attrs),
false,
Some(format!(
"The `{}` setter\n\n{}",
name,
mdn_doc(self_name, Some(name))
)),
)
}
pub fn import_function_kind(
&self,
self_name: &str,
is_static: bool,
operation_kind: backend::ast::OperationKind,
) -> backend::ast::ImportFunctionKind {
let operation = backend::ast::Operation {
is_static,
kind: operation_kind,
};
let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str()));
backend::ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty,
kind: backend::ast::MethodKind::Operation(operation),
}
}
pub fn create_imports(
&self,
container_attrs: Option<&ExtendedAttributeList<'src>>,
kind: backend::ast::ImportFunctionKind,
id: &OperationId<'src>,
2018-08-29 18:32:47 -07:00
data: &OperationData<'src>,
) -> Vec<backend::ast::ImportFunction> {
2018-08-30 16:29:51 -07:00
// First up, prune all signatures that reference unsupported arguments.
// We won't consider these until said arguments are implemented.
//
// Note that we handle optional arguments as well. Optional arguments
// should only appear at the end of argument lists and when we see one
// we can simply push our signature so far onto the list for the
// signature where that and all remaining optional arguments are
// undefined.
2018-08-30 16:29:51 -07:00
let mut signatures = Vec::new();
'outer: for signature in data.signatures.iter() {
2018-08-30 16:29:51 -07:00
let mut idl_args = Vec::with_capacity(signature.args.len());
for (i, arg) in signature.args.iter().enumerate() {
if arg.optional {
assert!(
signature.args[i..]
.iter()
.all(|arg| arg.optional || arg.variadic),
"Not optional or variadic argument after optional argument: {:?}",
signature.args,
);
signatures.push((signature, idl_args.clone()));
}
2019-01-14 17:45:43 -05:00
2019-01-22 21:35:22 -05:00
let mut idl_type = arg.ty.to_idl_type(self);
2019-01-14 17:45:43 -05:00
let idl_type = maybe_adjust(idl_type, id);
idl_args.push(idl_type);
2018-08-30 16:29:51 -07:00
}
signatures.push((signature, idl_args));
}
// Next expand all the signatures in `data` into all signatures that
// we're going to generate. These signatures will be used to determine
// the names for all the various functions.
#[derive(Clone)]
struct ExpandedSig<'a> {
orig: &'a Signature<'a>,
args: Vec<IdlType<'a>>,
}
let mut actual_signatures = Vec::new();
2018-08-30 16:29:51 -07:00
for (signature, idl_args) in signatures.iter() {
let mut start = actual_signatures.len();
// Start off with an empty signature, this'll handle zero-argument
// cases and otherwise the loop below will continue to add on to this.
actual_signatures.push(ExpandedSig {
orig: signature,
args: Vec::with_capacity(signature.args.len()),
});
for (i, idl_type) in idl_args.iter().enumerate() {
2018-08-30 16:29:51 -07:00
// small sanity check
assert!(start < actual_signatures.len());
for sig in actual_signatures[start..].iter() {
assert_eq!(sig.args.len(), i);
}
2018-08-30 16:29:51 -07:00
// The first element of the flattened type gets pushed directly
// in-place, but all other flattened types will cause new
// signatures to be created.
let cur = actual_signatures.len();
2018-08-30 22:31:03 -07:00
for (j, idl_type) in idl_type.flatten().into_iter().enumerate() {
for k in start..cur {
if j == 0 {
actual_signatures[k].args.push(idl_type.clone());
} else {
2018-08-30 22:31:03 -07:00
let mut sig = actual_signatures[k].clone();
assert_eq!(sig.args.len(), i + 1);
sig.args.truncate(i);
sig.args.push(idl_type.clone());
actual_signatures.push(sig);
}
}
}
}
}
let (name, force_structural, force_throws) = match id {
// Constructors aren't annotated with `[Throws]` extended attributes
// (how could they be, since they themselves are extended
// attributes?) so we must conservatively assume that they can
// always throw.
//
// From https://heycam.github.io/webidl/#Constructor (emphasis
// mine):
//
// > The prose definition of a constructor must either return an IDL
// > value of a type corresponding to the interface the
// > `[Constructor]` extended attribute appears on, **or throw an
// > exception**.
OperationId::Constructor(_) => ("new", false, true),
OperationId::Operation(Some(s)) => (*s, false, false),
OperationId::Operation(None) => {
warn!("unsupported unnamed operation");
return Vec::new();
}
OperationId::IndexingGetter => ("get", true, false),
OperationId::IndexingSetter => ("set", true, false),
OperationId::IndexingDeleter => ("delete", true, false),
};
let mut ret = Vec::new();
for signature in actual_signatures.iter() {
// Ignore signatures with invalid return types
2018-08-30 16:29:51 -07:00
//
// TODO: overloads probably never change return types, so we should
// do this much earlier to avoid all the above work if
// possible.
let ret_ty = signature.orig.ret.to_idl_type(self);
let mut rust_name = snake_case_ident(name);
let mut first = true;
for (i, arg) in signature.args.iter().enumerate() {
// Find out if any other known signature either has the same
// name for this argument or a different type for this argument.
let mut any_same_name = false;
let mut any_different_type = false;
let mut any_different = false;
let arg_name = signature.orig.args[i].name;
for other in actual_signatures.iter() {
if other.orig.args.get(i).map(|s| s.name) == Some(arg_name) {
if !ptr::eq(signature, other) {
any_same_name = true;
}
}
if let Some(other) = other.args.get(i) {
if other != arg {
any_different_type = true;
any_different = true;
}
} else {
any_different = true;
}
}
// If all signatures have the exact same type for this argument,
// then there's nothing to disambiguate so we don't modify the
// name.
if !any_different {
continue;
}
if first {
rust_name.push_str("_with_");
first = false;
} else {
rust_name.push_str("_and_");
}
// If this name of the argument for this signature is unique
// then that's a bit more human readable so we include it in the
// method name. Otherwise the type name should disambiguate
// correctly.
2018-08-30 16:29:51 -07:00
//
// If any signature's argument has the same name as our argument
// then we can't use that if the types are also the same because
// otherwise it could be ambiguous.
if any_same_name && any_different_type {
arg.push_snake_case_name(&mut rust_name);
2018-08-30 16:29:51 -07:00
} else {
rust_name.push_str(&snake_case_ident(arg_name));
}
}
let structural =
force_structural || is_structural(signature.orig.attrs.as_ref(), container_attrs);
let catch = force_throws || throws(&signature.orig.attrs);
2018-11-27 12:07:59 -08:00
let variadic = signature.args.len() == signature.orig.args.len()
&& signature
.orig
.args
.last()
.map(|arg| arg.variadic)
.unwrap_or(false);
ret.extend(
self.create_one_function(
2018-09-11 23:10:03 +03:00
name,
&rust_name,
signature
.args
2018-09-11 23:10:03 +03:00
.iter()
.zip(&signature.orig.args)
.map(|(idl_type, orig_arg)| (orig_arg.name, idl_type)),
2018-09-11 23:10:03 +03:00
&ret_ty,
kind.clone(),
structural,
catch,
variadic,
2018-09-11 23:10:03 +03:00
None,
),
);
if !variadic {
continue;
}
let last_idl_type = &signature.args[signature.args.len() - 1];
let last_name = signature.orig.args[signature.args.len() - 1].name;
for i in 0..=MAX_VARIADIC_ARGUMENTS_COUNT {
ret.extend(
self.create_one_function(
name,
&format!("{}_{}", rust_name, i),
signature.args[..signature.args.len() - 1]
.iter()
.zip(&signature.orig.args)
.map(|(idl_type, orig_arg)| (orig_arg.name.to_string(), idl_type))
.chain((1..=i).map(|j| (format!("{}_{}", last_name, j), last_idl_type)))
.collect::<Vec<_>>()
.iter()
.map(|(name, idl_type)| (&name[..], idl_type.clone())),
&ret_ty,
kind.clone(),
structural,
catch,
false,
None,
),
);
2018-09-11 23:10:03 +03:00
}
}
return ret;
}
}
2018-07-02 20:35:05 -07:00
/// Search for an attribute by name in some webidl object's attributes.
fn has_named_attribute(list: Option<&ExtendedAttributeList>, attribute: &str) -> bool {
let list = match list {
Some(list) => list,
None => return false,
};
list.body.list.iter().any(|attr| match attr {
ExtendedAttribute::NoArgs(name) => (name.0).0 == attribute,
_ => false,
2018-07-02 20:35:05 -07:00
})
}
Create the `web-sys` crate mechanically from WebIDL (#409) * Create a new `web-sys` crate This will eventually contain all the WebIDL-generated bindings to Web APIs. * ci: Test the new `web-sys` crate in CI * web-sys: Add a small README * web-sys: Vendor all the WebIDL files from mozilla-central * backend: Add a pass to remove AST items that use undefined imports This is necessary for the WebIDL frontend, which can't translate many WebIDL constructs into equivalent wasm-bindgen AST things yet. It lets us make incremental progress: we can generate bindings to methods we can support right now even though there might be methods on the same interface that we can't support yet. * webidl: Add a bunch of missing semicolons * webidl: Make parsing private It was only `pub` so that we could test it, but we ended up moving towards integration tests rather than unit tests that assert particular ASTs are parsed from WebIDL files. * webidl: Remove uses of undefined import types * test-project-builder: Build projects in "very verbose" mode This helps for debugging failing WebIDL-related tests. * test-project-builder: Add more profiling timers * test-project-builder: Detect when webpack-dev-server fails Instead of going into an infinite loop, detect when webpack-dev-server fails to start up and early exit the test. * webidl: Specify version for dev-dependency on wasm-bindgen-backend Instead of only a relative path. * guide: Add section about contributing to `web-sys` * WIP enable Event.webidl Still need to fix and finish the test. * Update expected webidl output * Start out a test's status as incomplete That way if we don't fill it in the error message doesn't look quite so bizarre * Fix onerror function in headless mode Otherwise we don't see any output! * Fix package.json/node_modules handling in project generation Make sure these are looked up in the git project root rather than the crate root * Avoid logging body text This was meant for debugging and is otherwise pretty noisy * Fix a relative path * More expected test fixes * Fix a typo * test-project-builder: Allow asynchronous tests * webidl: Convert [Unforgeable] attributes into `#[wasm_bindgen(structural)]` Fixes #432 * test-project-builder: Print generated WebIDL bindings for debugging purposes Helps debug bad WebIDL bindings generation inside tests. * When we can't find a descriptor, say which one can't be found This helps when debugging things that need to become structural. * web-sys: Test bindings for Event * ci: Use `--manifest-path dir` instead of `cd dir && ...` * web-sys: Just move .webidl files isntead of symlinking to enable them * tests: Polyfill Array.prototype.values for older browsers in CI * test-project-builder: Don't panic on poisoned headless test mutex We only use it to serialize headless tests so that we don't try to bind the port concurrently. Its OK to run another headless test if an earlier one panicked. * JsValue: Add {is,as}_{object,function} methods Allows dynamically casting values to `js::Object` and `js::Function`. * tidy: Fix whitespace and missing semicolons * Allow for dynamic feature detection of methods If we create bindings to a method that doesn't exist in this implementation, then it shouldn't fail until if/when we actually try and invoke that missing method. * tests: Do feature detection in Array.prototype.values test * Add JsValue::{is_string, as_js_string} methods And document all the cast/convert/check methods for js value. * eslint: allow backtick string literals * Only generate a fallback import function for non-structural imports
2018-07-09 16:35:25 -07:00
fn has_ident_attribute(list: Option<&ExtendedAttributeList>, ident: &str) -> bool {
let list = match list {
Some(list) => list,
None => return false,
};
list.body.list.iter().any(|attr| match attr {
ExtendedAttribute::Ident(id) => id.lhs_identifier.0 == ident,
ExtendedAttribute::IdentList(id) => id.identifier.0 == ident,
_ => false,
})
}
/// ChromeOnly is for things that are only exposed to privileged code in Firefox.
pub fn is_chrome_only(ext_attrs: &Option<ExtendedAttributeList>) -> bool {
has_named_attribute(ext_attrs.as_ref(), "ChromeOnly")
}
/// Whether a webidl object is marked as a no interface object.
pub fn is_no_interface_object(ext_attrs: &Option<ExtendedAttributeList>) -> bool {
has_named_attribute(ext_attrs.as_ref(), "NoInterfaceObject")
}
2018-11-27 12:07:59 -08:00
pub fn get_rust_deprecated<'a>(ext_attrs: &Option<ExtendedAttributeList<'a>>) -> Option<&'a str> {
ext_attrs
.as_ref()?
.body
.list
.iter()
2018-11-27 12:07:59 -08:00
.filter_map(|attr| match attr {
ExtendedAttribute::Ident(id) => Some(id),
_ => None,
})
.filter(|attr| attr.lhs_identifier.0 == "RustDeprecated")
2018-11-27 12:07:59 -08:00
.filter_map(|ident| match ident.rhs {
IdentifierOrString::String(s) => Some(s),
IdentifierOrString::Identifier(_) => None,
})
.next()
.map(|s| s.0)
}
/// Whether a webidl object is marked as structural.
pub fn is_structural(
item_attrs: Option<&ExtendedAttributeList>,
container_attrs: Option<&ExtendedAttributeList>,
) -> bool {
// Note that once host bindings is implemented we'll want to switch this
// from `true` to `false`, and then we'll want to largely read information
// from the WebIDL about whether to use structural bindings or not.
true
|| has_named_attribute(item_attrs, "Unforgeable")
|| has_named_attribute(container_attrs, "Unforgeable")
|| has_ident_attribute(container_attrs, "Global")
Create the `web-sys` crate mechanically from WebIDL (#409) * Create a new `web-sys` crate This will eventually contain all the WebIDL-generated bindings to Web APIs. * ci: Test the new `web-sys` crate in CI * web-sys: Add a small README * web-sys: Vendor all the WebIDL files from mozilla-central * backend: Add a pass to remove AST items that use undefined imports This is necessary for the WebIDL frontend, which can't translate many WebIDL constructs into equivalent wasm-bindgen AST things yet. It lets us make incremental progress: we can generate bindings to methods we can support right now even though there might be methods on the same interface that we can't support yet. * webidl: Add a bunch of missing semicolons * webidl: Make parsing private It was only `pub` so that we could test it, but we ended up moving towards integration tests rather than unit tests that assert particular ASTs are parsed from WebIDL files. * webidl: Remove uses of undefined import types * test-project-builder: Build projects in "very verbose" mode This helps for debugging failing WebIDL-related tests. * test-project-builder: Add more profiling timers * test-project-builder: Detect when webpack-dev-server fails Instead of going into an infinite loop, detect when webpack-dev-server fails to start up and early exit the test. * webidl: Specify version for dev-dependency on wasm-bindgen-backend Instead of only a relative path. * guide: Add section about contributing to `web-sys` * WIP enable Event.webidl Still need to fix and finish the test. * Update expected webidl output * Start out a test's status as incomplete That way if we don't fill it in the error message doesn't look quite so bizarre * Fix onerror function in headless mode Otherwise we don't see any output! * Fix package.json/node_modules handling in project generation Make sure these are looked up in the git project root rather than the crate root * Avoid logging body text This was meant for debugging and is otherwise pretty noisy * Fix a relative path * More expected test fixes * Fix a typo * test-project-builder: Allow asynchronous tests * webidl: Convert [Unforgeable] attributes into `#[wasm_bindgen(structural)]` Fixes #432 * test-project-builder: Print generated WebIDL bindings for debugging purposes Helps debug bad WebIDL bindings generation inside tests. * When we can't find a descriptor, say which one can't be found This helps when debugging things that need to become structural. * web-sys: Test bindings for Event * ci: Use `--manifest-path dir` instead of `cd dir && ...` * web-sys: Just move .webidl files isntead of symlinking to enable them * tests: Polyfill Array.prototype.values for older browsers in CI * test-project-builder: Don't panic on poisoned headless test mutex We only use it to serialize headless tests so that we don't try to bind the port concurrently. Its OK to run another headless test if an earlier one panicked. * JsValue: Add {is,as}_{object,function} methods Allows dynamically casting values to `js::Object` and `js::Function`. * tidy: Fix whitespace and missing semicolons * Allow for dynamic feature detection of methods If we create bindings to a method that doesn't exist in this implementation, then it shouldn't fail until if/when we actually try and invoke that missing method. * tests: Do feature detection in Array.prototype.values test * Add JsValue::{is_string, as_js_string} methods And document all the cast/convert/check methods for js value. * eslint: allow backtick string literals * Only generate a fallback import function for non-structural imports
2018-07-09 16:35:25 -07:00
}
/// Whether a webidl object is marked as throwing.
pub fn throws(attrs: &Option<ExtendedAttributeList>) -> bool {
has_named_attribute(attrs.as_ref(), "Throws")
}
/// Create a syn `pub` token
pub fn public() -> syn::Visibility {
syn::Visibility::Public(syn::VisPublic {
pub_token: Default::default(),
})
}
2019-01-14 17:45:43 -05:00
/// When generating our web_sys APIs we default to setting slice references that
/// get passed to JS as mutable in case they get mutated in JS.
///
/// In certain cases we know for sure that the slice will not get mutated - for
/// example when working with the WebGlRenderingContext APIs.
///
/// Here we implement a whitelist for those cases. This whitelist is currently
/// maintained by hand.
fn maybe_adjust<'a>(mut idl_type: IdlType<'a>, id: &'a OperationId) -> IdlType<'a> {
let immutable_f32_fns: Vec<&'static str> = vec![
// WebGlRenderingContext
"uniform1fv",
"uniform2fv",
"uniform3fv",
"uniform4fv",
"uniformMatrix2fv",
"uniformMatrix3fv",
"uniformMatrix4fv",
2019-01-26 12:19:28 -05:00
"vertexAttrib1fv",
"vertexAttrib2fv",
"vertexAttrib3fv",
"vertexAttrib4fv",
// TODO: Add another type's functions here. Leave a comment header with the type name
];
let mut immutable_f32_slice_whitelist: HashSet<&'static str> = HashSet::new();
for function_name in immutable_f32_fns.into_iter() {
immutable_f32_slice_whitelist.insert(function_name);
}
2019-01-22 21:35:22 -05:00
// example `op`... -> "vertexAttrib1fv"
if let OperationId::Operation(Some(op)) = id {
// Look up this funtion in our whitelist and see if we should make its
// slice argument immutable.
if immutable_f32_slice_whitelist.get(op).is_some() {
if let IdlType::Union(ref mut union) = idl_type {
if let IdlType::Float32Array { ref mut immutable } = union[0] {
*immutable = true;
}
}
2019-01-14 17:45:43 -05:00
}
}
idl_type
2019-01-14 17:45:43 -05:00
}