diff --git a/components/script/dom/console.rs b/components/script/dom/console.rs index 3d00194d2cf..945228e225a 100644 --- a/components/script/dom/console.rs +++ b/components/script/dom/console.rs @@ -3,19 +3,23 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::convert::TryFrom; -use std::{io, ptr}; +use std::ptr::{self, NonNull}; +use std::{io, slice}; use devtools_traits::{ ConsoleMessage, ConsoleMessageArgument, ConsoleMessageBuilder, LogLevel, ScriptToDevtoolsControlMsg, StackFrame, }; use js::jsapi::{self, ESClass, PropertyDescriptor}; -use js::jsval::UndefinedValue; +use js::jsval::{Int32Value, UndefinedValue}; use js::rust::wrappers::{ GetBuiltinClass, GetPropertyKeys, JS_GetOwnPropertyDescriptorById, JS_GetPropertyById, - JS_IdToValue, JS_ValueToSource, + JS_IdToValue, JS_Stringify, JS_ValueToSource, }; -use js::rust::{describe_scripted_caller, CapturedJSStack, HandleValue, IdVector}; +use js::rust::{ + describe_scripted_caller, CapturedJSStack, HandleObject, HandleValue, IdVector, ToString, +}; +use script_bindings::conversions::get_dom_class; use crate::dom::bindings::codegen::Bindings::ConsoleBinding::consoleMethods; use crate::dom::bindings::conversions::jsstring_to_str; @@ -147,10 +151,10 @@ fn console_argument_from_handle_value( #[allow(unsafe_code)] fn stringify_handle_value(message: HandleValue) -> DOMString { - let cx = *GlobalScope::get_cx(); + let cx = GlobalScope::get_cx(); unsafe { if message.is_string() { - return jsstring_to_str(cx, std::ptr::NonNull::new(message.to_string()).unwrap()); + return jsstring_to_str(*cx, std::ptr::NonNull::new(message.to_string()).unwrap()); } unsafe fn stringify_object_from_handle_value( cx: *mut jsapi::JSContext, @@ -213,7 +217,8 @@ fn stringify_handle_value(message: HandleValue) -> DOMString { explicit_keys = false; } } - let value_string = stringify_inner(cx, property.handle(), parents.clone()); + let value_string = + stringify_inner(JSContext::from_ptr(cx), property.handle(), parents.clone()); if explicit_keys { let key = if id.is_string() || id.is_symbol() || id.is_int() { rooted!(in(cx) let mut key_value = UndefinedValue()); @@ -239,11 +244,7 @@ fn stringify_handle_value(message: HandleValue) -> DOMString { DOMString::from(format!("{{{}}}", itertools::join(props, ", "))) } } - unsafe fn stringify_inner( - cx: *mut jsapi::JSContext, - value: HandleValue, - mut parents: Vec, - ) -> DOMString { + fn stringify_inner(cx: JSContext, value: HandleValue, mut parents: Vec) -> DOMString { if parents.len() >= MAX_LOG_DEPTH { return DOMString::from("..."); } @@ -255,15 +256,73 @@ fn stringify_handle_value(message: HandleValue) -> DOMString { // This produces a better value than "(void 0)" from JS_ValueToSource. return DOMString::from("undefined"); } else if !value.is_object() { - return handle_value_to_string(cx, value); + return unsafe { handle_value_to_string(*cx, value) }; } parents.push(value_bits); - stringify_object_from_handle_value(cx, value, parents) + + if value.is_object() { + if let Some(repr) = maybe_stringify_dom_object(cx, value) { + return repr; + } + } + unsafe { stringify_object_from_handle_value(*cx, value, parents) } } stringify_inner(cx, message, Vec::new()) } } +#[allow(unsafe_code)] +fn maybe_stringify_dom_object(cx: JSContext, value: HandleValue) -> Option { + // The standard object serialization is not effective for DOM objects, + // since their properties generally live on the prototype object. + // Instead, fall back to the output of JSON.stringify combined + // with the class name extracted from the output of toString(). + rooted!(in(*cx) let obj = value.to_object()); + let is_dom_class = unsafe { get_dom_class(obj.get()).is_ok() }; + if !is_dom_class { + return None; + } + rooted!(in(*cx) let class_name = unsafe { ToString(*cx, value) }); + let Some(class_name) = NonNull::new(class_name.get()) else { + return Some("".into()); + }; + let class_name = unsafe { + jsstring_to_str(*cx, class_name) + .replace("[object ", "") + .replace("]", "") + }; + let mut repr = format!("{} ", class_name); + rooted!(in(*cx) let mut value = value.get()); + + #[allow(unsafe_code)] + unsafe extern "C" fn stringified( + string: *const u16, + len: u32, + data: *mut std::ffi::c_void, + ) -> bool { + let s = data as *mut String; + let string_chars = slice::from_raw_parts(string, len as usize); + (*s).push_str(&String::from_utf16_lossy(string_chars)); + true + } + + rooted!(in(*cx) let space = Int32Value(2)); + let stringify_result = unsafe { + JS_Stringify( + *cx, + value.handle_mut(), + HandleObject::null(), + space.handle(), + Some(stringified), + &mut repr as *mut String as *mut _, + ) + }; + if !stringify_result { + return Some("".into()); + } + Some(repr.into()) +} + fn stringify_handle_values(messages: &[HandleValue]) -> DOMString { DOMString::from(itertools::join( messages.iter().copied().map(stringify_handle_value),