diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 97213216..406fb0a3 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -98,10 +98,6 @@ pub enum ImportFunctionKind { ty: syn::Type, kind: MethodKind, }, - ScopedMethod { - ty: syn::Type, - operation: Operation, - }, Normal, } @@ -432,16 +428,10 @@ impl ImportFunction { } }; Some(shared::MethodData { - class: Some(class.clone()), + class: class.clone(), kind, }) } - ImportFunctionKind::ScopedMethod { ref operation, .. } => { - Some(shared::MethodData { - class: None, - kind: shared::MethodKind::Operation(shared_operation(operation)), - }) - } ImportFunctionKind::Normal => None, }; diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 15a95829..28353d7f 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -772,9 +772,6 @@ impl TryToTokens for ast::ImportFunction { } class_ty = Some(ty); } - ast::ImportFunctionKind::ScopedMethod { ref ty, .. } => { - class_ty = Some(ty); - } ast::ImportFunctionKind::Normal => {} } let vis = &self.function.rust_vis; diff --git a/crates/backend/src/defined.rs b/crates/backend/src/defined.rs index 8eef37b7..35ebae37 100644 --- a/crates/backend/src/defined.rs +++ b/crates/backend/src/defined.rs @@ -256,7 +256,6 @@ impl ImportedTypes for ast::ImportFunctionKind { { match self { ast::ImportFunctionKind::Method { ty, .. } => ty.imported_types(f), - ast::ImportFunctionKind::ScopedMethod { ty, .. } => ty.imported_types(f), ast::ImportFunctionKind::Normal => {} } } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 1bd0b5b0..e19c05fe 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2008,30 +2008,7 @@ impl<'a, 'b> SubContext<'a, 'b> { } }; - let class = match &method_data.class { - Some(class) => self.import_name(info, class)?, - None => { - let op = match &method_data.kind { - shared::MethodKind::Operation(op) => op, - shared::MethodKind::Constructor => { - bail!("\"no class\" methods cannot be constructors") - } - }; - match &op.kind { - shared::OperationKind::Regular => { - return Ok(import.function.name.to_string()) - } - shared::OperationKind::Getter(g) => { - return Ok(format!("(() => {})", g)); - } - shared::OperationKind::Setter(g) => { - return Ok(format!("(v => {} = v)", g)); - } - _ => bail!("\"no class\" methods must be regular/getter/setter"), - } - - } - }; + let class = self.import_name(info, &method_data.class)?; let op = match &method_data.kind { shared::MethodKind::Constructor => return Ok(format!("new {}", class)), shared::MethodKind::Operation(op) => op, diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index fd20899f..d72bbcac 100644 --- a/crates/js-sys/src/lib.rs +++ b/crates/js-sys/src/lib.rs @@ -23,6 +23,8 @@ extern crate wasm_bindgen; use std::mem; use std::fmt; +use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering::SeqCst}; +use wasm_bindgen::JsCast; use wasm_bindgen::prelude::*; // When adding new imports: @@ -4100,3 +4102,54 @@ extern { #[wasm_bindgen(method)] pub fn finally(this: &Promise, cb: &Closure) -> Promise; } + +/// Returns a handle to the global scope object. +/// +/// This allows access to the global properties and global names by accessing +/// the `Object` returned. +pub fn global() -> Object { + // Cached `Box`, if we've already executed this. + // + // 0 = not calculated + // n = Some(n) == Some(Box) + static GLOBAL: AtomicUsize = ATOMIC_USIZE_INIT; + + match GLOBAL.load(SeqCst) { + 0 => {} + n => return unsafe { (*(n as *const JsValue)).clone().unchecked_into() }, + } + + // Ok we don't have a cached value, let's load one! + // + // According to StackOverflow you can access the global object via: + // + // const global = Function('return this')(); + // + // I think that's because the manufactured function isn't in "strict" mode. + // It also turns out that non-strict functions will ignore `undefined` + // values for `this` when using the `apply` function. + // + // As a result we use the equivalent of this snippet to get a handle to the + // global object in a sort of roundabout way that should hopefully work in + // all contexts like ESM, node, browsers, etc. + let this = Function::new_no_args("return this") + .call0(&JsValue::undefined()) + .ok(); + + // Note that we avoid `unwrap()` on `call0` to avoid code size bloat, we + // just handle the `Err` case as returning a different object. + debug_assert!(this.is_some()); + let this = match this { + Some(this) => this, + None => return JsValue::undefined().unchecked_into(), + }; + + let ptr: *mut JsValue = Box::into_raw(Box::new(this.clone())); + match GLOBAL.compare_exchange(0, ptr as usize, SeqCst, SeqCst) { + // We stored out value, relinquishing ownership of `ptr` + Ok(_) => {} + // Another thread one, drop our value + Err(_) => unsafe { drop(Box::from_raw(ptr)) }, + } + this.unchecked_into() +} diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 968f04c7..76bee033 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -510,7 +510,6 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn let shim = { let ns = match kind { - ast::ImportFunctionKind::ScopedMethod { .. } | ast::ImportFunctionKind::Normal => (0, "n"), ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]), }; diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index b2250d6d..3af33c36 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -51,7 +51,7 @@ pub struct ImportFunction { #[derive(Deserialize, Serialize)] pub struct MethodData { - pub class: Option, + pub class: String, pub kind: MethodKind, } diff --git a/crates/test/src/rt/detect.rs b/crates/test/src/rt/detect.rs index b8ce683a..ab5a2300 100644 --- a/crates/test/src/rt/detect.rs +++ b/crates/test/src/rt/detect.rs @@ -1,7 +1,8 @@ //! Runtime detection of whether we're in node.js or a browser. use wasm_bindgen::prelude::*; -use js_sys::Function; +use wasm_bindgen::JsCast; +use js_sys; #[wasm_bindgen] extern { @@ -13,49 +14,8 @@ extern { /// Returns whether it's likely we're executing in a browser environment, as /// opposed to node.js. pub fn is_browser() -> bool { - // This is a bit tricky to define. The basic crux of this is that we want to - // test if the `self` identifier is defined. That is defined in browsers - // (and web workers!) but not in Node. To that end you might expect: - // - // #[wasm_bindgen] - // extern { - // #[wasm_bindgen(js_name = self)] - // static SELF: JsValue; - // } - // - // *SELF != JsValue::undefined() - // - // this currently, however, throws a "not defined" error in JS because the - // generated function looks like `function() { return self; }` which throws - // an error in Node because `self` isn't defined. - // - // To work around this limitation we instead lookup the value of `self` - // through the `this` object, basically generating `this.self`. - // - // Unfortunately that's also hard to do! In ESM modes the top-level `this` - // object is undefined, meaning that we can't just generate a function that - // returns `this.self` as it'll throw "can't access field `self` of - // `undefined`" whenever ESMs are being used. - // - // So finally we reach the current implementation. According to - // StackOverflow you can access the global object via: - // - // const global = Function('return this')(); - // - // I think that's because the manufactured function isn't in "strict" mode. - // It also turns out that non-strict functions will ignore `undefined` - // values for `this` when using the `apply` function. Add it all up, and you - // get the below code: - // - // * Manufacture a function - // * Call `apply` where we specify `this` but the function ignores it - // * Once we have `this`, use a structural getter to get the value of `self` - // * Last but not least, test whether `self` is defined or not. - // - // Whew! - let this = Function::new_no_args("return this") - .call0(&JsValue::undefined()) - .unwrap(); - assert!(this != JsValue::undefined()); - This::from(this).self_() != JsValue::undefined() + // Test whether we're in a browser by seeing if the `self` property is + // defined on the global object, which should in turn only be true in + // browsers. + js_sys::global().unchecked_into::().self_() != JsValue::undefined() } diff --git a/crates/web-sys/src/lib.rs b/crates/web-sys/src/lib.rs index bccf5838..4ec67863 100755 --- a/crates/web-sys/src/lib.rs +++ b/crates/web-sys/src/lib.rs @@ -15,6 +15,14 @@ extern crate wasm_bindgen; extern crate js_sys; + use js_sys::Object; +#[cfg(feature = "Window")] +pub fn window() -> Option { + use wasm_bindgen::JsCast; + + js_sys::global().dyn_into::().ok() +} + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/crates/web-sys/tests/wasm/location.rs b/crates/web-sys/tests/wasm/location.rs index d6193a67..aa41d0d5 100644 --- a/crates/web-sys/tests/wasm/location.rs +++ b/crates/web-sys/tests/wasm/location.rs @@ -1,56 +1,60 @@ use wasm_bindgen_test::*; -use web_sys::Window; +use web_sys::{self, Location}; + +fn location() -> Location { + web_sys::window().unwrap().location() +} #[wasm_bindgen_test] fn href() { - let loc = Window::location(); + let loc = location(); loc.href().unwrap(); } #[wasm_bindgen_test] fn origin() { - let loc = Window::location(); + let loc = location(); loc.origin().unwrap(); } #[wasm_bindgen_test] fn protocol() { - let loc = Window::location(); + let loc = location(); loc.protocol().unwrap(); } #[wasm_bindgen_test] fn host() { - let loc = Window::location(); + let loc = location(); loc.host().unwrap(); } #[wasm_bindgen_test] fn hostname() { - let loc = Window::location(); + let loc = location(); loc.hostname().unwrap(); } #[wasm_bindgen_test] fn port() { - let loc = Window::location(); + let loc = location(); loc.port().unwrap(); } #[wasm_bindgen_test] fn pathname() { - let loc = Window::location(); + let loc = location(); loc.pathname().unwrap(); } #[wasm_bindgen_test] fn search() { - let loc = Window::location(); + let loc = location(); loc.search().unwrap(); } #[wasm_bindgen_test] fn hash() { - let loc = Window::location(); + let loc = location(); loc.hash().unwrap(); } diff --git a/crates/webidl-tests/global.js b/crates/webidl-tests/global.js index 305de81b..b703d16c 100644 --- a/crates/webidl-tests/global.js +++ b/crates/webidl-tests/global.js @@ -1,4 +1,8 @@ -global.global_no_args = () => 3; -global.global_with_args = (a, b) => a + b; -global.global_attribute = 'x'; +const map = { + global_no_args: () => 3, + global_with_args: (a, b) => a + b, + global_attribute: 'x', +}; + +global.get_global = () => map; diff --git a/crates/webidl-tests/global.rs b/crates/webidl-tests/global.rs index d52305fc..2a984690 100644 --- a/crates/webidl-tests/global.rs +++ b/crates/webidl-tests/global.rs @@ -1,13 +1,20 @@ use js_sys::Object; +use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; include!(concat!(env!("OUT_DIR"), "/global.rs")); +#[wasm_bindgen] +extern { + fn get_global() -> Global; +} + #[wasm_bindgen_test] fn works() { - assert_eq!(Global::global_no_args(), 3); - assert_eq!(Global::global_with_args("a", "b"), "ab"); - assert_eq!(Global::global_attribute(), "x"); - Global::set_global_attribute("y"); - assert_eq!(Global::global_attribute(), "y"); + let x = get_global(); + assert_eq!(x.global_no_args(), 3); + assert_eq!(x.global_with_args("a", "b"), "ab"); + assert_eq!(x.global_attribute(), "x"); + x.set_global_attribute("y"); + assert_eq!(x.global_attribute(), "y"); } diff --git a/crates/webidl-tests/simple.js b/crates/webidl-tests/simple.js index eaeb2bbd..8498b547 100644 --- a/crates/webidl-tests/simple.js +++ b/crates/webidl-tests/simple.js @@ -87,7 +87,11 @@ global.Unforgeable = class Unforgeable { } }; -global.m = () => 123; +global.GlobalMethod = class { + m() { return 123; } +}; + +global.get_global_method = () => new GlobalMethod(); global.Indexing = function () { return new Proxy({}, { diff --git a/crates/webidl-tests/simple.rs b/crates/webidl-tests/simple.rs index 7c1923f8..21a710e4 100644 --- a/crates/webidl-tests/simple.rs +++ b/crates/webidl-tests/simple.rs @@ -72,9 +72,16 @@ fn nullable_method() { assert!(f.opt(None) == None); } +#[wasm_bindgen] +extern { + fn get_global_method() -> GlobalMethod; +} + + #[wasm_bindgen_test] fn global_method() { - assert_eq!(GlobalMethod::m(), 123); + let x = get_global_method(); + assert_eq!(x.m(), 123); } #[wasm_bindgen_test] diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 66762095..c149e190 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -44,7 +44,6 @@ pub(crate) struct FirstPassRecord<'src> { pub(crate) struct InterfaceData<'src> { /// Whether only partial interfaces were encountered pub(crate) partial: bool, - pub(crate) global: bool, pub(crate) attributes: Vec<&'src AttributeInterfaceMember<'src>>, pub(crate) consts: Vec<&'src ConstMember<'src>>, pub(crate) operations: BTreeMap, OperationData<'src>>, @@ -373,12 +372,6 @@ fn process_interface_attribute<'src>( false, ); } - ExtendedAttribute::Ident(id) if id.lhs_identifier.0 == "Global" => { - record.interfaces.get_mut(self_name).unwrap().global = true; - } - ExtendedAttribute::IdentList(id) if id.identifier.0 == "Global" => { - record.interfaces.get_mut(self_name).unwrap().global = true; - } _ => {} } } diff --git a/crates/webidl/src/idl_type.rs b/crates/webidl/src/idl_type.rs index 2696ebfb..d5b4594e 100644 --- a/crates/webidl/src/idl_type.rs +++ b/crates/webidl/src/idl_type.rs @@ -305,6 +305,14 @@ impl<'a> ToIdlType<'a> for Identifier<'a> { name: self.0, single_function: data.single_function, }) + } else if self.0 == "WindowProxy" { + // See this for more info: + // + // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy + // + // namely this seems to be "legalese" for "this is a `Window`", so + // let's translate it as such. + Some(IdlType::Interface("Window")) } else { warn!("Unrecognized type: {}", self.0); None diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index c79e4e3f..f88a314c 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -554,18 +554,11 @@ impl<'src> FirstPassRecord<'src> { None => false, }; - let global = self - .interfaces - .get(self_name) - .map(|interface_data| interface_data.global) - .unwrap_or(false); - for mut import_function in self.create_getter( identifier, &type_.type_, self_name, is_static, - global, attrs, container_attrs, ) { @@ -581,7 +574,6 @@ impl<'src> FirstPassRecord<'src> { &type_.type_, self_name, is_static, - global, attrs, container_attrs, ) { @@ -602,7 +594,7 @@ impl<'src> FirstPassRecord<'src> { op_data: &OperationData<'src>, ) { let import_function_kind = |opkind| { - self.import_function_kind(self_name, data.global, op_data.is_static, opkind) + self.import_function_kind(self_name, op_data.is_static, opkind) }; let kind = match id { OperationId::Constructor(ctor_name) => { diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index 05ffd7af..7ed04a2d 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -316,7 +316,6 @@ impl<'src> FirstPassRecord<'src> { structural, shim: { let ns = match kind { - backend::ast::ImportFunctionKind::ScopedMethod { .. } | backend::ast::ImportFunctionKind::Normal => "", backend::ast::ImportFunctionKind::Method { ref class, .. } => class, }; @@ -334,12 +333,11 @@ impl<'src> FirstPassRecord<'src> { ty: &weedle::types::Type<'src>, self_name: &str, is_static: bool, - global: bool, attrs: &Option, container_attrs: Option<&ExtendedAttributeList>, ) -> Option { let kind = backend::ast::OperationKind::Getter(Some(raw_ident(name))); - let kind = self.import_function_kind(self_name, global, is_static, kind); + let kind = self.import_function_kind(self_name, is_static, kind); let ret = ty.to_idl_type(self)?; self.create_one_function( &name, @@ -361,12 +359,11 @@ impl<'src> FirstPassRecord<'src> { field_ty: &weedle::types::Type<'src>, self_name: &str, is_static: bool, - global: bool, attrs: &Option, container_attrs: Option<&ExtendedAttributeList>, ) -> Option { let kind = backend::ast::OperationKind::Setter(Some(raw_ident(name))); - let kind = self.import_function_kind(self_name, global, is_static, kind); + let kind = self.import_function_kind(self_name, is_static, kind); let field_ty = field_ty.to_idl_type(self)?; self.create_one_function( &name, @@ -384,7 +381,6 @@ impl<'src> FirstPassRecord<'src> { pub fn import_function_kind( &self, self_name: &str, - global: bool, is_static: bool, operation_kind: backend::ast::OperationKind, ) -> backend::ast::ImportFunctionKind { @@ -393,17 +389,10 @@ impl<'src> FirstPassRecord<'src> { kind: operation_kind, }; let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str())); - if global { - backend::ast::ImportFunctionKind::ScopedMethod { - ty, - operation, - } - } else { - backend::ast::ImportFunctionKind::Method { - class: self_name.to_string(), - ty, - kind: backend::ast::MethodKind::Operation(operation), - } + backend::ast::ImportFunctionKind::Method { + class: self_name.to_string(), + ty, + kind: backend::ast::MethodKind::Operation(operation), } } @@ -641,6 +630,18 @@ fn has_named_attribute(list: Option<&ExtendedAttributeList>, attribute: &str) -> }) } +fn has_ident_attribute(list: Option<&ExtendedAttributeList>, ident: &str) -> bool { + let list = match list { + Some(list) => list, + None => return false, + }; + list.body.list.iter().any(|attr| match attr { + ExtendedAttribute::Ident(id) => id.lhs_identifier.0 == ident, + ExtendedAttribute::IdentList(id) => id.identifier.0 == ident, + _ => false, + }) +} + /// ChromeOnly is for things that are only exposed to privileged code in Firefox. pub fn is_chrome_only(ext_attrs: &Option) -> bool { has_named_attribute(ext_attrs.as_ref(), "ChromeOnly") @@ -657,7 +658,8 @@ pub fn is_structural( container_attrs: Option<&ExtendedAttributeList>, ) -> bool { has_named_attribute(item_attrs, "Unforgeable") || - has_named_attribute(container_attrs, "Unforgeable") + has_named_attribute(container_attrs, "Unforgeable") || + has_ident_attribute(container_attrs, "Global") } /// Whether a webidl object is marked as throwing. diff --git a/examples/canvas/src/lib.rs b/examples/canvas/src/lib.rs index 1b200c4b..5396975c 100755 --- a/examples/canvas/src/lib.rs +++ b/examples/canvas/src/lib.rs @@ -8,7 +8,7 @@ use wasm_bindgen::JsCast; #[wasm_bindgen] pub fn draw() { - let document = web_sys::Window::document().unwrap(); + let document = web_sys::window().unwrap().document().unwrap(); let canvas = document.get_element_by_id("canvas").unwrap(); let canvas: web_sys::HtmlCanvasElement = canvas .dyn_into::() diff --git a/examples/fetch/build.sh b/examples/fetch/build.sh index 585f428c..78b9a458 100755 --- a/examples/fetch/build.sh +++ b/examples/fetch/build.sh @@ -8,3 +8,6 @@ cargo +nightly build --target wasm32-unknown-unknown cargo +nightly run --manifest-path ../../crates/cli/Cargo.toml \ --bin wasm-bindgen -- \ ../../target/wasm32-unknown-unknown/debug/fetch.wasm --out-dir . + +npm install +npm run serve diff --git a/examples/fetch/package.json b/examples/fetch/package.json index 596e8d02..176a69a4 100644 --- a/examples/fetch/package.json +++ b/examples/fetch/package.json @@ -1,10 +1,11 @@ { "scripts": { - "serve": "webpack-serve ./webpack.config.js" + "serve": "webpack-dev-server" }, "devDependencies": { "html-webpack-plugin": "^3.2.0", - "webpack": "^4.16.5", - "webpack-serve": "^2.0.2" + "webpack": "^4.11.1", + "webpack-cli": "^2.0.10", + "webpack-dev-server": "^3.1.0" } } diff --git a/examples/fetch/src/lib.rs b/examples/fetch/src/lib.rs index 523d4cf8..02d851b1 100644 --- a/examples/fetch/src/lib.rs +++ b/examples/fetch/src/lib.rs @@ -12,7 +12,7 @@ use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::future_to_promise; use wasm_bindgen_futures::JsFuture; -use web_sys::{Request, RequestInit, RequestMode, Response, Window}; +use web_sys::{Request, RequestInit, RequestMode, Response}; /// A struct to hold some data from the github Branch API. /// @@ -57,7 +57,8 @@ pub fn run() -> Promise { .set("Accept", "application/vnd.github.v3+json") .unwrap(); - let request_promise = Window::fetch_with_request(&request); + let window = web_sys::window().unwrap(); + let request_promise = window.fetch_with_request(&request); let future = JsFuture::from(request_promise) .and_then(|resp_value| { diff --git a/examples/paint/src/lib.rs b/examples/paint/src/lib.rs index 905b956f..f7abc520 100755 --- a/examples/paint/src/lib.rs +++ b/examples/paint/src/lib.rs @@ -9,7 +9,7 @@ use wasm_bindgen::JsCast; #[wasm_bindgen] pub fn main() { - let document = web_sys::Window::document().unwrap(); + let document = web_sys::window().unwrap().document().unwrap(); let canvas = document .create_element("canvas") .unwrap()