Skip to content

Commit a83d561

Browse files
committed
Add js_sys::global
1 parent 604ecd9 commit a83d561

File tree

3 files changed

+62
-87
lines changed

3 files changed

+62
-87
lines changed

crates/js-sys/src/lib.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ extern crate wasm_bindgen;
2323
use std::mem;
2424
use std::fmt;
2525

26+
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering::SeqCst};
27+
use wasm_bindgen::JsCast;
2628
use wasm_bindgen::prelude::*;
2729

2830
// When adding new imports:
@@ -4100,3 +4102,54 @@ extern {
41004102
#[wasm_bindgen(method)]
41014103
pub fn finally(this: &Promise, cb: &Closure<FnMut()>) -> Promise;
41024104
}
4105+
4106+
/// Returns a handle to the global scope object.
4107+
///
4108+
/// This allows access to the global properties and global names by accessing
4109+
/// the `Object` returned.
4110+
pub fn global() -> Object {
4111+
// Cached `Box<JsValue>`, if we've already executed this.
4112+
//
4113+
// 0 = not calculated
4114+
// n = Some(n) == Some(Box<JsValue>)
4115+
static GLOBAL: AtomicUsize = ATOMIC_USIZE_INIT;
4116+
4117+
match GLOBAL.load(SeqCst) {
4118+
0 => {}
4119+
n => return unsafe { (*(n as *const JsValue)).clone().unchecked_into() },
4120+
}
4121+
4122+
// Ok we don't have a cached value, let's load one!
4123+
//
4124+
// According to StackOverflow you can access the global object via:
4125+
//
4126+
// const global = Function('return this')();
4127+
//
4128+
// I think that's because the manufactured function isn't in "strict" mode.
4129+
// It also turns out that non-strict functions will ignore `undefined`
4130+
// values for `this` when using the `apply` function.
4131+
//
4132+
// As a result we use the equivalent of this snippet to get a handle to the
4133+
// global object in a sort of roundabout way that should hopefully work in
4134+
// all contexts like ESM, node, browsers, etc.
4135+
let this = Function::new_no_args("return this")
4136+
.call0(&JsValue::undefined())
4137+
.ok();
4138+
4139+
// Note that we avoid `unwrap()` on `call0` to avoid code size bloat, we
4140+
// just handle the `Err` case as returning a different object.
4141+
debug_assert!(this.is_some());
4142+
let this = match this {
4143+
Some(this) => this,
4144+
None => return JsValue::undefined().unchecked_into(),
4145+
};
4146+
4147+
let ptr: *mut JsValue = Box::into_raw(Box::new(this.clone()));
4148+
match GLOBAL.compare_exchange(0, ptr as usize, SeqCst, SeqCst) {
4149+
// We stored out value, relinquishing ownership of `ptr`
4150+
Ok(_) => {}
4151+
// Another thread one, drop our value
4152+
Err(_) => unsafe { drop(Box::from_raw(ptr)) },
4153+
}
4154+
this.unchecked_into()
4155+
}

crates/test/src/rt/detect.rs

Lines changed: 6 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! Runtime detection of whether we're in node.js or a browser.
22
33
use wasm_bindgen::prelude::*;
4-
use js_sys::Function;
4+
use wasm_bindgen::JsCast;
5+
use js_sys;
56

67
#[wasm_bindgen]
78
extern {
@@ -13,49 +14,8 @@ extern {
1314
/// Returns whether it's likely we're executing in a browser environment, as
1415
/// opposed to node.js.
1516
pub fn is_browser() -> bool {
16-
// This is a bit tricky to define. The basic crux of this is that we want to
17-
// test if the `self` identifier is defined. That is defined in browsers
18-
// (and web workers!) but not in Node. To that end you might expect:
19-
//
20-
// #[wasm_bindgen]
21-
// extern {
22-
// #[wasm_bindgen(js_name = self)]
23-
// static SELF: JsValue;
24-
// }
25-
//
26-
// *SELF != JsValue::undefined()
27-
//
28-
// this currently, however, throws a "not defined" error in JS because the
29-
// generated function looks like `function() { return self; }` which throws
30-
// an error in Node because `self` isn't defined.
31-
//
32-
// To work around this limitation we instead lookup the value of `self`
33-
// through the `this` object, basically generating `this.self`.
34-
//
35-
// Unfortunately that's also hard to do! In ESM modes the top-level `this`
36-
// object is undefined, meaning that we can't just generate a function that
37-
// returns `this.self` as it'll throw "can't access field `self` of
38-
// `undefined`" whenever ESMs are being used.
39-
//
40-
// So finally we reach the current implementation. According to
41-
// StackOverflow you can access the global object via:
42-
//
43-
// const global = Function('return this')();
44-
//
45-
// I think that's because the manufactured function isn't in "strict" mode.
46-
// It also turns out that non-strict functions will ignore `undefined`
47-
// values for `this` when using the `apply` function. Add it all up, and you
48-
// get the below code:
49-
//
50-
// * Manufacture a function
51-
// * Call `apply` where we specify `this` but the function ignores it
52-
// * Once we have `this`, use a structural getter to get the value of `self`
53-
// * Last but not least, test whether `self` is defined or not.
54-
//
55-
// Whew!
56-
let this = Function::new_no_args("return this")
57-
.call0(&JsValue::undefined())
58-
.unwrap();
59-
assert!(this != JsValue::undefined());
60-
This::from(this).self_() != JsValue::undefined()
17+
// Test whether we're in a browser by seeing if the `self` property is
18+
// defined on the global object, which should in turn only be true in
19+
// browsers.
20+
js_sys::global().unchecked_into::<This>().self_() != JsValue::undefined()
6121
}

crates/web-sys/src/lib.rs

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,52 +15,14 @@
1515

1616
extern crate wasm_bindgen;
1717
extern crate js_sys;
18+
1819
use js_sys::Object;
1920

2021
#[cfg(feature = "Window")]
2122
pub fn window() -> Option<Window> {
22-
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering::SeqCst};
23-
use wasm_bindgen::{JsValue, JsCast};
24-
25-
// Cached `Box<JsValue>`, if we've already executed this.
26-
//
27-
// 0 = not calculated
28-
// 1 = `None`
29-
// n = Some(n) == Some(Box<JsValue>)
30-
static WINDOW: AtomicUsize = ATOMIC_USIZE_INIT;
31-
32-
match WINDOW.load(SeqCst) {
33-
0 => {}
34-
1 => return None,
35-
n => return unsafe { Some((*(n as *const JsValue)).clone().unchecked_into()) },
36-
}
37-
38-
// Ok we don't have a cached value, let's load one! Manufacture a function
39-
// to get access to the `this` context and see if it's an instance of
40-
// `Window`.
41-
//
42-
// Note that we avoid `unwrap()` on `call0` to avoid code size bloat, we
43-
// just handle the `Err` case as returning `None`.
44-
let window = js_sys::Function::new_no_args("return this")
45-
.call0(&JsValue::undefined())
46-
.ok()
47-
.and_then(|w| w.dyn_into::<Window>().ok());
48-
49-
match &window {
50-
None => WINDOW.store(1, SeqCst),
51-
Some(window) => {
52-
let window: &JsValue = window.as_ref();
53-
let ptr: *mut JsValue = Box::into_raw(Box::new(window.clone()));
54-
match WINDOW.compare_exchange(0, ptr as usize, SeqCst, SeqCst) {
55-
// We stored out value, relinquishing ownership of `ptr`
56-
Ok(_) => {}
57-
// Another thread one, drop our value
58-
Err(_) => unsafe { drop(Box::from_raw(ptr)) },
59-
}
60-
}
61-
}
23+
use wasm_bindgen::JsCast;
6224

63-
window
25+
js_sys::global().dyn_into::<Window>().ok()
6426
}
6527

6628
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

0 commit comments

Comments
 (0)