boa_engine/value/display/
object.rs1use std::collections::HashSet;
2use std::fmt::{self, Display, Write};
3
4use crate::{
5 JsObject, JsString, JsValue, js_string,
6 property::{DescriptorKind, PropertyKey},
7};
8
9fn print_obj_value_internals(
10 f: &mut fmt::Formatter<'_>,
11 obj: &JsObject,
12 indent: usize,
13 encounters: &mut HashSet<usize>,
14) -> fmt::Result {
15 let object = obj.borrow();
16 write!(f, "{:>indent$}__proto__: ", "")?;
17 if let Some(object) = object.prototype() {
18 log_object_to_internal(
19 f,
20 &object.clone().into(),
21 encounters,
22 indent.wrapping_add(4),
23 true,
24 )?;
25 } else {
26 write!(f, "{}", JsValue::null().display())?;
27 }
28 f.write_char(',')?;
29 f.write_char('\n')
30}
31
32fn print_obj_value_props(
33 f: &mut fmt::Formatter<'_>,
34 obj: &JsObject,
35 indent: usize,
36 encounters: &mut HashSet<usize>,
37 print_internals: bool,
38) -> fmt::Result {
39 let mut keys: Vec<_> = obj
40 .borrow()
41 .properties()
42 .index_property_keys()
43 .map(PropertyKey::from)
44 .collect();
45 keys.extend(obj.borrow().properties().shape.keys());
46
47 let mut first = true;
48 for key in keys {
49 if first {
50 first = false;
51 } else {
52 f.write_char(',')?;
53 f.write_char('\n')?;
54 }
55 let val = obj
56 .borrow()
57 .properties()
58 .get(&key)
59 .expect("There should be a value");
60
61 write!(f, "{:>width$}{}: ", "", key, width = indent)?;
62 if val.is_data_descriptor() {
63 let v = val.expect_value();
64 log_object_to_internal(f, v, encounters, indent.wrapping_add(4), print_internals)?;
65 } else {
66 let display = match (val.set().is_some(), val.get().is_some()) {
67 (true, true) => "Getter & Setter",
68 (true, false) => "Setter",
69 (false, true) => "Getter",
70 _ => "No Getter/Setter",
71 };
72 write!(f, "{display}")?;
73 }
74 }
75 f.write_char('\n')?;
76 Ok(())
77}
78
79pub(super) fn log_object_to_internal(
80 f: &mut fmt::Formatter<'_>,
81 data: &JsValue,
82 encounters: &mut HashSet<usize>,
83 indent: usize,
84 print_internals: bool,
85) -> fmt::Result {
86 if let Some(v) = data.as_object() {
87 let addr = std::ptr::from_ref(v.as_ref()).addr();
89
90 if encounters.contains(&addr) {
93 return f.write_str("[Cycle]");
94 }
95
96 encounters.insert(addr);
98
99 if v.is::<crate::builtins::Array>() {
100 encounters.remove(&addr);
101 return super::array::log_array_to(f, &v, print_internals, false);
102 }
103
104 if v.is::<crate::builtins::typed_array::TypedArray>() {
105 encounters.remove(&addr);
106 return super::typed_array::log_typed_array(f, &v, true, print_internals);
107 }
108
109 let constructor_name = get_constructor_name_of(&v);
110 if let Some(name) = constructor_name {
111 write!(f, "{} ", name.to_std_string_lossy())?;
112 }
113 f.write_str("{\n")?;
114
115 if print_internals {
116 print_obj_value_internals(f, &v, indent, encounters)?;
117 }
118 print_obj_value_props(f, &v, indent, encounters, print_internals)?;
119 write!(f, "{:>indent$}}}", "", indent = indent.saturating_sub(4))?;
120
121 encounters.remove(&addr);
124 Ok(())
125 } else {
126 super::value::log_value_to(f, data, print_internals, false)
128 }
129}
130
131fn get_constructor_name_of(obj: &JsObject) -> Option<JsString> {
135 let prototype = obj.prototype()?;
136
137 prototype.prototype()?;
142
143 let constructor_property = prototype
144 .borrow()
145 .properties()
146 .get(&PropertyKey::from(js_string!("constructor")))?;
147 let constructor = constructor_property.value()?;
148
149 let name = constructor
150 .as_object()?
151 .borrow()
152 .properties()
153 .get(&PropertyKey::from(js_string!("name")))?
154 .value()?
155 .as_string()?;
156
157 Some(name)
158}
159
160pub(super) fn log_plain_object_compact(
161 f: &mut fmt::Formatter<'_>,
162 obj: &JsObject,
163 depth: u32,
164 print_internals: bool,
165 encounters: &mut HashSet<usize>,
166) -> fmt::Result {
167 let addr = std::ptr::from_ref(obj.as_ref()).addr();
168 if encounters.contains(&addr) {
169 return f.write_str("[Circular *]");
170 }
171 encounters.insert(addr);
172
173 let mut keys: Vec<_> = obj
174 .borrow()
175 .properties()
176 .index_property_keys()
177 .map(PropertyKey::from)
178 .collect();
179 keys.extend(obj.borrow().properties().shape.keys());
180
181 if keys.is_empty() {
182 encounters.remove(&addr);
183 return f.write_str("{}");
184 }
185
186 f.write_str("{ ")?;
187 let mut first = true;
188 for key in &keys {
189 if first {
190 first = false;
191 } else {
192 f.write_str(", ")?;
193 }
194 write!(f, "{key}: ")?;
195
196 if let Some(val) = obj.borrow().properties().get(key) {
197 match val.kind() {
198 DescriptorKind::Data { value, .. } => {
199 if let Some(value) = value {
200 super::value::log_value_compact(
201 f,
202 value,
203 depth + 1,
204 print_internals,
205 encounters,
206 )?;
207 } else {
208 f.write_str("undefined")?;
209 }
210 }
211 DescriptorKind::Accessor { get, set } => {
212 let display = match (get.is_some(), set.is_some()) {
213 (true, true) => "[Getter/Setter]",
214 (true, false) => "[Getter]",
215 (false, true) => "[Setter]",
216 _ => "[No Getter/Setter]",
217 };
218 f.write_str(display)?;
219 }
220 DescriptorKind::Generic => {
221 f.write_str("undefined")?;
222 }
223 }
224 }
225 }
226 f.write_str(" }")?;
227
228 encounters.remove(&addr);
229 Ok(())
230}
231
232impl JsValue {
233 #[must_use]
235 pub fn display_obj(&self, print_internals: bool) -> String {
236 struct DisplayObj<'a>(&'a JsValue, bool);
237 impl Display for DisplayObj<'_> {
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 log_object_to_internal(f, self.0, &mut HashSet::new(), 4, self.1)
240 }
241 }
242
243 DisplayObj(self, print_internals).to_string()
244 }
245}