diff --git a/.travis.yml b/.travis.yml index 3f1a7fb2e48..d84248e5b27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ INSTALL_NODE_VIA_NVM: &INSTALL_NODE_VIA_NVM rustup target add wasm32-unknown-unknown curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash source ~/.nvm/nvm.sh - nvm install v10.5 + nvm install v10.9 INSTALL_GECKODRIVER: &INSTALL_GECKODRIVER | diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index c63470ed180..97a062f48bc 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -338,6 +338,88 @@ impl<'a> Context<'a> { )) })?; + self.bind("__wbindgen_debug_string", &|me| { + me.expose_pass_string_to_wasm()?; + me.expose_get_object(); + me.expose_uint32_memory(); + Ok(String::from( + " + function(i, len_ptr) { + const toString = Object.prototype.toString; + const debug_str = val => { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `\"${val}\"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debug_str(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debug_str(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; + }; + const val = getObject(i); + const debug = debug_str(val); + const ptr = passStringToWasm(debug); + getUint32Memory()[len_ptr / 4] = WASM_VECTOR_LEN; + return ptr; + } + ", + )) + })?; + self.bind("__wbindgen_cb_drop", &|me| { me.expose_drop_ref(); Ok(String::from( diff --git a/src/lib.rs b/src/lib.rs index f8a0f12ff03..7b2ffb94184 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -303,6 +303,21 @@ impl JsValue { pub fn is_function(&self) -> bool { unsafe { __wbindgen_is_function(self.idx) == 1 } } + + /// Get a string representation of the JavaScript object for debugging + #[cfg(feature = "std")] + fn as_debug_string(&self) -> String { + unsafe { + let mut len = 0; + let ptr = __wbindgen_debug_string(self.idx, &mut len); + if ptr.is_null() { + unreachable!("`__wbindgen_debug_string` must return a valid string") + } else { + let data = Vec::from_raw_parts(ptr, len, len); + String::from_utf8_unchecked(data) + } + } + } } impl PartialEq for JsValue { @@ -477,6 +492,7 @@ externs! { fn __wbindgen_is_function(idx: u32) -> u32; fn __wbindgen_is_string(idx: u32) -> u32; fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8; + fn __wbindgen_debug_string(idx: u32, len: *mut usize) -> *mut u8; fn __wbindgen_throw(a: *const u8, b: usize) -> !; fn __wbindgen_rethrow(a: u32) -> !; @@ -503,30 +519,17 @@ impl Clone for JsValue { } } +#[cfg(feature = "std")] impl fmt::Debug for JsValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(n) = self.as_f64() { - return n.fmt(f); - } - #[cfg(feature = "std")] - { - if let Some(n) = self.as_string() { - return n.fmt(f); - } - } - if let Some(n) = self.as_bool() { - return n.fmt(f); - } - if self.is_null() { - return fmt::Display::fmt("null", f); - } - if self.is_undefined() { - return fmt::Display::fmt("undefined", f); - } - if self.is_symbol() { - return fmt::Display::fmt("Symbol(..)", f); - } - fmt::Display::fmt("[object]", f) + write!(f, "JsValue({})", self.as_debug_string()) + } +} + +#[cfg(not(feature = "std"))] +impl fmt::Debug for JsValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("JsValue") } } diff --git a/tests/wasm/api.js b/tests/wasm/api.js index 7136b5db2df..4c2dec0e045 100644 --- a/tests/wasm/api.js +++ b/tests/wasm/api.js @@ -41,3 +41,17 @@ exports.js_eq_works = () => { assert.strictEqual(wasm.eq_test(x, x), true); assert.strictEqual(wasm.eq_test1(x), true); }; + +exports.debug_values = () => ([ + null, + undefined, + 0, + 1.0, + true, + [1,2,3], + "string", + {test: "object"}, + [1.0, [2.0, 3.0]], + () => (null), + new Set(), +]); diff --git a/tests/wasm/api.rs b/tests/wasm/api.rs index e7d95133388..b72635151e3 100644 --- a/tests/wasm/api.rs +++ b/tests/wasm/api.rs @@ -8,6 +8,7 @@ extern "C" { fn js_works(); fn js_eq_works(); fn assert_null(v: JsValue); + fn debug_values() -> JsValue; } #[wasm_bindgen_test] @@ -71,7 +72,7 @@ pub fn api_get_false() -> JsValue { #[wasm_bindgen] pub fn api_test_bool(a: &JsValue, b: &JsValue, c: &JsValue) { assert_eq!(a.as_bool(), Some(true)); - assert_eq!(format!("{:?}", a), "true"); + assert_eq!(format!("{:?}", a), "JsValue(true)"); assert_eq!(b.as_bool(), Some(false)); assert_eq!(c.as_bool(), None); } @@ -80,7 +81,7 @@ pub fn api_test_bool(a: &JsValue, b: &JsValue, c: &JsValue) { pub fn api_mk_symbol() -> JsValue { let a = JsValue::symbol(None); assert!(a.is_symbol()); - assert_eq!(format!("{:?}", a), "Symbol(..)"); + assert_eq!(format!("{:?}", a), "JsValue(Symbol)"); return a; } @@ -100,7 +101,7 @@ pub fn api_assert_symbols(a: &JsValue, b: &JsValue) { #[wasm_bindgen] pub fn api_acquire_string(a: &JsValue, b: &JsValue) { assert_eq!(a.as_string().unwrap(), "foo"); - assert_eq!(format!("{:?}", a), "\"foo\""); + assert_eq!(format!("{:?}", a), "JsValue(\"foo\")"); assert_eq!(b.as_string(), None); } @@ -145,3 +146,24 @@ fn memory_accessor_appears_to_work() { .for_each(&mut |val, _, _| v.push(val)); assert_eq!(v, [3, 0, 0, 0]); } + +#[wasm_bindgen_test] +fn debug_output() { + let test_iter = debug_values().dyn_into::().unwrap().values().into_iter(); + let expecteds = vec![ + "JsValue(null)", + "JsValue(undefined)", + "JsValue(0)", + "JsValue(1)", + "JsValue(true)", + "JsValue([1, 2, 3])", + "JsValue(\"string\")", + "JsValue(Object({\"test\":\"object\"}))", + "JsValue([1, [2, 3]])", + "JsValue(Function)", + "JsValue(Set)", + ]; + for (test, expected) in test_iter.zip(expecteds) { + assert_eq!(format!("{:?}", test.unwrap()), expected); + } +}