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/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 c7dbacad..4ec67863 100755 --- a/crates/web-sys/src/lib.rs +++ b/crates/web-sys/src/lib.rs @@ -15,52 +15,14 @@ extern crate wasm_bindgen; extern crate js_sys; + use js_sys::Object; #[cfg(feature = "Window")] pub fn window() -> Option { - use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering::SeqCst}; - use wasm_bindgen::{JsValue, JsCast}; + use wasm_bindgen::JsCast; - // Cached `Box`, if we've already executed this. - // - // 0 = not calculated - // 1 = `None` - // n = Some(n) == Some(Box) - static WINDOW: AtomicUsize = ATOMIC_USIZE_INIT; - - match WINDOW.load(SeqCst) { - 0 => {} - 1 => return None, - n => return unsafe { Some((*(n as *const JsValue)).clone().unchecked_into()) }, - } - - // Ok we don't have a cached value, let's load one! Manufacture a function - // to get access to the `this` context and see if it's an instance of - // `Window`. - // - // Note that we avoid `unwrap()` on `call0` to avoid code size bloat, we - // just handle the `Err` case as returning `None`. - let window = js_sys::Function::new_no_args("return this") - .call0(&JsValue::undefined()) - .ok() - .and_then(|w| w.dyn_into::().ok()); - - match &window { - None => WINDOW.store(1, SeqCst), - Some(window) => { - let window: &JsValue = window.as_ref(); - let ptr: *mut JsValue = Box::into_raw(Box::new(window.clone())); - match WINDOW.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)) }, - } - } - } - - window + js_sys::global().dyn_into::().ok() } include!(concat!(env!("OUT_DIR"), "/bindings.rs"));