/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/devtools/actors/object.rs
370 строк
12 KB
atbrakhi
devtools: keep object actor identity stable across grips (#46201)
02 июл 2026, 17:41
Не верифицирован
02 июл 2026, 17:41
fe17152
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::collections::HashMap; use atomic_refcell::AtomicRefCell; use devtools_traits::{DebuggerValue, PropertyDescriptor}; use malloc_size_of_derive::MallocSizeOf; use serde::Serialize; use serde_json::{Map, Value}; use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry, new_actor_name}; use crate::actors::property_iterator::{PropertyIteratorActor, PropertyIteratorEntry}; use crate::actors::symbol_iterator::SymbolIteratorActor; use crate::protocol::ClientRequest; use crate::{StreamId, debugger_value_to_json}; #[derive(Serialize)] #[serde(rename_all = "camelCase")] enum EnumIteratorType { PropertyIterator, SymbolIterator, } #[derive(Serialize)] struct EnumIterator { actor: String, #[serde(rename = "type")] type_: EnumIteratorType, count: u32, } #[derive(Serialize)] struct EnumReply { from: String, iterator: EnumIterator, } #[derive(Serialize)] struct PrototypeReply { from: String, prototype: ObjectActorMsg, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ObjectPreview { pub kind: String, #[serde(skip_serializing_if = "Option::is_none")] pub size: Option<u32>, #[serde(skip_serializing_if = "Option::is_none")] pub entries: Option<Vec<(Value, Value)>>, #[serde(skip_serializing_if = "Option::is_none")] pub own_properties: Option<HashMap<String, ObjectPropertyDescriptor>>, #[serde(skip_serializing_if = "Option::is_none")] pub own_properties_length: Option<u32>, #[serde(flatten)] #[serde(skip_serializing_if = "Option::is_none")] pub function: Option<FunctionPreview>, #[serde(skip_serializing_if = "Option::is_none")] pub length: Option<u32>, #[serde(skip_serializing_if = "Option::is_none")] pub items: Option<Vec<Value>>, } #[derive(Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct FunctionPreview { #[serde(skip_serializing_if = "Option::is_none")] pub name: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub display_name: Option<String>, pub parameter_names: Vec<String>, #[serde(skip_serializing_if = "Option::is_none")] pub is_async: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub is_generator: Option<bool>, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ObjectActorMsg { actor: String, #[serde(rename = "type")] type_: String, class: String, #[serde(skip_serializing_if = "Option::is_none")] own_property_length: Option<u32>, extensible: bool, frozen: bool, sealed: bool, #[serde(flatten)] #[serde(skip_serializing_if = "Option::is_none")] function: Option<FunctionPreview>, #[serde(skip_serializing_if = "Option::is_none")] preview: Option<ObjectPreview>, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ObjectPropertyDescriptor { pub value: Value, #[serde(skip_serializing_if = "Option::is_none")] pub configurable: Option<bool>, pub enumerable: bool, #[serde(skip_serializing_if = "Option::is_none")] pub writable: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub is_accessor: Option<bool>, } impl ObjectPropertyDescriptor { pub(crate) fn from_property_descriptor( registry: &ActorRegistry, prop: &PropertyDescriptor, ) -> Self { Self { value: debugger_value_to_json(registry, prop.value.clone()), configurable: Some(prop.configurable), enumerable: prop.enumerable, writable: Some(prop.writable), is_accessor: Some(prop.is_accessor), } } } #[derive(Clone, MallocSizeOf)] struct ObjectActorData { class: String, own_property_length: Option<u32>, preview: Option<devtools_traits::ObjectPreview>, } #[derive(MallocSizeOf)] pub(crate) struct ObjectActor { name: String, data: AtomicRefCell<ObjectActorData>, } impl Actor for ObjectActor { fn name(&self) -> &str { &self.name } // https://searchfox.org/firefox-main/source/devtools/shared/specs/object.js fn handle_message( &self, request: ClientRequest, registry: &ActorRegistry, msg_type: &str, _msg: &Map<String, Value>, _id: StreamId, ) -> Result<(), ActorError> { match msg_type { "enumProperties" => { let preview = self.data.borrow().preview.clone(); let properties = preview.as_ref().map_or_else(Vec::new, |preview| { if preview.kind == "ArrayLike" { // For arrays, convert items to indexed properties // <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor#description> let mut props: Vec<PropertyDescriptor> = preview .items .as_ref() .map(|items| { items .iter() .enumerate() .map(|(index, value)| PropertyDescriptor { name: index.to_string(), value: value.clone(), configurable: true, enumerable: true, writable: true, is_accessor: false, }) .collect() }) .unwrap_or_default(); // Add length property if let Some(length) = preview.array_length { // <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length#value> props.push(PropertyDescriptor { name: "length".to_string(), value: DebuggerValue::NumberValue(length as f64), configurable: false, enumerable: false, writable: true, is_accessor: false, }); } props } else { preview.own_properties.clone().unwrap_or_default() } }); let entries = properties .into_iter() .map(PropertyIteratorEntry::Property) .collect(); self.reply_property_iterator(request, registry, entries)? }, "enumEntries" => { let mut entries = Vec::new(); let preview = self.data.borrow().preview.clone(); if let Some(preview) = preview && let Some(map_entries) = preview.entries { for (key, value) in map_entries { entries.push(PropertyIteratorEntry::MapEntry(key, value)); } } self.reply_property_iterator(request, registry, entries)? }, "enumSymbols" => { let symbol_iterator_actor = SymbolIteratorActor::register(registry); let msg = EnumReply { from: self.name().into(), iterator: EnumIterator { actor: symbol_iterator_actor.name().into(), type_: EnumIteratorType::SymbolIterator, count: 0, }, }; request.reply_final(&msg)? }, "prototype" => { let msg = PrototypeReply { from: self.name().into(), prototype: self.encode(registry), }; request.reply_final(&msg)? }, _ => return Err(ActorError::UnrecognizedPacketType), }; Ok(()) } } impl ObjectActor { fn reply_property_iterator( &self, request: ClientRequest, registry: &ActorRegistry, entries: Vec<PropertyIteratorEntry>, ) -> Result<(), ActorError> { let property_iterator_actor = PropertyIteratorActor::register(registry, entries); let msg = EnumReply { from: self.name().into(), iterator: EnumIterator { actor: property_iterator_actor.name().into(), type_: EnumIteratorType::PropertyIterator, count: property_iterator_actor.count(), }, }; request.reply_final(&msg) } pub fn register( registry: &ActorRegistry, actor_name: Option<String>, class: String, own_property_length: Option<u32>, preview: Option<devtools_traits::ObjectPreview>, ) -> String { if let Some(name) = actor_name { let actor = registry.find::<Self>(&name); let mut data = actor.data.borrow_mut(); data.class = class; data.own_property_length = own_property_length; if preview.is_some() || data.preview.is_none() { data.preview = preview; } return name; } let name = new_actor_name::<Self>(); let actor = ObjectActor { name: name.clone(), data: AtomicRefCell::new(ObjectActorData { class, own_property_length, preview, }), }; registry.register(actor); name } } impl ActorEncode<ObjectActorMsg> for ObjectActor { fn encode(&self, registry: &ActorRegistry) -> ObjectActorMsg { let data = self.data.borrow().clone(); let mut msg = ObjectActorMsg { actor: self.name().into(), type_: "object".into(), class: data.class.clone(), extensible: true, frozen: false, sealed: false, function: None, preview: None, own_property_length: data.own_property_length, }; // Build preview // <https://searchfox.org/firefox-main/source/devtools/server/actors/object/previewers.js#849> let Some(preview) = data.preview.clone() else { return msg; }; let function = preview.function.map(|function| FunctionPreview { name: function.name.clone(), display_name: function.display_name.clone(), parameter_names: function.parameter_names.clone(), is_async: function.is_async, is_generator: function.is_generator, }); if data.class == "Function" { msg.function = function.clone(); } let preview = ObjectPreview { kind: preview.kind.clone(), size: preview.size, entries: preview.entries.map(|entries| { entries .iter() .map(|(key, value)| { ( debugger_value_to_json(registry, key.clone()), debugger_value_to_json(registry, value.clone()), ) }) .collect() }), own_properties: preview.own_properties.map(|own_properties| { own_properties .iter() .map(|prop| { ( prop.name.clone(), ObjectPropertyDescriptor::from_property_descriptor(registry, prop), ) }) .collect() }), own_properties_length: preview.own_properties_length, function, length: preview.array_length, items: preview.items.map(|items| { items .iter() .map(|item| debugger_value_to_json(registry, item.clone())) .collect() }), }; msg.preview = Some(preview); msg } }