diff --git a/Cargo.toml b/Cargo.toml index acb18a27..45473c90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ Easy support for interacting between JS and Rust. edition = "2018" [package.metadata.docs.rs] -features = ['serde-serialize'] +features = ["serde-serialize"] +rustc-args = ["--cfg=web_sys_unstable_apis"] [lib] test = false diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5031f538..2261d673 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -122,6 +122,10 @@ jobs: - script: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features Element - script: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features Window - script: cargo test --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --all-features + - script: cargo test --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --all-features + displayName: "web-sys unstable APIs" + env: + RUSTFLAGS: --cfg=web_sys_unstable_apis - job: test_js_sys displayName: "Run js-sys crate tests" @@ -150,6 +154,10 @@ jobs: - script: cargo test -p webidl-tests --target wasm32-unknown-unknown env: WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: 1 + - script: cargo test -p webidl-tests --target wasm32-unknown-unknown + displayName: "webidl-tests unstable APIs" + env: + RUSTFLAGS: --cfg=web_sys_unstable_apis - job: test_ui displayName: "Run UI tests" @@ -319,6 +327,8 @@ jobs: displayName: "Document js-sys" - script: cargo doc --no-deps --manifest-path crates/web-sys/Cargo.toml --all-features displayName: "Document web-sys" + env: + RUSTFLAGS: --cfg=web_sys_unstable_apis - script: cargo doc --no-deps --manifest-path crates/futures/Cargo.toml displayName: "Document wasm-bindgen-futures" # Make a tarball even though a zip is uploaded, it looks like the tarball diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 047dfe1a..63e626fe 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -51,6 +51,8 @@ pub struct Export { /// Whether or not this function should be flagged as the wasm start /// function. pub start: bool, + /// Whether the API is unstable. This is only used internally. + pub unstable_api: bool, } /// The 3 types variations of `self`. @@ -71,6 +73,7 @@ pub struct Import { pub module: ImportModule, pub js_namespace: Option, pub kind: ImportKind, + pub unstable_api: bool, } #[cfg_attr(feature = "extra-traits", derive(Debug))] @@ -126,6 +129,7 @@ pub struct ImportFunction { pub kind: ImportFunctionKind, pub shim: Ident, pub doc_comment: Option, + pub unstable_api: bool, } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] @@ -182,6 +186,7 @@ pub struct ImportType { pub js_name: String, pub attrs: Vec, pub typescript_name: Option, + pub unstable_api: bool, pub doc_comment: Option, pub instanceof_shim: String, pub is_type_of: Option, @@ -202,6 +207,8 @@ pub struct ImportEnum { pub variant_values: Vec, /// Attributes to apply to the Rust enum pub rust_attrs: Vec, + /// Whether the enum is part of an unstable WebIDL + pub unstable_api: bool, } #[cfg_attr(feature = "extra-traits", derive(Debug))] @@ -237,6 +244,7 @@ pub struct StructField { pub getter: Ident, pub setter: Ident, pub comments: Vec, + pub unstable_api: bool, } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] @@ -246,6 +254,7 @@ pub struct Enum { pub variants: Vec, pub comments: Vec, pub hole: u32, + pub unstable_api: bool, } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] @@ -278,6 +287,7 @@ pub struct Const { pub class: Option, pub ty: syn::Type, pub value: ConstValue, + pub unstable_api: bool, } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq))] @@ -299,6 +309,7 @@ pub struct Dictionary { pub ctor: bool, pub doc_comment: Option, pub ctor_doc_comment: Option, + pub unstable_api: bool, } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 5d864009..315c8912 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -1,6 +1,6 @@ use crate::ast; use crate::encode; -use crate::util::ShortHash; +use crate::util::{self, ShortHash}; use crate::Diagnostic; use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::{quote, ToTokens}; @@ -39,7 +39,10 @@ impl TryToTokens for ast::Program { } } for i in self.imports.iter() { - DescribeImport(&i.kind).to_tokens(tokens); + DescribeImport { + kind: &i.kind, + unstable_api: i.unstable_api, + }.to_tokens(tokens); // If there is a js namespace, check that name isn't a type. If it is, // this import might be a method on that type. @@ -296,12 +299,13 @@ impl ToTokens for ast::StructField { }) .to_tokens(tokens); - Descriptor( - &getter, - quote! { + Descriptor { + ident: &getter, + inner: quote! { <#ty as WasmDescribe>::describe(); }, - ) + unstable_api: self.unstable_api, + } .to_tokens(tokens); if self.readonly { @@ -528,16 +532,17 @@ impl TryToTokens for ast::Export { // this, but the tl;dr; is that this is stripped from the final wasm // binary along with anything it references. let export = Ident::new(&export_name, Span::call_site()); - Descriptor( - &export, - quote! { + Descriptor { + ident: &export, + inner: quote! { inform(FUNCTION); inform(0); inform(#nargs); #(<#argtys as WasmDescribe>::describe();)* #describe_ret }, - ) + unstable_api: self.unstable_api, + } .to_tokens(into); Ok(()) @@ -562,6 +567,7 @@ impl ToTokens for ast::ImportType { let vis = &self.vis; let rust_name = &self.rust_name; let attrs = &self.attrs; + let unstable_api_attr = util::maybe_unstable_api_attr(self.unstable_api); let doc_comment = match &self.doc_comment { None => "", Some(comment) => comment, @@ -610,12 +616,14 @@ impl ToTokens for ast::ImportType { #[doc = #doc_comment] #[repr(transparent)] #[allow(clippy::all)] + #unstable_api_attr #vis struct #rust_name { obj: #internal_obj } #[allow(bad_style)] #[allow(clippy::all)] + #unstable_api_attr const #const_name: () = { use wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi}; use wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; @@ -766,6 +774,7 @@ impl ToTokens for ast::ImportType { for superclass in self.extends.iter() { (quote! { #[allow(clippy::all)] + #unstable_api_attr impl From<#rust_name> for #superclass { #[inline] fn from(obj: #rust_name) -> #superclass { @@ -775,6 +784,7 @@ impl ToTokens for ast::ImportType { } #[allow(clippy::all)] + #unstable_api_attr impl AsRef<#superclass> for #rust_name { #[inline] fn as_ref(&self) -> &#superclass { @@ -796,6 +806,7 @@ impl ToTokens for ast::ImportEnum { let variants = &self.variants; let variant_strings = &self.variant_values; let attrs = &self.rust_attrs; + let unstable_api_attr = util::maybe_unstable_api_attr(self.unstable_api); let mut current_idx: usize = 0; let variant_indexes: Vec = variants @@ -824,6 +835,7 @@ impl ToTokens for ast::ImportEnum { #[allow(bad_style)] #(#attrs)* #[allow(clippy::all)] + #unstable_api_attr #vis enum #name { #(#variants = #variant_indexes_ref,)* #[doc(hidden)] @@ -831,6 +843,7 @@ impl ToTokens for ast::ImportEnum { } #[allow(clippy::all)] + #unstable_api_attr impl #name { #vis fn from_js_value(obj: &wasm_bindgen::JsValue) -> Option<#name> { obj.as_string().and_then(|obj_str| match obj_str.as_str() { @@ -841,6 +854,7 @@ impl ToTokens for ast::ImportEnum { } #[allow(clippy::all)] + #unstable_api_attr impl wasm_bindgen::describe::WasmDescribe for #name { fn describe() { wasm_bindgen::JsValue::describe() @@ -848,6 +862,7 @@ impl ToTokens for ast::ImportEnum { } #[allow(clippy::all)] + #unstable_api_attr impl wasm_bindgen::convert::IntoWasmAbi for #name { type Abi = ::Abi; @@ -859,6 +874,7 @@ impl ToTokens for ast::ImportEnum { } #[allow(clippy::all)] + #unstable_api_attr impl wasm_bindgen::convert::FromWasmAbi for #name { type Abi = ::Abi; @@ -869,18 +885,21 @@ impl ToTokens for ast::ImportEnum { } #[allow(clippy::all)] + #unstable_api_attr impl wasm_bindgen::convert::OptionIntoWasmAbi for #name { #[inline] fn none() -> Self::Abi { Object::none() } } #[allow(clippy::all)] + #unstable_api_attr impl wasm_bindgen::convert::OptionFromWasmAbi for #name { #[inline] fn is_none(abi: &Self::Abi) -> bool { Object::is_none(abi) } } #[allow(clippy::all)] + #unstable_api_attr impl From<#name> for wasm_bindgen::JsValue { fn from(obj: #name) -> wasm_bindgen::JsValue { match obj { @@ -992,6 +1011,7 @@ impl TryToTokens for ast::ImportFunction { let arguments = &arguments; let abi_arguments = &abi_arguments; let abi_argument_names = &abi_argument_names; + let unstable_api_attr = util::maybe_unstable_api_attr(self.unstable_api); let doc_comment = match &self.doc_comment { None => "", @@ -1058,6 +1078,7 @@ impl TryToTokens for ast::ImportFunction { if let Some(class) = class_ty { (quote! { + #unstable_api_attr impl #class { #invocation } @@ -1072,11 +1093,14 @@ impl TryToTokens for ast::ImportFunction { } // See comment above in ast::Export for what's going on here. -struct DescribeImport<'a>(&'a ast::ImportKind); +struct DescribeImport<'a> { + kind: &'a ast::ImportKind, + unstable_api: bool, +} impl<'a> ToTokens for DescribeImport<'a> { fn to_tokens(&self, tokens: &mut TokenStream) { - let f = match *self.0 { + let f = match *self.kind { ast::ImportKind::Function(ref f) => f, ast::ImportKind::Static(_) => return, ast::ImportKind::Type(_) => return, @@ -1089,16 +1113,17 @@ impl<'a> ToTokens for DescribeImport<'a> { None => quote! { <() as WasmDescribe>::describe(); }, }; - Descriptor( - &f.shim, - quote! { + Descriptor { + ident: &f.shim, + inner: quote! { inform(FUNCTION); inform(0); inform(#nargs); #(<#argtys as WasmDescribe>::describe();)* #inform_ret }, - ) + unstable_api: self.unstable_api, + } .to_tokens(tokens); } } @@ -1107,6 +1132,7 @@ impl ToTokens for ast::Enum { fn to_tokens(&self, into: &mut TokenStream) { let enum_name = &self.name; let hole = &self.hole; + let unstable_api_attr = util::maybe_unstable_api_attr(self.unstable_api); let cast_clauses = self.variants.iter().map(|variant| { let variant_name = &variant.name; quote! { @@ -1117,6 +1143,7 @@ impl ToTokens for ast::Enum { }); (quote! { #[allow(clippy::all)] + #unstable_api_attr impl wasm_bindgen::convert::IntoWasmAbi for #enum_name { type Abi = u32; @@ -1127,6 +1154,7 @@ impl ToTokens for ast::Enum { } #[allow(clippy::all)] + #unstable_api_attr impl wasm_bindgen::convert::FromWasmAbi for #enum_name { type Abi = u32; @@ -1139,18 +1167,21 @@ impl ToTokens for ast::Enum { } #[allow(clippy::all)] + #unstable_api_attr impl wasm_bindgen::convert::OptionFromWasmAbi for #enum_name { #[inline] fn is_none(val: &u32) -> bool { *val == #hole } } #[allow(clippy::all)] + #unstable_api_attr impl wasm_bindgen::convert::OptionIntoWasmAbi for #enum_name { #[inline] fn none() -> Self::Abi { #hole } } #[allow(clippy::all)] + #unstable_api_attr impl wasm_bindgen::describe::WasmDescribe for #enum_name { fn describe() { use wasm_bindgen::describe::*; @@ -1196,12 +1227,13 @@ impl ToTokens for ast::ImportStatic { }) .to_tokens(into); - Descriptor( - &shim_name, - quote! { + Descriptor { + ident: &shim_name, + inner: quote! { <#ty as WasmDescribe>::describe(); }, - ) + unstable_api: false, + } .to_tokens(into); } } @@ -1213,6 +1245,7 @@ impl ToTokens for ast::Const { let vis = &self.vis; let name = &self.name; let ty = &self.ty; + let unstable_api_attr = util::maybe_unstable_api_attr(self.unstable_api); let value: TokenStream = match self.value { BooleanLiteral(false) => quote!(false), @@ -1244,6 +1277,7 @@ impl ToTokens for ast::Const { if let Some(class) = &self.class { (quote! { + #unstable_api_attr impl #class { #declaration } @@ -1257,6 +1291,7 @@ impl ToTokens for ast::Const { impl ToTokens for ast::Dictionary { fn to_tokens(&self, tokens: &mut TokenStream) { + let unstable_api_attr = util::maybe_unstable_api_attr(self.unstable_api); let name = &self.name; let mut methods = TokenStream::new(); for field in self.fields.iter() { @@ -1304,11 +1339,13 @@ impl ToTokens for ast::Dictionary { #[repr(transparent)] #[allow(clippy::all)] #[doc = #doc_comment] + #unstable_api_attr pub struct #name { obj: ::js_sys::Object, } #[allow(clippy::all)] + #unstable_api_attr impl #name { #ctor #methods @@ -1316,6 +1353,7 @@ impl ToTokens for ast::Dictionary { #[allow(bad_style)] #[allow(clippy::all)] + #unstable_api_attr const #const_name: () = { use js_sys::Object; use wasm_bindgen::describe::WasmDescribe; @@ -1443,7 +1481,11 @@ impl ToTokens for ast::DictionaryField { /// Emits the necessary glue tokens for "descriptor", generating an appropriate /// symbol name as well as attributes around the descriptor function itself. -struct Descriptor<'a, T>(&'a Ident, T); +struct Descriptor<'a, T> { + ident: &'a Ident, + inner: T, + unstable_api: bool, +} impl<'a, T: ToTokens> ToTokens for Descriptor<'a, T> { fn to_tokens(&self, tokens: &mut TokenStream) { @@ -1458,22 +1500,27 @@ impl<'a, T: ToTokens> ToTokens for Descriptor<'a, T> { lazy_static::lazy_static! { static ref DESCRIPTORS_EMITTED: Mutex> = Default::default(); } + + let ident = self.ident; + if !DESCRIPTORS_EMITTED .lock() .unwrap() - .insert(self.0.to_string()) + .insert(ident.to_string()) { return; } - let name = Ident::new(&format!("__wbindgen_describe_{}", self.0), self.0.span()); - let inner = &self.1; + let name = Ident::new(&format!("__wbindgen_describe_{}", ident), ident.span()); + let unstable_api_attr = util::maybe_unstable_api_attr(self.unstable_api); + let inner = &self.inner; (quote! { #[no_mangle] #[allow(non_snake_case)] #[doc(hidden)] #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] #[allow(clippy::all)] + #unstable_api_attr pub extern "C" fn #name() { use wasm_bindgen::describe::*; // See definition of `link_mem_intrinsics` for what this is doing diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index d60e34c6..081a52cf 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -188,6 +188,7 @@ fn shared_export<'a>( function: shared_function(&export.function, intern), method_kind, start: export.start, + unstable_api: export.unstable_api, }) } diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index 950ae9b4..766aac06 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -113,10 +113,12 @@ pub fn ident_ty(ident: Ident) -> syn::Type { } pub fn wrap_import_function(function: ast::ImportFunction) -> ast::Import { + let unstable_api = function.unstable_api; ast::Import { module: ast::ImportModule::None, js_namespace: None, kind: ast::ImportKind::Function(function), + unstable_api, } } @@ -154,3 +156,20 @@ impl fmt::Display for ShortHash { write!(f, "{:016x}", h.finish()) } } + + +/// Create syn attribute for `#[cfg(web_sys_unstable_apis)]` and a doc comment. +pub fn unstable_api_attrs() -> proc_macro2::TokenStream { + quote::quote!( + #[cfg(web_sys_unstable_apis)] + #[doc = "\n\n*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as [described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"] + ) +} + +pub fn maybe_unstable_api_attr(unstable_api: bool) -> Option { + if unstable_api { + Some(unstable_api_attrs()) + } else { + None + } +} diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 2be71c16..4727a665 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -22,6 +22,7 @@ struct AttributeParseState { pub struct BindgenAttrs { /// List of parsed attributes pub attrs: Vec<(Cell, BindgenAttr)>, + pub unstable_api_attr: Option, } macro_rules! attrgen { @@ -186,7 +187,7 @@ impl Default for BindgenAttrs { // sanity check that we call `check_used` an appropriate number of // times. ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1)); - BindgenAttrs { attrs: Vec::new() } + BindgenAttrs { attrs: Vec::new(), unstable_api_attr: None, } } } @@ -353,6 +354,7 @@ impl<'a> ConvertToAst for &'a mut syn::ItemStruct { getter: Ident::new(&getter, Span::call_site()), setter: Ident::new(&setter, Span::call_site()), comments, + unstable_api: false, }); attrs.check_used()?; } @@ -513,6 +515,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a ast::ImportModule)> for syn::ForeignIte rust_name: self.sig.ident.clone(), shim: Ident::new(&shim, Span::call_site()), doc_comment: None, + unstable_api: false, }); opts.check_used()?; @@ -550,6 +553,7 @@ impl ConvertToAst for syn::ForeignItemType { Ok(ast::ImportKind::Type(ast::ImportType { vis: self.vis, attrs: self.attrs, + unstable_api: false, doc_comment: None, instanceof_shim: shim, is_type_of, @@ -777,6 +781,7 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { rust_class: None, rust_name, start, + unstable_api: false, }); } syn::Item::Struct(mut s) => { @@ -977,6 +982,7 @@ impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod { rust_class: Some(class.clone()), rust_name: self.sig.ident.clone(), start: false, + unstable_api: false, }); opts.check_used()?; Ok(()) @@ -1071,6 +1077,7 @@ impl MacroParse<()> for syn::ItemEnum { variants, comments, hole, + unstable_api: false, }); Ok(()) } @@ -1175,6 +1182,7 @@ impl MacroParse for syn::ForeignItem { module, js_namespace, kind, + unstable_api: false, }); Ok(()) diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index ceea8baf..e0b59c11 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -94,6 +94,7 @@ macro_rules! shared_api { function: Function<'a>, method_kind: MethodKind<'a>, start: bool, + unstable_api: bool, } struct Enum<'a> { diff --git a/crates/web-sys/Cargo.toml b/crates/web-sys/Cargo.toml index bbc4ae00..75fc4fa0 100644 --- a/crates/web-sys/Cargo.toml +++ b/crates/web-sys/Cargo.toml @@ -389,6 +389,105 @@ Geolocation = [] GetNotificationOptions = [] GetRootNodeOptions = [] GetUserMediaRequest = [] +Gpu = [] +GpuAdapter = [] +GpuAddressMode = [] +GpuBindGroup = [] +GpuBindGroupBinding = [] +GpuBindGroupDescriptor = [] +GpuBindGroupLayout = [] +GpuBindGroupLayoutBinding = [] +GpuBindGroupLayoutDescriptor = [] +GpuBindingType = [] +GpuBlendDescriptor = [] +GpuBlendFactor = [] +GpuBlendOperation = [] +GpuBuffer = [] +GpuBufferBinding = [] +GpuBufferCopyView = [] +GpuBufferDescriptor = [] +GpuBufferUsage = [] +GpuCanvasContext = [] +GpuColorDict = [] +GpuColorStateDescriptor = [] +GpuColorWrite = [] +GpuCommandBuffer = [] +GpuCommandBufferDescriptor = [] +GpuCommandEncoder = [] +GpuCommandEncoderDescriptor = [] +GpuCompareFunction = [] +GpuComputePassDescriptor = [] +GpuComputePassEncoder = [] +GpuComputePipeline = [] +GpuComputePipelineDescriptor = [] +GpuCullMode = [] +GpuDepthStencilStateDescriptor = [] +GpuDevice = [] +GpuDeviceDescriptor = [] +GpuDeviceLostInfo = [] +GpuErrorFilter = [] +GpuExtensionName = [] +GpuExtent3dDict = [] +GpuFence = [] +GpuFenceDescriptor = [] +GpuFilterMode = [] +GpuFrontFace = [] +GpuImageBitmapCopyView = [] +GpuIndexFormat = [] +GpuInputStepMode = [] +GpuLimits = [] +GpuLoadOp = [] +GpuObjectDescriptorBase = [] +GpuOrigin2dDict = [] +GpuOrigin3dDict = [] +GpuOutOfMemoryError = [] +GpuPipelineDescriptorBase = [] +GpuPipelineLayout = [] +GpuPipelineLayoutDescriptor = [] +GpuPowerPreference = [] +GpuPrimitiveTopology = [] +GpuProgrammableStageDescriptor = [] +GpuQueue = [] +GpuRasterizationStateDescriptor = [] +GpuRenderBundle = [] +GpuRenderBundleDescriptor = [] +GpuRenderBundleEncoder = [] +GpuRenderBundleEncoderDescriptor = [] +GpuRenderPassColorAttachmentDescriptor = [] +GpuRenderPassDepthStencilAttachmentDescriptor = [] +GpuRenderPassDescriptor = [] +GpuRenderPassEncoder = [] +GpuRenderPipeline = [] +GpuRenderPipelineDescriptor = [] +GpuRequestAdapterOptions = [] +GpuSampler = [] +GpuSamplerDescriptor = [] +GpuShaderModule = [] +GpuShaderModuleDescriptor = [] +GpuShaderStage = [] +GpuStencilOperation = [] +GpuStencilStateFaceDescriptor = [] +GpuStoreOp = [] +GpuSwapChain = [] +GpuSwapChainDescriptor = [] +GpuTexture = [] +GpuTextureAspect = [] +GpuTextureComponentType = [] +GpuTextureCopyView = [] +GpuTextureDescriptor = [] +GpuTextureDimension = [] +GpuTextureFormat = [] +GpuTextureUsage = [] +GpuTextureView = [] +GpuTextureViewDescriptor = [] +GpuTextureViewDimension = [] +GpuUncapturedErrorEvent = [] +GpuUncapturedErrorEventInit = [] +GpuValidationError = [] +GpuVertexAttributeDescriptor = [] +GpuVertexBufferLayoutDescriptor = [] +GpuVertexFormat = [] +GpuVertexStateDescriptor = [] GridDeclaration = [] GridTrackState = [] GroupedHistoryEventInit = [] diff --git a/crates/web-sys/build.rs b/crates/web-sys/build.rs index 51210840..6a40df49 100644 --- a/crates/web-sys/build.rs +++ b/crates/web-sys/build.rs @@ -7,16 +7,12 @@ use std::fs; use std::path::{self, PathBuf}; use std::process::Command; -fn main() -> Result<()> { - #[cfg(feature = "env_logger")] - env_logger::init(); - println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=webidls/enabled"); - - let entries = fs::read_dir("webidls/enabled").context("reading webidls/enabled directory")?; +/// Read all WebIDL files in a directory into a single `SourceFile` +fn read_source_from_path(dir: &str) -> Result { + let entries = fs::read_dir(dir).context("reading webidls/enabled directory")?; let mut source = SourceFile::default(); for entry in entries { - let entry = entry.context("getting webidls/enabled/*.webidl entry")?; + let entry = entry.context(format!("getting {}/*.webidl entry", dir))?; let path = entry.path(); if path.extension() != Some(OsStr::new("webidl")) { continue; @@ -27,6 +23,16 @@ fn main() -> Result<()> { .with_context(|| format!("reading contents of file \"{}\"", path.display()))?; } + Ok(source) +} + +fn main() -> Result<()> { + #[cfg(feature = "env_logger")] + env_logger::init(); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=webidls/enabled"); + println!("cargo:rerun-if-changed=webidls/unstable"); + // Read our manifest, learn all `[feature]` directives with "toml parsing". // Use all these names to match against environment variables set by Cargo // to figure out which features are activated to we can pass that down to @@ -63,7 +69,10 @@ fn main() -> Result<()> { Some(&allowed[..]) }; - let bindings = match wasm_bindgen_webidl::compile(&source.contents, allowed) { + let source = read_source_from_path("webidls/enabled")?; + let unstable_source = read_source_from_path("webidls/unstable")?; + + let bindings = match wasm_bindgen_webidl::compile(&source.contents, &unstable_source.contents, allowed) { Ok(bindings) => bindings, Err(e) => { if let Some(err) = e.downcast_ref::() { diff --git a/crates/web-sys/webidls/disabled/WebGPU.webidl b/crates/web-sys/webidls/disabled/WebGPU.webidl deleted file mode 100644 index 2ab932a5..00000000 --- a/crates/web-sys/webidls/disabled/WebGPU.webidl +++ /dev/null @@ -1,638 +0,0 @@ -/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The origin of this IDL file is - * https://github.com/gpuweb/gpuweb/blob/master/design/sketch.webidl - */ - -typedef unsigned long u32; -typedef unsigned long long u64; - -// **************************************************************************** -// ERROR HANDLING -// **************************************************************************** - -enum WebGPULogEntryType { - "device-lost", - "validation-error", - "recoverable-out-of-memory", -}; - -[Pref="dom.webgpu.enable"] -interface WebGPULogEntry { - readonly attribute WebGPULogEntryType type; - readonly attribute any obj; - readonly attribute DOMString? reason; -}; - -enum WebGPUObjectStatus { - "valid", - "out-of-memory", - "invalid", -}; - -typedef (WebGPUBuffer or WebGPUTexture) WebGPUStatusable; - -callback WebGPULogCallback = void (WebGPULogEntry error); - -// **************************************************************************** -// SHADER RESOURCES (buffer, textures, texture views, samples) -// **************************************************************************** - -// Buffer -typedef u32 WebGPUBufferUsageFlags; -[Pref="dom.webgpu.enable"] -interface WebGPUBufferUsage { - const u32 NONE = 0; - const u32 MAP_READ = 1; - const u32 MAP_WRITE = 2; - const u32 TRANSFER_SRC = 4; - const u32 TRANSFER_DST = 8; - const u32 INDEX = 16; - const u32 VERTEX = 32; - const u32 UNIFORM = 64; - const u32 STORAGE = 128; -}; - -dictionary WebGPUBufferDescriptor { - u32 size; - WebGPUBufferUsageFlags usage; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUBuffer { - readonly attribute ArrayBuffer? mapping; - void unmap(); -}; - -// Texture view -dictionary WebGPUTextureViewDescriptor { - // TODO Investigate what goes in there. -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUTextureView { -}; - -// Texture -typedef u32 WebGPUTextureDimensionEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUTextureDimension { - const u32 e1D = 0; - const u32 e2D = 1; - const u32 e3D = 2; - // TODO other dimensions (cube, arrays) -}; - -typedef u32 WebGPUTextureFormatEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUTextureFormat { - const u32 R8_G8_B8_A8_UNORM = 0; - const u32 R8_G8_B8_A8_UINT = 1; - const u32 B8_G8_R8_A8_UNORM = 2; - const u32 D32_FLOAT_S8_UINT = 3; - // TODO other formats -}; - -typedef u32 WebGPUTextureUsageFlags; -[Pref="dom.webgpu.enable"] -interface WebGPUTextureUsage { - const u32 NONE = 0; - const u32 TRANSFER_SRC = 1; - const u32 TRANSFER_DST = 2; - const u32 SAMPLED = 4; - const u32 STORAGE = 8; - const u32 OUTPUT_ATTACHMENT = 16; - const u32 PRESENT = 32; -}; - -dictionary WebGPUTextureDescriptor { - u32 width; - u32 height; - u32 depth; - u32 arraySize; - WebGPUTextureDimensionEnum dimension; - WebGPUTextureFormatEnum format; - WebGPUTextureUsageFlags usage; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUTexture { - WebGPUTextureView createTextureView(optional WebGPUTextureViewDescriptor desc); -}; - -// Sampler -typedef u32 WebGPUFilterModeEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUFilterMode { - const u32 NEAREST = 0; - const u32 LINEAR = 1; -}; - -dictionary WebGPUSamplerDescriptor { - WebGPUFilterModeEnum magFilter; - WebGPUFilterModeEnum minFilter; - WebGPUFilterModeEnum mipmapFilter; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUSampler { -}; - -// **************************************************************************** -// BINDING MODEL (bindgroup layout, bindgroup) -// **************************************************************************** - -// BindGroupLayout -typedef u32 WebGPUShaderStageFlags; -[Pref="dom.webgpu.enable"] -interface WebGPUShaderStageBit { - const u32 NONE = 0; - const u32 VERTEX = 1; - const u32 FRAGMENT = 2; - const u32 COMPUTE = 4; -}; - -typedef u32 WebGPUBindingTypeEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUBindingType { - const u32 UNIFORM_BUFFER = 0; - const u32 SAMPLER = 1; - const u32 SAMPLED_TEXTURE = 2; - const u32 STORAGE_BUFFER = 3; - // TODO other binding types (storage images, ...) -}; - -dictionary WebGPUBindGroupBinding { - WebGPUShaderStageFlags visibility; - WebGPUBindingTypeEnum type; - u32 start; - u32 count; -}; - -dictionary WebGPUBindGroupLayoutDescriptor { - sequence bindingTypes; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUBindGroupLayout { -}; - -// PipelineLayout -dictionary WebGPUPipelineLayoutDescriptor { - sequence bindGroupLayouts; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUPipelineLayout { -}; - -// BindGroup -/* Moved to WebGPUExtras.webidl for now. -dictionary WebGPUBufferBinding { - WebGPUBuffer buffer; - u32 offset; - u32 size; -}; -*/ - -typedef (WebGPUSampler or WebGPUTextureView or WebGPUBufferBinding) WebGPUBindingResource; - -dictionary WebGPUBinding { - sequence resources; - u32 start; - u32 count; -}; - -dictionary WebGPUBindGroupDescriptor { - WebGPUBindGroupLayout layout; - sequence bindings; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUBindGroup { -}; - -// **************************************************************************** -// PIPELINE CREATION (blend state, DS state, ..., pipelines) -// **************************************************************************** - -// BlendState -typedef u32 WebGPUBlendFactorEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUBlendFactor { - const u32 ZERO = 0; - const u32 ONE = 1; - const u32 SRC_COLOR = 2; - const u32 ONE_MINUS_SRC_COLOR = 3; - const u32 SRC_ALPHA = 4; - const u32 ONE_MINUS_SRC_ALPHA = 5; - const u32 DST_COLOR = 6; - const u32 ONE_MINUS_DST_COLOR = 7; - const u32 DST_ALPHA = 8; - const u32 ONE_MINUS_DST_ALPHA = 9; - const u32 SRC_ALPHA_SATURATED = 10; - const u32 BLEND_COLOR = 11; - const u32 ONE_MINUS_BLEND_COLOR = 12; -}; - -typedef u32 WebGPUBlendOperationEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUBlendOperation { - const u32 ADD = 0; - const u32 SUBTRACT = 1; - const u32 REVERSE_SUBTRACT = 2; - const u32 MIN = 3; - const u32 MAX = 4; -}; - -typedef u32 WebGPUColorWriteFlags; -[Pref="dom.webgpu.enable"] -interface WebGPUColorWriteBits { - const u32 NONE = 0; - const u32 RED = 1; - const u32 GREEN = 2; - const u32 BLUE = 4; - const u32 ALPHA = 8; - const u32 ALL = 15; -}; - -dictionary WebGPUBlendDescriptor { - WebGPUBlendFactorEnum srcFactor; - WebGPUBlendFactorEnum dstFactor; - WebGPUBlendOperationEnum operation; -}; - -dictionary WebGPUBlendStateDescriptor { - boolean blendEnabled; - WebGPUBlendDescriptor alpha; - WebGPUBlendDescriptor color; - WebGPUColorWriteFlags writeMask; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUBlendState { -}; - -// DepthStencilState -typedef u32 WebGPUCompareFunctionEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUCompareFunction { - const u32 NEVER = 0; - const u32 LESS = 1; - const u32 LESS_EQUAL = 2; - const u32 GREATER = 3; - const u32 GREATER_EQUAL = 4; - const u32 EQUAL = 5; - const u32 NOT_EQUAL = 6; - const u32 ALWAYS = 7; -}; - -typedef u32 WebGPUStencilOperationEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUStencilOperation { - const u32 KEEP = 0; - const u32 ZERO = 1; - const u32 REPLACE = 2; - const u32 INVERT = 3; - const u32 INCREMENT_CLAMP = 4; - const u32 DECREMENT_CLAMP = 5; - const u32 INCREMENT_WRAP = 6; - const u32 DECREMENT_WRAP = 7; -}; - -dictionary WebGPUStencilStateFaceDescriptor { - WebGPUCompareFunctionEnum compare; - WebGPUStencilOperationEnum stencilFailOp; - WebGPUStencilOperationEnum depthFailOp; - WebGPUStencilOperationEnum passOp; -}; - -dictionary WebGPUDepthStencilStateDescriptor { - boolean depthWriteEnabled; - WebGPUCompareFunctionEnum depthCompare; - - WebGPUStencilStateFaceDescriptor front; - WebGPUStencilStateFaceDescriptor back; - - u32 stencilReadMask; - u32 stencilWriteMask; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUDepthStencilState { -}; - -// InputState -typedef u32 WebGPUIndexFormatEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUIndexFormat { - const u32 UINT16 = 0; - const u32 UINT32 = 1; -}; - -typedef u32 WebGPUVertexFormatEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUVertexFormat { - const u32 FLOAT_R32_G32_B32_A32 = 0; - const u32 FLOAT_R32_G32_B32 = 1; - const u32 FLOAT_R32_G32 = 2; - const u32 FLOAT_R32 = 3; - // TODO other vertex formats -}; - -typedef u32 WebGPUInputStepModeEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUInputStepMode { - const u32 VERTEX = 0; - const u32 INSTANCE = 1; -}; - -dictionary WebGPUVertexAttributeDescriptor { - u32 shaderLocation; - u32 inputSlot; - u32 offset; - WebGPUVertexFormatEnum format; -}; - -dictionary WebGPUVertexInputDescriptor { - u32 inputSlot; - u32 stride; - WebGPUInputStepModeEnum stepMode; -}; - -dictionary WebGPUInputStateDescriptor { - WebGPUIndexFormatEnum indexFormat; - - sequence attributes; - sequence inputs; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUInputState { -}; - -// ShaderModule -dictionary WebGPUShaderModuleDescriptor { - required ArrayBuffer code; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUShaderModule { -}; - -// AttachmentState -dictionary WebGPUAttachmentStateDescriptor { - sequence formats; - // TODO other stuff like sample count etc. -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUAttachmentState { -}; - -// Common stuff for ComputePipeline and RenderPipeline -typedef u32 WebGPUShaderStageEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUShaderStage { - const u32 VERTEX = 0; - const u32 FRAGMENT = 1; - const u32 COMPUTE = 2; -}; - -dictionary WebGPUPipelineStageDescriptor { - required WebGPUShaderModule shaderModule; - required WebGPUShaderStageEnum stage; - required DOMString entryPoint; - // TODO other stuff like specialization constants? -}; - -dictionary WebGPUPipelineDescriptorBase { - required WebGPUPipelineLayout layout; - sequence stages; -}; - -// ComputePipeline -dictionary WebGPUComputePipelineDescriptor : WebGPUPipelineDescriptorBase { -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUComputePipeline { -}; - -// WebGPURenderPipeline -typedef u32 WebGPUPrimitiveTopologyEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUPrimitiveTopology { - const u32 POINT_LIST = 0; - const u32 LINE_LIST = 1; - const u32 LINE_STRIP = 2; - const u32 TRIANGLE_LIST = 3; - const u32 TRIANGLE_STRIP = 4; -}; - -dictionary WebGPURenderPipelineDescriptor : WebGPUPipelineDescriptorBase { - WebGPUPrimitiveTopologyEnum primitiveTopology; - sequence blendState; - WebGPUDepthStencilState depthStencilState; - WebGPUInputState inputState; - WebGPUAttachmentState attachmentState; - // TODO other properties -}; - -[Pref="dom.webgpu.enable"] -interface WebGPURenderPipeline { -}; -// **************************************************************************** -// COMMAND RECORDING (Command buffer and all relevant structures) -// **************************************************************************** - -typedef u32 WebGPULoadOpEnum; -[Pref="dom.webgpu.enable"] -interface WebGPULoadOp { - const u32 CLEAR = 0; - const u32 LOAD = 1; -}; - -typedef u32 WebGPUStoreOpEnum; -[Pref="dom.webgpu.enable"] -interface WebGPUStoreOp { - const u32 STORE = 0; -}; - -dictionary WebGPURenderPassAttachmentDescriptor { - WebGPUTextureView attachment; - WebGPULoadOpEnum loadOp; - WebGPUStoreOpEnum storeOp; -}; - -dictionary WebGPURenderPassDescriptor { - sequence colorAttachments; - WebGPURenderPassAttachmentDescriptor depthStencilAttachment; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUCommandBuffer { -}; - -dictionary WebGPUCommandEncoderDescriptor { -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUCommandEncoder { - WebGPUCommandBuffer finishEncoding(); - - // Commands allowed outside of "passes" - void copyBufferToBuffer(WebGPUBuffer src, - u32 srcOffset, - WebGPUBuffer dst, - u32 dstOffset, - u32 size); - // TODO figure out all the arguments required for these - void copyBufferToTexture(); - void copyTextureToBuffer(); - void copyTextureToTexture(); - void blit(); - - void transitionBuffer(WebGPUBuffer b, WebGPUBufferUsageFlags f); - - // Allowed in both compute and render passes - void setPushConstants(WebGPUShaderStageFlags stage, - u32 offset, - u32 count, - ArrayBuffer data); - void setBindGroup(u32 index, WebGPUBindGroup bindGroup); - void setPipeline((WebGPUComputePipeline or WebGPURenderPipeline) pipeline); - - // Compute pass commands - void beginComputePass(); - void endComputePass(); - - void dispatch(u32 x, u32 y, u32 z); - - // Render pass commands - void beginRenderPass(optional WebGPURenderPassDescriptor descriptor); - void endRenderPass(); - - void setBlendColor(float r, float g, float b, float a); - void setIndexBuffer(WebGPUBuffer buffer, u32 offset); - void setVertexBuffers(u32 startSlot, sequence buffers, sequence offsets); - - void draw(u32 vertexCount, u32 instanceCount, u32 firstVertex, u32 firstInstance); - void drawIndexed(u32 indexCount, u32 instanceCount, u32 firstIndex, u32 firstInstance, u32 firstVertex); - - // TODO add missing commands -}; - -// **************************************************************************** -// OTHER (Fence, Queue SwapChain, Device) -// **************************************************************************** - -// Fence -[Pref="dom.webgpu.enable"] -interface WebGPUFence { - boolean wait(double milliseconds); - readonly attribute Promise promise; -}; - -// Queue -[Pref="dom.webgpu.enable"] -interface WebGPUQueue { - void submit(sequence buffers); - WebGPUFence insertFence(); -}; - -// SwapChain / RenderingContext -dictionary WebGPUSwapChainDescriptor { - WebGPUTextureUsageFlags usage; - WebGPUTextureFormatEnum format; - u32 width; - u32 height; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUSwapChain { - void configure(optional WebGPUSwapChainDescriptor descriptor); - WebGPUTexture getNextTexture(); - void present(); -}; - -//[Pref="dom.webgpu.enable"] -//interface WebGPURenderingContext : WebGPUSwapChain { -//}; - -// WebGPU "namespace" used for device creation -dictionary WebGPUExtensions { - boolean anisotropicFiltering; - boolean logicOp; // Previously a "Feature". -}; - -dictionary WebGPULimits { - u32 maxBindGroups; -}; - -// Device -[Pref="dom.webgpu.enable"] -interface WebGPUDevice { - readonly attribute WebGPUAdapter adapter; - WebGPUExtensions extensions(); - WebGPULimits limits(); - - WebGPUBuffer createBuffer(optional WebGPUBufferDescriptor descriptor); - WebGPUTexture createTexture(optional WebGPUTextureDescriptor descriptor); - WebGPUSampler createSampler(optional WebGPUSamplerDescriptor descriptor); - - WebGPUBindGroupLayout createBindGroupLayout(optional WebGPUBindGroupLayoutDescriptor descriptor); - WebGPUPipelineLayout createPipelineLayout(optional WebGPUPipelineLayoutDescriptor descriptor); - WebGPUBindGroup createBindGroup(optional WebGPUBindGroupDescriptor descriptor); - - WebGPUBlendState createBlendState(optional WebGPUBlendStateDescriptor descriptor); - WebGPUDepthStencilState createDepthStencilState(optional WebGPUDepthStencilStateDescriptor descriptor); - WebGPUInputState createInputState(optional WebGPUInputStateDescriptor descriptor); - WebGPUShaderModule createShaderModule(WebGPUShaderModuleDescriptor descriptor); - WebGPUAttachmentState createAttachmentState(optional WebGPUAttachmentStateDescriptor descriptor); - WebGPUComputePipeline createComputePipeline(WebGPUComputePipelineDescriptor descriptor); - WebGPURenderPipeline createRenderPipeline(WebGPURenderPipelineDescriptor descriptor); - - WebGPUCommandEncoder createCommandEncoder(optional WebGPUCommandEncoderDescriptor descriptor); - - WebGPUQueue getQueue(); - - attribute WebGPULogCallback onLog; - Promise getObjectStatus(WebGPUStatusable obj); -}; - -dictionary WebGPUDeviceDescriptor { - WebGPUExtensions extensions; - //WebGPULimits limits; Don't expose higher limits for now. - - // TODO are other things configurable like queues? -}; - -[Pref="dom.webgpu.enable"] -interface WebGPUAdapter { - readonly attribute DOMString name; - WebGPUExtensions extensions(); - //WebGPULimits limits(); Don't expose higher limits for now. - - WebGPUDevice createDevice(optional WebGPUDeviceDescriptor descriptor); -}; - -enum WebGPUPowerPreference { "default", "low-power", "high-performance" }; - -dictionary WebGPUAdapterDescriptor { - WebGPUPowerPreference powerPreference; -}; - -[Pref="dom.webgpu.enable"] -interface WebGPU { - WebGPUAdapter getAdapter(optional WebGPUAdapterDescriptor desc); -}; - -// Add a "webgpu" member to Window that contains the global instance of a "WebGPU" -interface mixin WebGPUProvider { - [SameObject, Replaceable, Pref="dom.webgpu.enable"] readonly attribute WebGPU webgpu; -}; -//Window includes WebGPUProvider; diff --git a/crates/web-sys/webidls/disabled/WebGPUExtras.webidl b/crates/web-sys/webidls/disabled/WebGPUExtras.webidl deleted file mode 100644 index ad38d509..00000000 --- a/crates/web-sys/webidls/disabled/WebGPUExtras.webidl +++ /dev/null @@ -1,14 +0,0 @@ -/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Some parts of WebGPU.webidl need to be pulled into a different file due to a codegen - * bug/missing support. - */ - -dictionary WebGPUBufferBinding { - WebGPUBuffer buffer; - u32 offset; - u32 size; -}; diff --git a/crates/web-sys/webidls/enabled/Window.webidl b/crates/web-sys/webidls/enabled/Window.webidl index 902abb0d..10a7bd95 100644 --- a/crates/web-sys/webidls/enabled/Window.webidl +++ b/crates/web-sys/webidls/enabled/Window.webidl @@ -272,5 +272,3 @@ dictionary IdleRequestOptions { }; callback IdleRequestCallback = void (IdleDeadline deadline); - -//Window includes WebGPUProvider; diff --git a/crates/web-sys/webidls/unstable/WebGPU.webidl b/crates/web-sys/webidls/unstable/WebGPU.webidl new file mode 100644 index 00000000..b8fdf379 --- /dev/null +++ b/crates/web-sys/webidls/unstable/WebGPU.webidl @@ -0,0 +1,864 @@ +interface mixin GPUObjectBase { + attribute DOMString? label; +}; + +dictionary GPUObjectDescriptorBase { + DOMString label; +}; + +[Exposed=Window] +partial interface Navigator { + [SameObject] readonly attribute GPU gpu; +}; + +[Exposed=DedicatedWorker] +partial interface WorkerNavigator { + [SameObject] readonly attribute GPU gpu; +}; + +[Exposed=(Window, DedicatedWorker)] +interface GPU { + Promise requestAdapter(optional GPURequestAdapterOptions options = {}); +}; + +dictionary GPURequestAdapterOptions { + GPUPowerPreference powerPreference; +}; + +enum GPUPowerPreference { + "low-power", + "high-performance" +}; + +interface GPUAdapter { + readonly attribute DOMString name; + readonly attribute FrozenArray extensions; + //readonly attribute GPULimits limits; Don’t expose higher limits for now. + + Promise requestDevice(optional GPUDeviceDescriptor descriptor = {}); +}; + +dictionary GPUDeviceDescriptor : GPUObjectDescriptorBase { + sequence extensions = []; + GPULimits limits = {}; +}; + +enum GPUExtensionName { + "texture-compression-bc" +}; + +dictionary GPULimits { + GPUSize32 maxBindGroups = 4; + GPUSize32 maxDynamicUniformBuffersPerPipelineLayout = 8; + GPUSize32 maxDynamicStorageBuffersPerPipelineLayout = 4; + GPUSize32 maxSampledTexturesPerShaderStage = 16; + GPUSize32 maxSamplersPerShaderStage = 16; + GPUSize32 maxStorageBuffersPerShaderStage = 4; + GPUSize32 maxStorageTexturesPerShaderStage = 4; + GPUSize32 maxUniformBuffersPerShaderStage = 12; +}; + +[Exposed=(Window, DedicatedWorker), Serializable] +interface GPUDevice : EventTarget { + [SameObject] readonly attribute GPUAdapter adapter; + readonly attribute FrozenArray extensions; + readonly attribute object limits; + + [SameObject] readonly attribute GPUQueue defaultQueue; + + GPUBuffer createBuffer(GPUBufferDescriptor descriptor); + GPUMappedBuffer createBufferMapped(GPUBufferDescriptor descriptor); + GPUTexture createTexture(GPUTextureDescriptor descriptor); + GPUSampler createSampler(optional GPUSamplerDescriptor descriptor = {}); + + GPUBindGroupLayout createBindGroupLayout(GPUBindGroupLayoutDescriptor descriptor); + GPUPipelineLayout createPipelineLayout(GPUPipelineLayoutDescriptor descriptor); + GPUBindGroup createBindGroup(GPUBindGroupDescriptor descriptor); + + GPUShaderModule createShaderModule(GPUShaderModuleDescriptor descriptor); + GPUComputePipeline createComputePipeline(GPUComputePipelineDescriptor descriptor); + GPURenderPipeline createRenderPipeline(GPURenderPipelineDescriptor descriptor); + + GPUCommandEncoder createCommandEncoder(optional GPUCommandEncoderDescriptor descriptor = {}); + GPURenderBundleEncoder createRenderBundleEncoder(GPURenderBundleEncoderDescriptor descriptor); +}; +GPUDevice includes GPUObjectBase; + +[Serializable] +interface GPUBuffer { + Promise mapReadAsync(); + Promise mapWriteAsync(); + void unmap(); + + void destroy(); +}; +GPUBuffer includes GPUObjectBase; + +dictionary GPUBufferDescriptor : GPUObjectDescriptorBase { + required GPUSize64 size; + required GPUBufferUsageFlags usage; +}; + +typedef [EnforceRange] unsigned long GPUBufferUsageFlags; +interface GPUBufferUsage { + const GPUBufferUsageFlags MAP_READ = 0x0001; + const GPUBufferUsageFlags MAP_WRITE = 0x0002; + const GPUBufferUsageFlags COPY_SRC = 0x0004; + const GPUBufferUsageFlags COPY_DST = 0x0008; + const GPUBufferUsageFlags INDEX = 0x0010; + const GPUBufferUsageFlags VERTEX = 0x0020; + const GPUBufferUsageFlags UNIFORM = 0x0040; + const GPUBufferUsageFlags STORAGE = 0x0080; + const GPUBufferUsageFlags INDIRECT = 0x0100; +}; + +[Serializable] +interface GPUTexture { + GPUTextureView createView(optional GPUTextureViewDescriptor descriptor = {}); + + void destroy(); +}; +GPUTexture includes GPUObjectBase; + +dictionary GPUTextureDescriptor : GPUObjectDescriptorBase { + required GPUExtent3D size; + GPUIntegerCoordinate arrayLayerCount = 1; + GPUIntegerCoordinate mipLevelCount = 1; + GPUSize32 sampleCount = 1; + GPUTextureDimension dimension = "2d"; + required GPUTextureFormat format; + required GPUTextureUsageFlags usage; +}; + +enum GPUTextureDimension { + "1d", + "2d", + "3d" +}; + +typedef [EnforceRange] unsigned long GPUTextureUsageFlags; +interface GPUTextureUsage { + const GPUTextureUsageFlags COPY_SRC = 0x01; + const GPUTextureUsageFlags COPY_DST = 0x02; + const GPUTextureUsageFlags SAMPLED = 0x04; + const GPUTextureUsageFlags STORAGE = 0x08; + const GPUTextureUsageFlags OUTPUT_ATTACHMENT = 0x10; +}; + +interface GPUTextureView { +}; +GPUTextureView includes GPUObjectBase; + +dictionary GPUTextureViewDescriptor : GPUObjectDescriptorBase { + GPUTextureFormat format; + GPUTextureViewDimension dimension; + GPUTextureAspect aspect = "all"; + GPUIntegerCoordinate baseMipLevel = 0; + GPUIntegerCoordinate mipLevelCount = 0; + GPUIntegerCoordinate baseArrayLayer = 0; + GPUIntegerCoordinate arrayLayerCount = 0; +}; + +enum GPUTextureViewDimension { + "1d", + "2d", + "2d-array", + "cube", + "cube-array", + "3d" +}; + +enum GPUTextureAspect { + "all", + "stencil-only", + "depth-only" +}; + +enum GPUTextureFormat { + // 8-bit formats + "r8unorm", + "r8snorm", + "r8uint", + "r8sint", + + // 16-bit formats + "r16uint", + "r16sint", + "r16float", + "rg8unorm", + "rg8snorm", + "rg8uint", + "rg8sint", + + // 32-bit formats + "r32uint", + "r32sint", + "r32float", + "rg16uint", + "rg16sint", + "rg16float", + "rgba8unorm", + "rgba8unorm-srgb", + "rgba8snorm", + "rgba8uint", + "rgba8sint", + "bgra8unorm", + "bgra8unorm-srgb", + // Packed 32-bit formats + "rgb10a2unorm", + "rg11b10float", + + // 64-bit formats + "rg32uint", + "rg32sint", + "rg32float", + "rgba16uint", + "rgba16sint", + "rgba16float", + + // 128-bit formats + "rgba32uint", + "rgba32sint", + "rgba32float", + + // Depth and stencil formats + "depth32float", + "depth24plus", + "depth24plus-stencil8" +}; + +enum GPUTextureComponentType { + "float", + "sint", + "uint" +}; + +interface GPUSampler { +}; +GPUSampler includes GPUObjectBase; + +dictionary GPUSamplerDescriptor : GPUObjectDescriptorBase { + GPUAddressMode addressModeU = "clamp-to-edge"; + GPUAddressMode addressModeV = "clamp-to-edge"; + GPUAddressMode addressModeW = "clamp-to-edge"; + GPUFilterMode magFilter = "nearest"; + GPUFilterMode minFilter = "nearest"; + GPUFilterMode mipmapFilter = "nearest"; + float lodMinClamp = 0; + float lodMaxClamp = 0xffffffff; // TODO: What should this be? Was Number.MAX_VALUE. + GPUCompareFunction compare = "never"; +}; + +enum GPUAddressMode { + "clamp-to-edge", + "repeat", + "mirror-repeat" +}; + +enum GPUFilterMode { + "nearest", + "linear" +}; + +enum GPUCompareFunction { + "never", + "less", + "equal", + "less-equal", + "greater", + "not-equal", + "greater-equal", + "always" +}; + +[Serializable] +interface GPUBindGroupLayout { +}; +GPUBindGroupLayout includes GPUObjectBase; + +dictionary GPUBindGroupLayoutDescriptor : GPUObjectDescriptorBase { + required sequence bindings; +}; + +dictionary GPUBindGroupLayoutBinding { + required GPUIndex32 binding; + required GPUShaderStageFlags visibility; + required GPUBindingType type; + GPUTextureViewDimension textureDimension = "2d"; + GPUTextureComponentType textureComponentType = "float"; + boolean multisampled = false; + boolean hasDynamicOffset = false; +}; + +typedef [EnforceRange] unsigned long GPUShaderStageFlags; +interface GPUShaderStage { + const GPUShaderStageFlags VERTEX = 0x1; + const GPUShaderStageFlags FRAGMENT = 0x2; + const GPUShaderStageFlags COMPUTE = 0x4; +}; + +enum GPUBindingType { + "uniform-buffer", + "storage-buffer", + "readonly-storage-buffer", + "sampler", + "sampled-texture", + "storage-texture" + // TODO: other binding types +}; + +interface GPUBindGroup { +}; +GPUBindGroup includes GPUObjectBase; + +dictionary GPUBindGroupDescriptor : GPUObjectDescriptorBase { + required GPUBindGroupLayout layout; + required sequence bindings; +}; + +typedef (GPUSampler or GPUTextureView or GPUBufferBinding) GPUBindingResource; + +dictionary GPUBindGroupBinding { + required GPUIndex32 binding; + required GPUBindingResource resource; +}; + +dictionary GPUBufferBinding { + required GPUBuffer buffer; + GPUSize64 offset = 0; + GPUSize64 size; +}; + +interface GPUPipelineLayout { +}; +GPUPipelineLayout includes GPUObjectBase; + +dictionary GPUPipelineLayoutDescriptor : GPUObjectDescriptorBase { + required sequence bindGroupLayouts; +}; + +[Serializable] +interface GPUShaderModule { +}; +GPUShaderModule includes GPUObjectBase; + +typedef (Uint32Array or DOMString) GPUShaderCode; + +dictionary GPUShaderModuleDescriptor : GPUObjectDescriptorBase { + required GPUShaderCode code; +}; + +dictionary GPUPipelineDescriptorBase : GPUObjectDescriptorBase { + required GPUPipelineLayout layout; +}; + +dictionary GPUProgrammableStageDescriptor { + required GPUShaderModule module; + required DOMString entryPoint; + // TODO: other stuff like specialization constants? +}; + +[Serializable] +interface GPUComputePipeline { +}; +GPUComputePipeline includes GPUObjectBase; + +dictionary GPUComputePipelineDescriptor : GPUPipelineDescriptorBase { + required GPUProgrammableStageDescriptor computeStage; +}; + +[Serializable] +interface GPURenderPipeline { +}; +GPURenderPipeline includes GPUObjectBase; + +dictionary GPURenderPipelineDescriptor : GPUPipelineDescriptorBase { + required GPUProgrammableStageDescriptor vertexStage; + GPUProgrammableStageDescriptor fragmentStage; + + required GPUPrimitiveTopology primitiveTopology; + GPURasterizationStateDescriptor rasterizationState = {}; + required sequence colorStates; + GPUDepthStencilStateDescriptor depthStencilState; + GPUVertexStateDescriptor vertexState = {}; + + GPUSize32 sampleCount = 1; + GPUSampleMask sampleMask = 0xFFFFFFFF; + boolean alphaToCoverageEnabled = false; + // TODO: other properties +}; + +enum GPUPrimitiveTopology { + "point-list", + "line-list", + "line-strip", + "triangle-list", + "triangle-strip" +}; + +dictionary GPURasterizationStateDescriptor { + GPUFrontFace frontFace = "ccw"; + GPUCullMode cullMode = "none"; + + GPUDepthBias depthBias = 0; + float depthBiasSlopeScale = 0; + float depthBiasClamp = 0; +}; + +enum GPUFrontFace { + "ccw", + "cw" +}; + +enum GPUCullMode { + "none", + "front", + "back" +}; + +dictionary GPUColorStateDescriptor { + required GPUTextureFormat format; + + GPUBlendDescriptor alphaBlend = {}; + GPUBlendDescriptor colorBlend = {}; + GPUColorWriteFlags writeMask = 0xF; // GPUColorWrite.ALL +}; + +typedef [EnforceRange] unsigned long GPUColorWriteFlags; +interface GPUColorWrite { + const GPUColorWriteFlags RED = 0x1; + const GPUColorWriteFlags GREEN = 0x2; + const GPUColorWriteFlags BLUE = 0x4; + const GPUColorWriteFlags ALPHA = 0x8; + const GPUColorWriteFlags ALL = 0xF; +}; + +dictionary GPUBlendDescriptor { + GPUBlendFactor srcFactor = "one"; + GPUBlendFactor dstFactor = "zero"; + GPUBlendOperation operation = "add"; +}; + +enum GPUBlendFactor { + "zero", + "one", + "src-color", + "one-minus-src-color", + "src-alpha", + "one-minus-src-alpha", + "dst-color", + "one-minus-dst-color", + "dst-alpha", + "one-minus-dst-alpha", + "src-alpha-saturated", + "blend-color", + "one-minus-blend-color" +}; + +enum GPUBlendOperation { + "add", + "subtract", + "reverse-subtract", + "min", + "max" +}; + +enum GPUStencilOperation { + "keep", + "zero", + "replace", + "invert", + "increment-clamp", + "decrement-clamp", + "increment-wrap", + "decrement-wrap" +}; + +dictionary GPUDepthStencilStateDescriptor { + required GPUTextureFormat format; + + boolean depthWriteEnabled = false; + GPUCompareFunction depthCompare = "always"; + + GPUStencilStateFaceDescriptor stencilFront = {}; + GPUStencilStateFaceDescriptor stencilBack = {}; + + GPUStencilValue stencilReadMask = 0xFFFFFFFF; + GPUStencilValue stencilWriteMask = 0xFFFFFFFF; +}; + +dictionary GPUStencilStateFaceDescriptor { + GPUCompareFunction compare = "always"; + GPUStencilOperation failOp = "keep"; + GPUStencilOperation depthFailOp = "keep"; + GPUStencilOperation passOp = "keep"; +}; + +enum GPUIndexFormat { + "uint16", + "uint32" +}; + +enum GPUVertexFormat { + "uchar2", + "uchar4", + "char2", + "char4", + "uchar2norm", + "uchar4norm", + "char2norm", + "char4norm", + "ushort2", + "ushort4", + "short2", + "short4", + "ushort2norm", + "ushort4norm", + "short2norm", + "short4norm", + "half2", + "half4", + "float", + "float2", + "float3", + "float4", + "uint", + "uint2", + "uint3", + "uint4", + "int", + "int2", + "int3", + "int4" +}; + +enum GPUInputStepMode { + "vertex", + "instance" +}; + +dictionary GPUVertexStateDescriptor { + GPUIndexFormat indexFormat = "uint32"; + sequence vertexBuffers = []; +}; + +dictionary GPUVertexBufferLayoutDescriptor { + required GPUSize64 arrayStride; + GPUInputStepMode stepMode = "vertex"; + required sequence attributes; +}; + +dictionary GPUVertexAttributeDescriptor { + required GPUVertexFormat format; + required GPUSize64 offset; + + required GPUIndex32 shaderLocation; +}; + +interface GPUCommandBuffer { +}; +GPUCommandBuffer includes GPUObjectBase; + +dictionary GPUCommandBufferDescriptor : GPUObjectDescriptorBase { +}; + +interface GPUCommandEncoder { + GPURenderPassEncoder beginRenderPass(GPURenderPassDescriptor descriptor); + GPUComputePassEncoder beginComputePass(optional GPUComputePassDescriptor descriptor = {}); + + void copyBufferToBuffer( + GPUBuffer source, + GPUSize64 sourceOffset, + GPUBuffer destination, + GPUSize64 destinationOffset, + GPUSize64 size); + + void copyBufferToTexture( + GPUBufferCopyView source, + GPUTextureCopyView destination, + GPUExtent3D copySize); + + void copyTextureToBuffer( + GPUTextureCopyView source, + GPUBufferCopyView destination, + GPUExtent3D copySize); + + void copyTextureToTexture( + GPUTextureCopyView source, + GPUTextureCopyView destination, + GPUExtent3D copySize); + + void pushDebugGroup(DOMString groupLabel); + void popDebugGroup(); + void insertDebugMarker(DOMString markerLabel); + + GPUCommandBuffer finish(optional GPUCommandBufferDescriptor descriptor = {}); +}; +GPUCommandEncoder includes GPUObjectBase; + +dictionary GPUCommandEncoderDescriptor : GPUObjectDescriptorBase { + // TODO: reusability flag? +}; + +dictionary GPUBufferCopyView { + required GPUBuffer buffer; + GPUSize64 offset = 0; + required GPUSize32 rowPitch; + required GPUSize32 imageHeight; +}; + +dictionary GPUTextureCopyView { + required GPUTexture texture; + GPUIntegerCoordinate mipLevel = 0; + GPUIntegerCoordinate arrayLayer = 0; + GPUOrigin3D origin = {}; +}; + +dictionary GPUImageBitmapCopyView { + required ImageBitmap imageBitmap; + GPUOrigin2D origin = {}; +}; + +interface mixin GPUProgrammablePassEncoder { + void setBindGroup(GPUIndex32 index, GPUBindGroup bindGroup, + optional sequence dynamicOffsets = []); + + void setBindGroup(GPUIndex32 index, GPUBindGroup bindGroup, + Uint32Array dynamicOffsetsData, + GPUSize64 dynamicOffsetsDataStart, + GPUSize32 dynamicOffsetsDataLength); + + void pushDebugGroup(DOMString groupLabel); + void popDebugGroup(); + void insertDebugMarker(DOMString markerLabel); +}; + +interface GPUComputePassEncoder { + void setPipeline(GPUComputePipeline pipeline); + void dispatch(GPUSize32 x, optional GPUSize32 y = 1, optional GPUSize32 z = 1); + void dispatchIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); + + void endPass(); +}; +GPUComputePassEncoder includes GPUObjectBase; +GPUComputePassEncoder includes GPUProgrammablePassEncoder; + +dictionary GPUComputePassDescriptor : GPUObjectDescriptorBase { +}; + +interface mixin GPURenderEncoderBase { + void setPipeline(GPURenderPipeline pipeline); + + void setIndexBuffer(GPUBuffer buffer, optional GPUSize64 offset = 0); + void setVertexBuffer(GPUIndex32 slot, GPUBuffer buffer, optional GPUSize64 offset = 0); + + void draw(GPUSize32 vertexCount, GPUSize32 instanceCount, + GPUSize32 firstVertex, GPUSize32 firstInstance); + void drawIndexed(GPUSize32 indexCount, GPUSize32 instanceCount, + GPUSize32 firstIndex, GPUSignedOffset32 baseVertex, GPUSize32 firstInstance); + + void drawIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); + void drawIndexedIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); +}; + +interface GPURenderPassEncoder { + void setViewport(float x, float y, + float width, float height, + float minDepth, float maxDepth); + + void setScissorRect(GPUIntegerCoordinate x, GPUIntegerCoordinate y, + GPUIntegerCoordinate width, GPUIntegerCoordinate height); + + void setBlendColor(GPUColor color); + void setStencilReference(GPUStencilValue reference); + + void executeBundles(sequence bundles); + void endPass(); +}; +GPURenderPassEncoder includes GPUObjectBase; +GPURenderPassEncoder includes GPUProgrammablePassEncoder; +GPURenderPassEncoder includes GPURenderEncoderBase; + +dictionary GPURenderPassDescriptor : GPUObjectDescriptorBase { + required sequence colorAttachments; + GPURenderPassDepthStencilAttachmentDescriptor depthStencilAttachment; +}; + +dictionary GPURenderPassColorAttachmentDescriptor { + required GPUTextureView attachment; + GPUTextureView resolveTarget; + + required (GPULoadOp or GPUColor) loadValue; + GPUStoreOp storeOp = "store"; +}; + +dictionary GPURenderPassDepthStencilAttachmentDescriptor { + required GPUTextureView attachment; + + required (GPULoadOp or float) depthLoadValue; + required GPUStoreOp depthStoreOp; + + required (GPULoadOp or GPUStencilValue) stencilLoadValue; + required GPUStoreOp stencilStoreOp; +}; + +enum GPULoadOp { + "load" +}; + +enum GPUStoreOp { + "store", + "clear" +}; + +interface GPURenderBundle { +}; +GPURenderBundle includes GPUObjectBase; + +dictionary GPURenderBundleDescriptor : GPUObjectDescriptorBase { +}; + +interface GPURenderBundleEncoder { + GPURenderBundle finish(optional GPURenderBundleDescriptor descriptor = {}); +}; +GPURenderBundleEncoder includes GPUObjectBase; +GPURenderBundleEncoder includes GPUProgrammablePassEncoder; +GPURenderBundleEncoder includes GPURenderEncoderBase; + +dictionary GPURenderBundleEncoderDescriptor : GPUObjectDescriptorBase { + required sequence colorFormats; + GPUTextureFormat depthStencilFormat; + GPUSize32 sampleCount = 1; +}; + +interface GPUQueue { + void submit(sequence commandBuffers); + + GPUFence createFence(optional GPUFenceDescriptor descriptor = {}); + void signal(GPUFence fence, GPUFenceValue signalValue); + + void copyImageBitmapToTexture( + GPUImageBitmapCopyView source, + GPUTextureCopyView destination, + GPUExtent3D copySize); +}; +GPUQueue includes GPUObjectBase; + +interface GPUFence { + GPUFenceValue getCompletedValue(); + Promise onCompletion(GPUFenceValue completionValue); +}; +GPUFence includes GPUObjectBase; + +dictionary GPUFenceDescriptor : GPUObjectDescriptorBase { + GPUFenceValue initialValue = 0; +}; + +interface GPUCanvasContext { + GPUSwapChain configureSwapChain(GPUSwapChainDescriptor descriptor); + + Promise getSwapChainPreferredFormat(GPUDevice device); +}; + +dictionary GPUSwapChainDescriptor : GPUObjectDescriptorBase { + required GPUDevice device; + required GPUTextureFormat format; + GPUTextureUsageFlags usage = 0x10; // GPUTextureUsage.OUTPUT_ATTACHMENT +}; + +interface GPUSwapChain { + GPUTexture getCurrentTexture(); +}; +GPUSwapChain includes GPUObjectBase; + +interface GPUDeviceLostInfo { + readonly attribute DOMString message; +}; + +partial interface GPUDevice { + readonly attribute Promise lost; +}; + +enum GPUErrorFilter { + "none", + "out-of-memory", + "validation" +}; + +interface GPUOutOfMemoryError { + constructor(); +}; + +interface GPUValidationError { + constructor(DOMString message); + readonly attribute DOMString message; +}; + +typedef (GPUOutOfMemoryError or GPUValidationError) GPUError; + +partial interface GPUDevice { + void pushErrorScope(GPUErrorFilter filter); + Promise popErrorScope(); +}; + +[ + Exposed=(Window, DedicatedWorker) +] +interface GPUUncapturedErrorEvent : Event { + constructor( + DOMString type, + GPUUncapturedErrorEventInit gpuUncapturedErrorEventInitDict + ); + [SameObject] readonly attribute GPUError error; +}; + +dictionary GPUUncapturedErrorEventInit : EventInit { + required GPUError error; +}; + +partial interface GPUDevice { + [Exposed=(Window, DedicatedWorker)] + attribute EventHandler onuncapturederror; +}; + +typedef [EnforceRange] unsigned long GPUBufferDynamicOffset; +typedef [EnforceRange] unsigned long long GPUFenceValue; +typedef [EnforceRange] unsigned long GPUStencilValue; +typedef [EnforceRange] unsigned long GPUSampleMask; +typedef [EnforceRange] long GPUDepthBias; + +typedef [EnforceRange] unsigned long long GPUSize64; +typedef [EnforceRange] unsigned long GPUIntegerCoordinate; +typedef [EnforceRange] unsigned long GPUIndex32; +typedef [EnforceRange] unsigned long GPUSize32; +typedef [EnforceRange] long GPUSignedOffset32; + +dictionary GPUColorDict { + required double r; + required double g; + required double b; + required double a; +}; +typedef (sequence or GPUColorDict) GPUColor; + +dictionary GPUOrigin2DDict { + GPUIntegerCoordinate x = 0; + GPUIntegerCoordinate y = 0; +}; +typedef (sequence or GPUOrigin2DDict) GPUOrigin2D; + +dictionary GPUOrigin3DDict { + GPUIntegerCoordinate x = 0; + GPUIntegerCoordinate y = 0; + GPUIntegerCoordinate z = 0; +}; +typedef (sequence or GPUOrigin3DDict) GPUOrigin3D; + +dictionary GPUExtent3DDict { + required GPUIntegerCoordinate width; + required GPUIntegerCoordinate height; + required GPUIntegerCoordinate depth; +}; +typedef (sequence or GPUExtent3DDict) GPUExtent3D; + +typedef sequence<(GPUBuffer or ArrayBuffer)> GPUMappedBuffer; diff --git a/crates/webidl-tests/build.rs b/crates/webidl-tests/build.rs index 273e3920..8804e803 100644 --- a/crates/webidl-tests/build.rs +++ b/crates/webidl-tests/build.rs @@ -11,13 +11,22 @@ fn main() { let idls = fs::read_dir(".") .unwrap() .map(|f| f.unwrap().path()) - .filter(|f| f.extension().and_then(|s| s.to_str()) == Some("webidl")) - .map(|f| (fs::read_to_string(&f).unwrap(), f)); + .filter(|path| path.extension().and_then(|s| s.to_str()) == Some("webidl")) + .map(|path| { + let unstable = path.file_name().and_then(|s| s.to_str()) == Some("unstable.webidl"); + let file = fs::read_to_string(&path).unwrap(); + (file, path, unstable) + }); let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); - for (i, (idl, path)) in idls.enumerate() { + for (i, (idl, path, unstable)) in idls.enumerate() { println!("processing {:?}", path); - let mut generated_rust = wasm_bindgen_webidl::compile(&idl, None).unwrap(); + let (stable_source, experimental_source) = if unstable { + (String::new(), idl) + } else { + (idl, String::new()) + }; + let mut generated_rust = wasm_bindgen_webidl::compile(&stable_source, &experimental_source, None).unwrap(); generated_rust.insert_str( 0, diff --git a/crates/webidl-tests/main.rs b/crates/webidl-tests/main.rs index c7a081bf..e3c56929 100644 --- a/crates/webidl-tests/main.rs +++ b/crates/webidl-tests/main.rs @@ -13,3 +13,4 @@ pub mod namespace; pub mod no_interface; pub mod simple; pub mod throws; +pub mod unstable; diff --git a/crates/webidl-tests/unstable.js b/crates/webidl-tests/unstable.js new file mode 100644 index 00000000..377b2cf3 --- /dev/null +++ b/crates/webidl-tests/unstable.js @@ -0,0 +1,13 @@ +global.GetUnstableInterface = class { + static get() { + return { + enum_value(dict) { + if (!dict) { + return 0; + } + + return dict.unstableEnum === "a" ? 1 : 2; + } + } + } +} diff --git a/crates/webidl-tests/unstable.rs b/crates/webidl-tests/unstable.rs new file mode 100644 index 00000000..1c419835 --- /dev/null +++ b/crates/webidl-tests/unstable.rs @@ -0,0 +1,17 @@ +use wasm_bindgen_test::*; + +include!(concat!(env!("OUT_DIR"), "/unstable.rs")); + +#[cfg(web_sys_unstable_apis)] +#[wasm_bindgen_test] +fn can_use_unstable_apis() { + let unstable_interface = GetUnstableInterface::get(); + assert_eq!(0u32, unstable_interface.enum_value()); + + let mut dict = UnstableDictionary::new(); + dict.unstable_enum(UnstableEnum::B); + assert_eq!( + 2u32, + unstable_interface.enum_value_with_unstable_dictionary(&dict) + ); +} diff --git a/crates/webidl-tests/unstable.webidl b/crates/webidl-tests/unstable.webidl new file mode 100644 index 00000000..01b02be1 --- /dev/null +++ b/crates/webidl-tests/unstable.webidl @@ -0,0 +1,19 @@ +enum UnstableEnum { + "a", + "b" +}; + +dictionary UnstableDictionary { + UnstableEnum unstableEnum; +}; + +typedef unsigned long UnstableTypedef; + +[NoInterfaceObject] +partial interface UnstableInterface { + UnstableTypedef enum_value(optional UnstableDictionary unstableDictionary = {}); +}; + +interface GetUnstableInterface { + static UnstableInterface get(); +}; diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 8e125ad5..05129298 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -20,15 +20,14 @@ use weedle::CallbackInterfaceDefinition; use weedle::{DictionaryDefinition, PartialDictionaryDefinition}; use super::Result; -use crate::util; -use crate::util::camel_case_ident; +use crate::{util::{self, camel_case_ident}, ApiStability}; /// Collection of constructs that may use partial. #[derive(Default)] pub(crate) struct FirstPassRecord<'src> { pub(crate) builtin_idents: BTreeSet, pub(crate) interfaces: BTreeMap<&'src str, InterfaceData<'src>>, - pub(crate) enums: BTreeMap<&'src str, &'src weedle::EnumDefinition<'src>>, + pub(crate) enums: BTreeMap<&'src str, EnumData<'src>>, /// The mixins, mapping their name to the webidl ast node for the mixin. pub(crate) mixins: BTreeMap<&'src str, MixinData<'src>>, pub(crate) typedefs: BTreeMap<&'src str, &'src weedle::types::Type<'src>>, @@ -40,6 +39,11 @@ pub(crate) struct FirstPassRecord<'src> { pub(crate) immutable_slice_whitelist: BTreeSet<&'static str>, } +pub(crate) struct AttributeInterfaceData<'src> { + pub(crate) definition: &'src AttributeInterfaceMember<'src>, + pub(crate) stability: ApiStability, +} + /// We need to collect interface data during the first pass, to be used later. #[derive(Default)] pub(crate) struct InterfaceData<'src> { @@ -47,11 +51,17 @@ pub(crate) struct InterfaceData<'src> { pub(crate) partial: bool, pub(crate) has_interface: bool, pub(crate) deprecated: Option, - pub(crate) attributes: Vec<&'src AttributeInterfaceMember<'src>>, + pub(crate) attributes: Vec>, pub(crate) consts: Vec<&'src ConstMember<'src>>, pub(crate) operations: BTreeMap, OperationData<'src>>, pub(crate) superclass: Option<&'src str>, pub(crate) definition_attributes: Option<&'src ExtendedAttributeList<'src>>, + pub(crate) stability: ApiStability, +} + +pub(crate) struct AttributeMixinData<'src> { + pub(crate) definition: &'src AttributeMixinMember<'src>, + pub(crate) stability: ApiStability, } /// We need to collect mixin data during the first pass, to be used later. @@ -59,10 +69,11 @@ pub(crate) struct InterfaceData<'src> { pub(crate) struct MixinData<'src> { /// Whether only partial mixins were encountered pub(crate) partial: bool, - pub(crate) attributes: Vec<&'src AttributeMixinMember<'src>>, + pub(crate) attributes: Vec>, pub(crate) consts: Vec<&'src ConstMember<'src>>, pub(crate) operations: BTreeMap, OperationData<'src>>, pub(crate) definition_attributes: Option<&'src ExtendedAttributeList<'src>>, + pub(crate) stability: ApiStability, } /// We need to collect namespace data during the first pass, to be used later. @@ -75,6 +86,12 @@ pub(crate) struct NamespaceData<'src> { pub(crate) struct DictionaryData<'src> { pub(crate) partials: Vec<&'src PartialDictionaryDefinition<'src>>, pub(crate) definition: Option<&'src DictionaryDefinition<'src>>, + pub(crate) stability: ApiStability, +} + +pub(crate) struct EnumData<'src> { + pub(crate) definition: &'src weedle::EnumDefinition<'src>, + pub(crate) stability: ApiStability, } pub(crate) struct CallbackInterfaceData<'src> { @@ -121,29 +138,29 @@ pub(crate) trait FirstPass<'src, Ctx> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, ctx: Ctx) -> Result<()>; } -impl<'src> FirstPass<'src, ()> for [weedle::Definition<'src>] { - fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { +impl<'src> FirstPass<'src, ApiStability> for [weedle::Definition<'src>] { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, stability: ApiStability) -> Result<()> { for def in self { - def.first_pass(record, ())?; + def.first_pass(record, stability)?; } Ok(()) } } -impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> { - fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { +impl<'src> FirstPass<'src, ApiStability> for weedle::Definition<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, stability: ApiStability) -> Result<()> { use weedle::Definition::*; match self { - Dictionary(dictionary) => dictionary.first_pass(record, ()), + Dictionary(dictionary) => dictionary.first_pass(record, stability), PartialDictionary(dictionary) => dictionary.first_pass(record, ()), - Enum(enum_) => enum_.first_pass(record, ()), + Enum(enum_) => enum_.first_pass(record, stability), IncludesStatement(includes) => includes.first_pass(record, ()), - Interface(interface) => interface.first_pass(record, ()), - PartialInterface(interface) => interface.first_pass(record, ()), - InterfaceMixin(mixin) => mixin.first_pass(record, ()), - PartialInterfaceMixin(mixin) => mixin.first_pass(record, ()), + Interface(interface) => interface.first_pass(record, stability), + PartialInterface(interface) => interface.first_pass(record, stability), + InterfaceMixin(mixin) => mixin.first_pass(record, stability), + PartialInterfaceMixin(mixin) => mixin.first_pass(record, stability), Namespace(namespace) => namespace.first_pass(record, ()), PartialNamespace(namespace) => namespace.first_pass(record, ()), Typedef(typedef) => typedef.first_pass(record, ()), @@ -154,17 +171,20 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> { } } -impl<'src> FirstPass<'src, ()> for weedle::DictionaryDefinition<'src> { - fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { +impl<'src> FirstPass<'src, ApiStability> for weedle::DictionaryDefinition<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, stability: ApiStability) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } - record + let dictionary_data = record .dictionaries .entry(self.identifier.0) - .or_default() - .definition = Some(self); + .or_default(); + + dictionary_data.definition = Some(self); + dictionary_data.stability = stability; + Ok(()) } } @@ -181,17 +201,23 @@ impl<'src> FirstPass<'src, ()> for weedle::PartialDictionaryDefinition<'src> { .or_default() .partials .push(self); + Ok(()) } } -impl<'src> FirstPass<'src, ()> for weedle::EnumDefinition<'src> { - fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { +impl<'src> FirstPass<'src, ApiStability> for weedle::EnumDefinition<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, stability: ApiStability) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } - if record.enums.insert(self.identifier.0, self).is_some() { + let enum_data = EnumData { + definition: self, + stability, + }; + + if record.enums.insert(self.identifier.0, enum_data).is_some() { log::info!( "Encountered multiple enum declarations: {}", self.identifier.0 @@ -298,8 +324,8 @@ fn first_pass_operation<'src>( } } -impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> { - fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { +impl<'src> FirstPass<'src, ApiStability> for weedle::InterfaceDefinition<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, stability: ApiStability) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } @@ -311,6 +337,7 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> { interface_data.deprecated = util::get_rust_deprecated(&self.attributes).map(|s| s.to_string()); interface_data.has_interface = !util::is_no_interface_object(&self.attributes); + interface_data.stability = stability; if let Some(attrs) = &self.attributes { for attr in attrs.body.list.iter() { process_interface_attribute(record, self.identifier.0, attr); @@ -318,7 +345,7 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> { } for member in &self.members.body { - member.first_pass(record, self.identifier.0)?; + member.first_pass(record, (self.identifier.0, stability))?; } Ok(()) @@ -382,8 +409,8 @@ fn process_interface_attribute<'src>( } } -impl<'src> FirstPass<'src, ()> for weedle::PartialInterfaceDefinition<'src> { - fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { +impl<'src> FirstPass<'src, ApiStability> for weedle::PartialInterfaceDefinition<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, stability: ApiStability) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } @@ -392,31 +419,33 @@ impl<'src> FirstPass<'src, ()> for weedle::PartialInterfaceDefinition<'src> { .entry(self.identifier.0) .or_insert_with(|| InterfaceData { partial: true, + stability, ..Default::default() }); for member in &self.members.body { - member.first_pass(record, self.identifier.0)?; + member.first_pass(record, (self.identifier.0, stability))?; } + Ok(()) } } -impl<'src> FirstPass<'src, &'src str> for weedle::interface::InterfaceMember<'src> { +impl<'src> FirstPass<'src, (&'src str, ApiStability)> for weedle::interface::InterfaceMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, - self_name: &'src str, + ctx: (&'src str, ApiStability), ) -> Result<()> { match self { - InterfaceMember::Attribute(attr) => attr.first_pass(record, self_name), - InterfaceMember::Operation(op) => op.first_pass(record, self_name), + InterfaceMember::Attribute(attr) => attr.first_pass(record, ctx), + InterfaceMember::Operation(op) => op.first_pass(record, ctx.0), InterfaceMember::Const(const_) => { if util::is_chrome_only(&const_.attributes) { return Ok(()); } record .interfaces - .get_mut(self_name) + .get_mut(ctx.0) .unwrap() .consts .push(const_); @@ -484,11 +513,11 @@ impl<'src> FirstPass<'src, &'src str> for weedle::interface::OperationInterfaceM } } -impl<'src> FirstPass<'src, &'src str> for weedle::interface::AttributeInterfaceMember<'src> { +impl<'src> FirstPass<'src, (&'src str, ApiStability)> for weedle::interface::AttributeInterfaceMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, - self_name: &'src str, + ctx: (&'src str, ApiStability), ) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); @@ -496,16 +525,19 @@ impl<'src> FirstPass<'src, &'src str> for weedle::interface::AttributeInterfaceM record .interfaces - .get_mut(self_name) + .get_mut(ctx.0) .unwrap() .attributes - .push(self); + .push(AttributeInterfaceData { + definition: self, + stability: ctx.1 + }); Ok(()) } } -impl<'src> FirstPass<'src, ()> for weedle::InterfaceMixinDefinition<'src> { - fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { +impl<'src> FirstPass<'src, ApiStability> for weedle::InterfaceMixinDefinition<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, stability: ApiStability) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } @@ -514,18 +546,19 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceMixinDefinition<'src> { let mixin_data = record.mixins.entry(self.identifier.0).or_default(); mixin_data.partial = false; mixin_data.definition_attributes = self.attributes.as_ref(); + mixin_data.stability = stability; } for member in &self.members.body { - member.first_pass(record, self.identifier.0)?; + member.first_pass(record, (self.identifier.0, stability))?; } Ok(()) } } -impl<'src> FirstPass<'src, ()> for weedle::PartialInterfaceMixinDefinition<'src> { - fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { +impl<'src> FirstPass<'src, ApiStability> for weedle::PartialInterfaceMixinDefinition<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, stability: ApiStability) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } @@ -535,31 +568,32 @@ impl<'src> FirstPass<'src, ()> for weedle::PartialInterfaceMixinDefinition<'src> .entry(self.identifier.0) .or_insert_with(|| MixinData { partial: true, + stability, ..Default::default() }); for member in &self.members.body { - member.first_pass(record, self.identifier.0)?; + member.first_pass(record, (self.identifier.0, stability))?; } Ok(()) } } -impl<'src> FirstPass<'src, &'src str> for weedle::mixin::MixinMember<'src> { +impl<'src> FirstPass<'src, (&'src str, ApiStability)> for weedle::mixin::MixinMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, - self_name: &'src str, + ctx: (&'src str, ApiStability), ) -> Result<()> { match self { - MixinMember::Operation(op) => op.first_pass(record, self_name), - MixinMember::Attribute(a) => a.first_pass(record, self_name), + MixinMember::Operation(op) => op.first_pass(record, ctx), + MixinMember::Attribute(a) => a.first_pass(record, ctx), MixinMember::Const(a) => { if util::is_chrome_only(&a.attributes) { return Ok(()); } - record.mixins.get_mut(self_name).unwrap().consts.push(a); + record.mixins.get_mut(ctx.0).unwrap().consts.push(a); Ok(()) } MixinMember::Stringifier(_) => { @@ -570,11 +604,11 @@ impl<'src> FirstPass<'src, &'src str> for weedle::mixin::MixinMember<'src> { } } -impl<'src> FirstPass<'src, &'src str> for weedle::mixin::OperationMixinMember<'src> { +impl<'src> FirstPass<'src, (&'src str, ApiStability)> for weedle::mixin::OperationMixinMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, - self_name: &'src str, + ctx: (&'src str, ApiStability), ) -> Result<()> { if self.stringifier.is_some() { log::warn!("Unsupported webidl stringifier: {:?}", self); @@ -584,7 +618,7 @@ impl<'src> FirstPass<'src, &'src str> for weedle::mixin::OperationMixinMember<'s first_pass_operation( record, FirstPassOperationType::Mixin, - self_name, + ctx.0, &[OperationId::Operation(self.identifier.map(|s| s.0.clone()))], &self.args.body.list, &self.return_type, @@ -595,21 +629,24 @@ impl<'src> FirstPass<'src, &'src str> for weedle::mixin::OperationMixinMember<'s } } -impl<'src> FirstPass<'src, &'src str> for weedle::mixin::AttributeMixinMember<'src> { +impl<'src> FirstPass<'src, (&'src str, ApiStability)> for weedle::mixin::AttributeMixinMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, - self_name: &'src str, + ctx: (&'src str, ApiStability), ) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } record .mixins - .get_mut(self_name) + .get_mut(ctx.0) .unwrap() .attributes - .push(self); + .push(AttributeMixinData { + definition: self, + stability: ctx.1 + }); Ok(()) } } diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 15dc3a9b..8fc374f6 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -20,7 +20,7 @@ use crate::util::{ camel_case_ident, mdn_doc, public, shouty_snake_case_ident, snake_case_ident, webidl_const_v_to_backend_const_v, TypePosition, }; -use anyhow::{bail, Result}; +use anyhow::Result; use proc_macro2::{Ident, Span}; use quote::{quote, ToTokens}; use std::collections::{BTreeSet, HashSet}; @@ -55,37 +55,59 @@ impl fmt::Display for WebIDLParseError { impl std::error::Error for WebIDLParseError {} -/// Parse a string of WebIDL source text into a wasm-bindgen AST. -fn parse(webidl_source: &str, allowed_types: Option<&[&str]>) -> Result { - let definitions = match weedle::parse(webidl_source) { - Ok(def) => def, - Err(e) => { - match &e { - weedle::Err::Incomplete(needed) => bail!("needed {:?} more bytes", needed), - weedle::Err::Error(cx) | weedle::Err::Failure(cx) => { - // Note that #[allow] here is a workaround for Geal/nom#843 - // because the `Context` type here comes from `nom` and if - // something else in our crate graph enables the - // `verbose-errors` feature then we need to still compiled - // against the changed enum definition. - #[allow(unreachable_patterns)] - let remaining = match cx { - weedle::Context::Code(remaining, _) => remaining.len(), - _ => 0, - }; - let pos = webidl_source.len() - remaining; +#[derive(Clone, Copy, PartialEq)] +pub(crate) enum ApiStability { + Stable, + Unstable, +} - bail!(WebIDLParseError(pos)) - } +impl ApiStability { + pub(crate) fn is_unstable(self) -> bool { + self == Self::Unstable + } +} + +impl Default for ApiStability { + fn default() -> Self { + Self::Stable + } +} + +fn parse_source(source: &str) -> Result> { + weedle::parse(source).map_err(|e| { + match &e { + weedle::Err::Incomplete(needed) => anyhow::anyhow!("needed {:?} more bytes", needed), + weedle::Err::Error(cx) | weedle::Err::Failure(cx) => { + // Note that #[allow] here is a workaround for Geal/nom#843 + // because the `Context` type here comes from `nom` and if + // something else in our crate graph enables the + // `verbose-errors` feature then we need to still compiled + // against the changed enum definition. + #[allow(unreachable_patterns)] + let remaining = match cx { + weedle::Context::Code(remaining, _) => remaining.len(), + _ => 0, + }; + let pos = source.len() - remaining; + + WebIDLParseError(pos).into() } } - }; + }) +} +/// Parse a string of WebIDL source text into a wasm-bindgen AST. +fn parse(webidl_source: &str, unstable_source: &str, allowed_types: Option<&[&str]>) -> Result { let mut first_pass_record: FirstPassRecord = Default::default(); first_pass_record.builtin_idents = builtin_idents(); first_pass_record.immutable_slice_whitelist = immutable_slice_whitelist(); - definitions.first_pass(&mut first_pass_record, ())?; + let definitions = parse_source(webidl_source)?; + definitions.first_pass(&mut first_pass_record, ApiStability::Stable)?; + + let unstable_definitions = parse_source(unstable_source)?; + unstable_definitions.first_pass(&mut first_pass_record, ApiStability::Unstable)?; + let mut program = Default::default(); let mut submodules = Vec::new(); @@ -136,14 +158,14 @@ fn parse(webidl_source: &str, allowed_types: Option<&[&str]>) -> Result Ok(Program { main: program, - submodules: submodules, + submodules, }) } /// Compile the given WebIDL source text into Rust source text containing /// `wasm-bindgen` bindings to the things described in the WebIDL. -pub fn compile(webidl_source: &str, allowed_types: Option<&[&str]>) -> Result { - let ast = parse(webidl_source, allowed_types)?; +pub fn compile(webidl_source: &str, experimental_source: &str, allowed_types: Option<&[&str]>) -> Result { + let ast = parse(webidl_source, experimental_source, allowed_types)?; Ok(compile_ast(ast)) } @@ -292,8 +314,10 @@ fn compile_ast(mut ast: Program) -> String { } impl<'src> FirstPassRecord<'src> { - fn append_enum(&self, program: &mut ast::Program, enum_: &'src weedle::EnumDefinition<'src>) { + fn append_enum(&self, program: &mut ast::Program, data: &first_pass::EnumData<'src>) { + let enum_ = data.definition; let variants = &enum_.values.body.list; + let unstable_api = data.stability.is_unstable(); program.imports.push(ast::Import { module: ast::ImportModule::None, js_namespace: None, @@ -312,7 +336,9 @@ impl<'src> FirstPassRecord<'src> { .collect(), variant_values: variants.iter().map(|v| v.0.to_string()).collect(), rust_attrs: vec![syn::parse_quote!(#[derive(Copy, Clone, PartialEq, Debug)])], + unstable_api, }), + unstable_api, }); } @@ -352,6 +378,7 @@ impl<'src> FirstPassRecord<'src> { ctor: true, doc_comment: Some(doc_comment), ctor_doc_comment: None, + unstable_api: data.stability.is_unstable(), }; let mut ctor_doc_comment = Some(format!("Construct a new `{}`\n", def.identifier.0)); self.append_required_features_doc(&dict, &mut ctor_doc_comment, &[&extra_feature]); @@ -504,7 +531,7 @@ impl<'src> FirstPassRecord<'src> { let kind = ast::ImportFunctionKind::Normal; let extra = snake_case_ident(self_name); let extra = &[&extra[..]]; - for mut import_function in self.create_imports(None, kind, id, data) { + for mut import_function in self.create_imports(None, kind, id, data, false) { let mut doc = Some(doc_comment.clone()); self.append_required_features_doc(&import_function, &mut doc, extra); import_function.doc_comment = doc; @@ -512,6 +539,7 @@ impl<'src> FirstPassRecord<'src> { module: ast::ImportModule::None, js_namespace: Some(raw_ident(self_name)), kind: ast::ImportKind::Function(import_function), + unstable_api: false, }); } } @@ -521,6 +549,7 @@ impl<'src> FirstPassRecord<'src> { program: &mut ast::Program, self_name: &'src str, member: &'src weedle::interface::ConstMember<'src>, + unstable_api: bool, ) { let idl_type = member.const_type.to_idl_type(self); let ty = match idl_type.to_syn_type(TypePosition::Return) { @@ -542,6 +571,7 @@ impl<'src> FirstPassRecord<'src> { class: Some(rust_ident(camel_case_ident(&self_name).as_str())), ty, value: webidl_const_v_to_backend_const_v(&member.const_value), + unstable_api, }); } @@ -553,6 +583,8 @@ impl<'src> FirstPassRecord<'src> { ) { let mut doc_comment = Some(format!("The `{}` object\n\n{}", name, mdn_doc(name, None),)); + let interface_unstable = data.stability.is_unstable(); + let mut attrs = Vec::new(); attrs.push(syn::parse_quote!( #[derive(Debug, Clone, PartialEq, Eq)] )); self.add_deprecated(data, &mut attrs); @@ -561,6 +593,7 @@ impl<'src> FirstPassRecord<'src> { rust_name: rust_ident(camel_case_ident(name).as_str()), js_name: name.to_string(), attrs, + unstable_api: interface_unstable, doc_comment: None, instanceof_shim: format!("__widl_instanceof_{}", name), is_type_of: if data.has_interface { @@ -596,25 +629,28 @@ impl<'src> FirstPassRecord<'src> { module: ast::ImportModule::None, js_namespace: None, kind: ast::ImportKind::Type(import_type), + unstable_api: interface_unstable, }); for (id, op_data) in data.operations.iter() { self.member_operation(program, name, data, id, op_data); } for member in data.consts.iter() { - self.append_const(program, name, member); + self.append_const(program, name, member, interface_unstable); } for member in data.attributes.iter() { + let member_def = member.definition; self.member_attribute( program, name, data, - member.modifier, - member.readonly.is_some(), - &member.type_, - member.identifier.0, - &member.attributes, + member_def.modifier, + member_def.readonly.is_some(), + &member_def.type_, + member_def.identifier.0, + &member_def.attributes, data.definition_attributes, + interface_unstable || member.stability.is_unstable(), ); } @@ -623,23 +659,25 @@ impl<'src> FirstPassRecord<'src> { self.member_operation(program, name, data, id, op_data); } for member in &mixin_data.consts { - self.append_const(program, name, member); + self.append_const(program, name, member, interface_unstable); } for member in &mixin_data.attributes { + let member_def = member.definition; self.member_attribute( program, name, data, - if let Some(s) = member.stringifier { + if let Some(s) = member_def.stringifier { Some(weedle::interface::StringifierOrInheritOrStatic::Stringifier(s)) } else { None }, - member.readonly.is_some(), - &member.type_, - member.identifier.0, - &member.attributes, + member_def.readonly.is_some(), + &member_def.type_, + member_def.identifier.0, + &member_def.attributes, data.definition_attributes, + interface_unstable || member.stability.is_unstable(), ); } } @@ -656,6 +694,7 @@ impl<'src> FirstPassRecord<'src> { identifier: &'src str, attrs: &'src Option>, container_attrs: Option<&'src ExtendedAttributeList<'src>>, + unstable_api: bool, ) { use weedle::interface::StringifierOrInheritOrStatic::*; @@ -673,6 +712,7 @@ impl<'src> FirstPassRecord<'src> { is_static, attrs, container_attrs, + unstable_api, ) { let mut doc = import_function.doc_comment.take(); self.append_required_features_doc(&import_function, &mut doc, &[]); @@ -688,6 +728,7 @@ impl<'src> FirstPassRecord<'src> { is_static, attrs, container_attrs, + unstable_api, ) { let mut doc = import_function.doc_comment.take(); self.append_required_features_doc(&import_function, &mut doc, &[]); @@ -742,7 +783,7 @@ impl<'src> FirstPassRecord<'src> { OperationId::IndexingDeleter => Some(format!("The indexing deleter\n\n")), }; let attrs = data.definition_attributes; - for mut method in self.create_imports(attrs, kind, id, op_data) { + for mut method in self.create_imports(attrs, kind, id, op_data, data.stability.is_unstable()) { let mut doc = doc.clone(); self.append_required_features_doc(&method, &mut doc, &[]); method.doc_comment = doc; @@ -843,6 +884,7 @@ impl<'src> FirstPassRecord<'src> { ctor: true, doc_comment: None, ctor_doc_comment: None, + unstable_api: false, }); } } diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index c15a6d1b..b567b178 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -231,6 +231,7 @@ impl<'src> FirstPassRecord<'src> { catch: bool, variadic: bool, doc_comment: Option, + unstable_api: bool, ) -> Option where 'src: 'a, @@ -327,6 +328,7 @@ impl<'src> FirstPassRecord<'src> { }, kind, doc_comment, + unstable_api, }) } @@ -339,6 +341,7 @@ impl<'src> FirstPassRecord<'src> { is_static: bool, attrs: &Option, container_attrs: Option<&ExtendedAttributeList>, + unstable_api: bool, ) -> Option { let kind = ast::OperationKind::Getter(Some(raw_ident(name))); let kind = self.import_function_kind(self_name, is_static, kind); @@ -357,6 +360,7 @@ impl<'src> FirstPassRecord<'src> { name, mdn_doc(self_name, Some(name)) )), + unstable_api, ) } @@ -369,6 +373,7 @@ impl<'src> FirstPassRecord<'src> { is_static: bool, attrs: &Option, container_attrs: Option<&ExtendedAttributeList>, + unstable_api: bool, ) -> Option { let kind = ast::OperationKind::Setter(Some(raw_ident(name))); let kind = self.import_function_kind(self_name, is_static, kind); @@ -387,6 +392,7 @@ impl<'src> FirstPassRecord<'src> { name, mdn_doc(self_name, Some(name)) )), + unstable_api, ) } @@ -414,6 +420,7 @@ impl<'src> FirstPassRecord<'src> { kind: ast::ImportFunctionKind, id: &OperationId<'src>, data: &OperationData<'src>, + unstable_api: bool, ) -> Vec { // First up, prune all signatures that reference unsupported arguments. // We won't consider these until said arguments are implemented. @@ -618,6 +625,7 @@ impl<'src> FirstPassRecord<'src> { catch, variadic, None, + unstable_api, ), ); if !variadic { @@ -644,6 +652,7 @@ impl<'src> FirstPassRecord<'src> { catch, false, None, + unstable_api, ), ); } diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index d4a57980..e93b5b22 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -88,6 +88,7 @@ - [Function Overloads](./web-sys/function-overloads.md) - [Type Translations](./web-sys/type-translations.md) - [Inheritance](./web-sys/inheritance.md) + - [Unstable APIs](./web-sys/unstable-apis.md) - [Testing with `wasm-bindgen-test`](./wasm-bindgen-test/index.md) - [Usage](./wasm-bindgen-test/usage.md) diff --git a/guide/src/web-sys/unstable-apis.md b/guide/src/web-sys/unstable-apis.md new file mode 100644 index 00000000..974a4dd4 --- /dev/null +++ b/guide/src/web-sys/unstable-apis.md @@ -0,0 +1,31 @@ +# Unstable APIs + +It's common for browsers to implement parts of a web API while the specification +for that API is still being written. The API may require frequent changes as the +specification continues to be developed, so the WebIDL is relatively unstable. + +This causes some challenges for `web-sys` because it means `web-sys` would have +to make breaking API changes whenever the WebIDL changes. It also means that +previously published `web-sys` versions would be invalid, because the browser +API may have been changed to match the updated WebIDL. + +To avoid frequent breaking changes for unstable APIs, `web-sys` hides all +unstable APIs through an attribute that looks like: + +```rust +#[cfg(web_sys_unstable_apis)] +pub struct Foo; +``` + +By hiding unstable APIs through an attribute, it's necessary for crates to +explicitly opt-in to these reduced stability guarantees in order to use these +APIs. Specifically, these APIs do not follow semver and may break whenever the +WebIDL changes. + +Crates can opt-in to unstable APIs at compile-time by passing the `cfg` flag +`web_sys_unstable_apis`. Typically the `RUSTFLAGS` environment variable is used +to do this. For example: + +```bash +RUSTFLAGS=--cfg=web_sys_unstable_apis cargo run +```