diff --git a/crates/web-sys/src/lib.rs b/crates/web-sys/src/lib.rs index 36944101..c7dbacad 100755 --- a/crates/web-sys/src/lib.rs +++ b/crates/web-sys/src/lib.rs @@ -19,13 +19,48 @@ 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}; - js_sys::Function::new_no_args("return this") + // 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()? - .dyn_into::() .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 } 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(); }