1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
//! Downcast a JavaScript wrapper generated by `wasm-bindgen` back to its original
//! struct.
//!
//! # Examples
//!
//! ```rust
//! use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
//! use wasm_bindgen_downcast::DowncastJS;
//!
//! #[wasm_bindgen]
//! #[derive(DowncastJS)]
//! pub struct Person {
//! name: String,
//! }
//!
//! /// Try to greet something. If it is the provided value wraps a [`Person`]
//! /// struct, we'll try to greet them by name.
//! #[wasm_bindgen]
//! pub fn greet(maybe_person: &JsValue) -> Option<String> {
//! let p = Person::downcast_js_ref(maybe_person)?;
//! Some(format!("Hello, {}!", p.name))
//! }
//! ```
pub use wasm_bindgen_downcast_macros::DowncastJS;
use js_sys::{Function, Reflect};
use wasm_bindgen::{
convert::{FromWasmAbi, RefFromWasmAbi},
JsCast, JsValue, UnwrapThrowExt,
};
/// Cast a Rust wrapper class that was generated by `#[wasm_bindgen]` back to
/// the underlying Rust type.
///
/// This is a workaround for
/// [rustwasm/wasm-bindgen#2231](https://github.com/rustwasm/wasm-bindgen/issues/2231)
/// that is robust with respect to minification (something other solutions
/// [can't handle](https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-1167895291)).
///
/// # Safety
///
/// This assumes the wrapper class has a static `__wbgd_downcast_symbol`
/// method which returns the same [`DowncastToken`] as
/// [`DowncastJS::token()`]. Each trait implementation.
///
/// Users should use the custom derive rather than implement this manually.
pub unsafe trait DowncastJS:
Sized + FromWasmAbi<Abi = u32> + RefFromWasmAbi<Abi = u32>
{
fn token() -> &'static DowncastToken;
fn downcast_js(value: JsValue) -> Result<Self, JsValue> {
// Safety: By using an unsafe trait, we're the responsibility to
// satisfy downcast_to_ptr()'s invariants is moved to the implementor.
// Assuming they are satisfied, it should be fine to convert back
// from the ABI representation.
unsafe {
match downcast_to_ptr::<Self>(&value) {
Some(ptr) => Ok(Self::from_abi(ptr)),
None => Err(value),
}
}
}
fn downcast_js_ref(value: &JsValue) -> Option<Self::Anchor> {
// Safety: By using an unsafe trait, we're the responsibility to
// satisfy downcast_to_ptr()'s invariants is moved to the implementor.
// Assuming they are satisfied, it should be fine to convert back
// from the ABI representation.
unsafe { downcast_to_ptr::<Self>(value).map(|ptr| Self::ref_from_abi(ptr)) }
}
}
// This whole mechanism works because the JavaScript wrapper
// class has a static `__wbgd_downcast_symbol()` method which returns the
// same unique Symbol we get in the [`DowncastJS::symbol()`] method.
//
// If we can read that symbol and it matches the symbol we get from
// [`DowncastJS::symbol()`], we know for sure that the pointer is valid and safe
// to cast back to our type.
unsafe fn downcast_to_ptr<T: DowncastJS>(value: &JsValue) -> Option<u32> {
if !value.is_object() {
return None;
}
let class = Reflect::get_prototype_of(value).ok()?.constructor();
let key = JsValue::from_str("__wbgd_downcast_token");
let downcast_symbol_func: Function = Reflect::get(&class, &key)
.and_then(|v: JsValue| v.dyn_into())
.ok()?;
let actual_symbol = downcast_symbol_func.call0(&class).ok()?;
if *T::token() != actual_symbol {
return None;
}
// Note: this assumes the wrapper class generated by #[wasm_bindgen] will
// always store the pointer in a field called "ptr".
let key = JsValue::from_str("ptr");
let ptr = Reflect::get(value, &key).ok().and_then(|v| v.as_f64())?;
Some(ptr as u32)
}
/// A token used when downcasting wrapper classes back to their Rust types.
#[derive(Clone)]
pub struct DowncastToken(pub js_sys::Symbol);
impl DowncastToken {
/// Create a new, unique token.
pub fn unique() -> Self {
#[wasm_bindgen::prelude::wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = "Symbol")]
static SYMBOL: js_sys::Function;
}
let js_symbol = SYMBOL
.call0(&JsValue::NULL)
.expect_throw("Unable to call the Symbol() function")
.dyn_into()
.expect_throw("Unable to convert the return value of Symbol() into a symbol");
DowncastToken(js_symbol)
}
}
impl PartialEq<wasm_bindgen::JsValue> for DowncastToken {
fn eq(&self, other: &wasm_bindgen::JsValue) -> bool {
*self.0 == *other
}
}
// Safety: This should be okay because
// a) JavaScript is single-threaded, and
// b) The Symbol type is only ever stored in an once_cell::sync::Lazy
// without being mutated.
unsafe impl Sync for DowncastToken {}
unsafe impl Send for DowncastToken {}
#[doc(hidden)]
pub mod internal {
pub use crate::DowncastToken;
pub use js_sys::Symbol;
pub use once_cell::sync::Lazy;
pub use wasm_bindgen::prelude::wasm_bindgen;
}