devtools/actors/
object.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use devtools_traits::{DebuggerValue, ObjectPreview, PropertyDescriptor};
6use malloc_size_of_derive::MallocSizeOf;
7use serde::Serialize;
8use serde_json::{Map, Number, Value};
9
10use crate::StreamId;
11use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
12use crate::actors::property_iterator::PropertyIteratorActor;
13use crate::protocol::ClientRequest;
14
15#[derive(Serialize)]
16#[serde(rename_all = "camelCase")]
17enum EnumIteratorType {
18    PropertyIterator,
19    SymbolIterator,
20}
21
22#[derive(Serialize)]
23struct EnumIterator {
24    actor: String,
25    #[serde(rename = "type")]
26    type_: EnumIteratorType,
27    count: u32,
28}
29
30#[derive(Serialize)]
31struct EnumReply {
32    from: String,
33    iterator: EnumIterator,
34}
35
36#[derive(Serialize)]
37struct PrototypeReply {
38    from: String,
39    prototype: Value,
40}
41
42#[derive(Serialize)]
43#[serde(rename_all = "camelCase")]
44pub(crate) struct ObjectActorMsg {
45    actor: String,
46    #[serde(rename = "type")]
47    type_: String,
48    class: String,
49    own_property_length: i32,
50    extensible: bool,
51    frozen: bool,
52    sealed: bool,
53    is_error: bool,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    preview: Option<ObjectPreview>,
56}
57
58#[derive(Serialize)]
59pub(crate) struct ObjectPropertyDescriptor {
60    pub configurable: bool,
61    pub enumerable: bool,
62    pub writable: bool,
63    pub value: Value,
64}
65
66impl ObjectPropertyDescriptor {
67    pub(crate) fn from_property_descriptor(
68        registry: &ActorRegistry,
69        prop: &PropertyDescriptor,
70    ) -> Self {
71        Self {
72            configurable: prop.configurable,
73            enumerable: prop.enumerable,
74            writable: prop.writable,
75            value: debugger_value_to_json(registry, prop.value.clone()),
76        }
77    }
78}
79
80/// <https://searchfox.org/mozilla-central/source/devtools/server/actors/object/utils.js#148>
81pub(crate) fn debugger_value_to_json(registry: &ActorRegistry, value: DebuggerValue) -> Value {
82    let mut v = Map::new();
83    match value {
84        DebuggerValue::VoidValue => {
85            v.insert("type".to_owned(), Value::String("undefined".to_owned()));
86            Value::Object(v)
87        },
88        DebuggerValue::NullValue => {
89            v.insert("type".to_owned(), Value::String("null".to_owned()));
90            Value::Object(v)
91        },
92        DebuggerValue::BooleanValue(boolean) => Value::Bool(boolean),
93        DebuggerValue::NumberValue(val) => {
94            if val.is_nan() {
95                v.insert("type".to_owned(), Value::String("NaN".to_owned()));
96                Value::Object(v)
97            } else if val.is_infinite() {
98                if val < 0. {
99                    v.insert("type".to_owned(), Value::String("-Infinity".to_owned()));
100                } else {
101                    v.insert("type".to_owned(), Value::String("Infinity".to_owned()));
102                }
103                Value::Object(v)
104            } else if val == 0. && val.is_sign_negative() {
105                v.insert("type".to_owned(), Value::String("-0".to_owned()));
106                Value::Object(v)
107            } else {
108                Value::Number(Number::from_f64(val).unwrap())
109            }
110        },
111        DebuggerValue::StringValue(str) => Value::String(str),
112        DebuggerValue::ObjectValue {
113            uuid,
114            class,
115            preview,
116            ..
117        } => {
118            let object_name = ObjectActor::register(registry, Some(uuid), class, preview);
119            let object_msg = registry.encode::<ObjectActor, _>(&object_name);
120            let value = serde_json::to_value(object_msg).unwrap_or_default();
121            Value::Object(value.as_object().cloned().unwrap_or_default())
122        },
123    }
124}
125
126#[derive(MallocSizeOf)]
127pub(crate) struct ObjectActor {
128    name: String,
129    _uuid: Option<String>,
130    class: String,
131    preview: Option<ObjectPreview>,
132}
133
134impl Actor for ObjectActor {
135    fn name(&self) -> String {
136        self.name.clone()
137    }
138
139    // https://searchfox.org/firefox-main/source/devtools/shared/specs/object.js
140    fn handle_message(
141        &self,
142        request: ClientRequest,
143        registry: &ActorRegistry,
144        msg_type: &str,
145        _msg: &Map<String, Value>,
146        _id: StreamId,
147    ) -> Result<(), ActorError> {
148        match msg_type {
149            "enumProperties" => {
150                let properties = self
151                    .preview
152                    .as_ref()
153                    .and_then(|preview| preview.own_properties.clone())
154                    .unwrap_or_default();
155                let property_iterator_name = PropertyIteratorActor::register(registry, properties);
156                let property_iterator_actor =
157                    registry.find::<PropertyIteratorActor>(&property_iterator_name);
158                let count = property_iterator_actor.count();
159                let msg = EnumReply {
160                    from: self.name(),
161                    iterator: EnumIterator {
162                        actor: property_iterator_name,
163                        type_: EnumIteratorType::PropertyIterator,
164                        count,
165                    },
166                };
167
168                request.reply_final(&msg)?
169            },
170
171            "enumSymbols" => {
172                let symbol_iterator_actor = SymbolIteratorActor {
173                    name: registry.new_name::<SymbolIteratorActor>(),
174                };
175                let msg = EnumReply {
176                    from: self.name(),
177                    iterator: EnumIterator {
178                        actor: symbol_iterator_actor.name(),
179                        type_: EnumIteratorType::SymbolIterator,
180                        count: 0,
181                    },
182                };
183                registry.register(symbol_iterator_actor);
184                request.reply_final(&msg)?
185            },
186
187            "prototype" => {
188                let msg = PrototypeReply {
189                    from: self.name(),
190                    prototype: self.encode(registry),
191                };
192                request.reply_final(&msg)?
193            },
194
195            _ => return Err(ActorError::UnrecognizedPacketType),
196        };
197        Ok(())
198    }
199}
200
201impl ObjectActor {
202    pub fn register(
203        registry: &ActorRegistry,
204        uuid: Option<String>,
205        class: String,
206        preview: Option<ObjectPreview>,
207    ) -> String {
208        let Some(uuid) = uuid else {
209            let name = registry.new_name::<Self>();
210            let actor = ObjectActor {
211                name: name.clone(),
212                _uuid: None,
213                class,
214                preview,
215            };
216            registry.register(actor);
217            return name;
218        };
219        if !registry.script_actor_registered(uuid.clone()) {
220            let name = registry.new_name::<Self>();
221            let actor = ObjectActor {
222                name: name.clone(),
223                _uuid: Some(uuid.clone()),
224                class,
225                preview,
226            };
227
228            registry.register_script_actor(uuid, name.clone());
229            registry.register(actor);
230
231            name
232        } else {
233            registry.script_to_actor(uuid)
234        }
235    }
236}
237
238impl ActorEncode<Value> for ObjectActor {
239    fn encode(&self, registry: &ActorRegistry) -> Value {
240        // TODO: convert to a serialize struct instead
241        let mut m = Map::new();
242        m.insert("type".to_owned(), Value::String("object".to_owned()));
243        m.insert("class".to_owned(), Value::String(self.class.clone()));
244        m.insert("actor".to_owned(), Value::String(self.name()));
245        m.insert("extensible".to_owned(), Value::Bool(true));
246        m.insert("frozen".to_owned(), Value::Bool(false));
247        m.insert("sealed".to_owned(), Value::Bool(false));
248
249        // Build preview
250        // <https://searchfox.org/firefox-main/source/devtools/server/actors/object/previewers.js#849>
251        let Some(preview) = self.preview.clone() else {
252            return Value::Object(m);
253        };
254        let mut preview_map = Map::new();
255
256        if preview.kind == "ArrayLike" {
257            if let Some(length) = preview.array_length {
258                preview_map.insert("length".to_owned(), Value::Number(length.into()));
259            }
260        } else {
261            if let Some(ref props) = preview.own_properties {
262                let mut own_props_map = Map::new();
263                for prop in props {
264                    let descriptor = serde_json::to_value(
265                        ObjectPropertyDescriptor::from_property_descriptor(registry, prop),
266                    )
267                    .unwrap();
268                    own_props_map.insert(prop.name.clone(), descriptor);
269                }
270                preview_map.insert("ownProperties".to_owned(), Value::Object(own_props_map));
271            }
272
273            if let Some(length) = preview.own_properties_length {
274                preview_map.insert(
275                    "ownPropertiesLength".to_owned(),
276                    Value::Number(length.into()),
277                );
278                m.insert("ownPropertyLength".to_owned(), Value::Number(length.into()));
279            }
280        }
281        preview_map.insert("kind".to_owned(), Value::String(preview.kind));
282
283        // Function-specific metadata
284        if let Some(function) = preview.function {
285            if let Some(name) = function.name {
286                m.insert("name".to_owned(), Value::String(name));
287            }
288            if let Some(display_name) = function.display_name {
289                m.insert("displayName".to_owned(), Value::String(display_name));
290            }
291            m.insert(
292                "parameterNames".to_owned(),
293                Value::Array(
294                    function
295                        .parameter_names
296                        .into_iter()
297                        .map(Value::String)
298                        .collect(),
299                ),
300            );
301            m.insert("isAsync".to_owned(), Value::Bool(function.is_async));
302            m.insert("isGenerator".to_owned(), Value::Bool(function.is_generator));
303        }
304
305        m.insert("preview".to_owned(), Value::Object(preview_map));
306
307        Value::Object(m)
308    }
309}
310
311#[derive(MallocSizeOf)]
312struct SymbolIteratorActor {
313    name: String,
314}
315
316impl Actor for SymbolIteratorActor {
317    fn name(&self) -> String {
318        self.name.clone()
319    }
320}