devtools/actors/inspector/
node.rs1use std::collections::HashMap;
9
10use atomic_refcell::AtomicRefCell;
11use devtools_traits::{
12 AttrModification, DevtoolScriptControlMsg, EventListenerInfo, MatchedRule, NodeInfo,
13 ShadowRootMode,
14};
15use malloc_size_of_derive::MallocSizeOf;
16use serde::Serialize;
17use serde_json::{self, Map, Value};
18use servo_base::generic_channel;
19
20use crate::actor::{
21 Actor, ActorEncode, ActorError, ActorRegistry, DowncastableActorArc, new_actor_name,
22};
23use crate::actors::inspector::walker::WalkerActor;
24use crate::protocol::ClientRequest;
25use crate::{EmptyReplyMsg, StreamId};
26
27const TEXT_NODE: u16 = 3;
30
31const MAX_INLINE_LENGTH: usize = 50;
33
34#[derive(Serialize)]
35struct GetEventListenerInfoReply {
36 from: String,
37 events: Vec<DevtoolsEventListenerInfo>,
38}
39
40#[derive(Serialize)]
41#[serde(rename_all = "camelCase")]
42struct DevtoolsEventListenerInfo {
43 r#type: String,
44 handler: String,
45 origin: String,
46 tags: String,
47 capturing: bool,
48 hide: Value,
50 native: bool,
51 source_actor: String,
52 enabled: bool,
53 is_user_defined: bool,
54 event_listener_info_id: String,
55}
56
57#[derive(Serialize)]
58struct GetUniqueSelectorReply {
59 from: String,
60 value: String,
61}
62
63#[derive(Serialize)]
64struct GetXPathReply {
65 from: String,
66 value: String,
67}
68
69#[derive(Clone, Serialize)]
70struct AttrMsg {
71 name: String,
72 value: String,
73}
74
75#[derive(Clone, Default, Serialize)]
76#[serde(rename_all = "camelCase")]
77pub(crate) struct NodeActorMsg {
78 pub actor: String,
79
80 host: Option<String>,
83 #[serde(rename = "baseURI")]
84 base_uri: String,
85 causes_overflow: bool,
86 container_type: Option<()>,
87 pub display_name: String,
88 display_type: Option<String>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 inline_text_child: Option<Box<NodeActorMsg>>,
91 is_after_pseudo_element: bool,
92 is_anonymous: bool,
93 is_before_pseudo_element: bool,
94 is_direct_shadow_host_child: Option<bool>,
95 is_displayed: bool,
99 #[serde(rename = "isInHTMLDocument")]
100 is_in_html_document: Option<bool>,
101 is_marker_pseudo_element: bool,
102 is_native_anonymous: bool,
103 is_scrollable: bool,
104 is_shadow_host: bool,
105 is_shadow_root: bool,
106 is_top_level_document: bool,
107 node_name: String,
108 node_type: u16,
109 node_value: Option<String>,
110 pub num_children: usize,
111 #[serde(skip_serializing_if = "String::is_empty")]
112 parent: String,
113 shadow_root_mode: Option<String>,
114 traits: HashMap<String, ()>,
115 attrs: Vec<AttrMsg>,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
119 name: Option<String>,
120
121 #[serde(skip_serializing_if = "Option::is_none")]
123 public_id: Option<String>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
127 system_id: Option<String>,
128
129 has_event_listeners: bool,
130}
131
132#[derive(MallocSizeOf)]
133pub(crate) struct NodeActor {
134 name: String,
135 pub walker_name: String,
136 pub style_rules: AtomicRefCell<HashMap<MatchedRule, String>>,
137 node_info: AtomicRefCell<NodeInfo>,
138}
139
140impl Actor for NodeActor {
141 fn name(&self) -> &str {
142 &self.name
143 }
144
145 fn handle_message(
152 &self,
153 request: ClientRequest,
154 registry: &ActorRegistry,
155 msg_type: &str,
156 msg: &Map<String, Value>,
157 _id: StreamId,
158 ) -> Result<(), ActorError> {
159 let browsing_context_actor = registry
160 .find::<WalkerActor>(&self.walker_name)
161 .browsing_context_actor(registry);
162 let script_chan = browsing_context_actor.script_chan();
163 let pipeline_id = browsing_context_actor.pipeline_id();
164
165 match msg_type {
166 "modifyAttributes" => {
167 let mods = msg
168 .get("modifications")
169 .ok_or(ActorError::MissingParameter)?
170 .as_array()
171 .ok_or(ActorError::BadParameterType)?;
172 let modifications: Vec<AttrModification> = mods
173 .iter()
174 .filter_map(|json_mod| {
175 serde_json::from_str(&serde_json::to_string(json_mod).ok()?).ok()
176 })
177 .collect();
178
179 script_chan
180 .send(DevtoolScriptControlMsg::ModifyAttribute(
181 browsing_context_actor.pipeline_id(),
182 registry.actor_to_script(self.name().into()),
183 modifications,
184 ))
185 .map_err(|_| ActorError::Internal)?;
186
187 let reply = EmptyReplyMsg {
188 from: self.name().into(),
189 };
190 request.reply_final(&reply)?
191 },
192 "getEventListenerInfo" => {
193 let target = msg
194 .get("to")
195 .ok_or(ActorError::MissingParameter)?
196 .as_str()
197 .ok_or(ActorError::BadParameterType)?;
198
199 let (tx, rx) = generic_channel::channel().ok_or(ActorError::Internal)?;
200 script_chan
201 .send(DevtoolScriptControlMsg::GetEventListenerInfo(
202 pipeline_id,
203 registry.actor_to_script(target.to_owned()),
204 tx,
205 ))
206 .unwrap();
207 let event_listeners = rx.recv().map_err(|_| ActorError::Internal)?;
208
209 let msg = GetEventListenerInfoReply {
210 from: self.name().into(),
211 events: event_listeners.into_iter().map(From::from).collect(),
212 };
213 request.reply_final(&msg)?
214 },
215 "getUniqueSelector" => {
216 let (tx, rx) = generic_channel::channel().unwrap();
217 script_chan
218 .send(DevtoolScriptControlMsg::GetDocumentElement(pipeline_id, tx))
219 .unwrap();
220 let node_info = rx
221 .recv()
222 .map_err(|_| ActorError::Internal)?
223 .ok_or(ActorError::Internal)?;
224
225 self.update(node_info);
226 let msg = GetUniqueSelectorReply {
227 from: self.name().into(),
228 value: self.node_info.borrow().node_name.to_lowercase(),
229 };
230 request.reply_final(&msg)?
231 },
232 "getXPath" => {
233 let target = msg
234 .get("to")
235 .ok_or(ActorError::MissingParameter)?
236 .as_str()
237 .ok_or(ActorError::BadParameterType)?;
238
239 let (tx, rx) = generic_channel::channel().unwrap();
240 script_chan
241 .send(DevtoolScriptControlMsg::GetXPath(
242 pipeline_id,
243 registry.actor_to_script(target.to_owned()),
244 tx,
245 ))
246 .unwrap();
247
248 let xpath_selector = rx.recv().map_err(|_| ActorError::Internal)?;
249 let msg = GetXPathReply {
250 from: self.name().into(),
251 value: xpath_selector,
252 };
253 request.reply_final(&msg)?
254 },
255
256 _ => return Err(ActorError::UnrecognizedPacketType),
257 };
258 Ok(())
259 }
260}
261
262impl NodeActor {
263 pub fn register_or_update(
264 registry: &ActorRegistry,
265 walker_name: &str,
266 node_info: NodeInfo,
267 ) -> DowncastableActorArc<Self> {
268 let unique_id = &node_info.unique_id;
269
270 if !registry.script_actor_registered(unique_id) {
271 let name = new_actor_name::<Self>();
272 registry.register_script_actor(unique_id.clone(), name.clone());
273
274 let actor = Self {
275 name,
276 walker_name: walker_name.into(),
277 style_rules: AtomicRefCell::new(HashMap::new()),
278 node_info: AtomicRefCell::new(node_info),
279 };
280
281 registry.register(actor).into()
282 } else {
283 let name = registry.script_to_actor(unique_id);
284 let actor = registry.find::<NodeActor>(&name);
285 actor.update(node_info);
286 actor
287 }
288 }
289
290 fn update(&self, node_info: NodeInfo) {
291 *self.node_info.borrow_mut() = node_info;
292 }
293}
294
295impl ActorEncode<NodeActorMsg> for NodeActor {
296 fn encode(&self, registry: &ActorRegistry) -> NodeActorMsg {
297 let node_info = self.node_info.borrow();
298
299 let actor = self.name();
300 let host = node_info.host.as_ref().and_then(|host_id| {
301 if registry.script_actor_registered(host_id) {
302 Some(registry.script_to_actor(host_id))
303 } else {
304 None
305 }
306 });
307
308 let browsing_context = registry
309 .find::<WalkerActor>(&self.walker_name)
310 .browsing_context_actor(registry);
311 let script_chan = browsing_context.script_chan();
312 let pipeline = browsing_context.pipeline_id();
313 let script_id = registry.actor_to_script(actor.into());
314
315 let inline_text_child = (|| {
318 if node_info.num_children != 1 || node_info.node_name == "SLOT" {
320 return None;
321 }
322
323 let (tx, rx) = generic_channel::channel()?;
324 script_chan
325 .send(DevtoolScriptControlMsg::GetChildren(
326 pipeline,
327 script_id.clone(),
328 tx,
329 ))
330 .unwrap();
331 let mut children = rx.recv().ok()??;
332
333 let child = children.pop()?;
334 let node_actor = NodeActor::register_or_update(registry, &self.walker_name, child);
335 let msg = node_actor.encode(registry);
336
337 if msg.node_type != TEXT_NODE {
339 return None;
340 }
341
342 if msg.node_value.clone().unwrap_or_default().len() > MAX_INLINE_LENGTH {
344 return None;
345 }
346
347 Some(Box::new(msg))
348 })();
349
350 NodeActorMsg {
351 actor: actor.into(),
352 host,
353 base_uri: node_info.base_uri.clone(),
354 display_name: node_info.node_name.to_lowercase(),
355 display_type: node_info.display.clone(),
356 inline_text_child,
357 is_displayed: node_info.is_displayed,
358 is_in_html_document: Some(true),
359 is_shadow_host: node_info.is_shadow_host,
360 is_shadow_root: node_info.shadow_root_mode.is_some(),
361 is_top_level_document: node_info.is_top_level_document,
362 node_name: node_info.node_name.clone(),
363 node_type: node_info.node_type,
364 node_value: node_info.node_value.clone(),
365 num_children: node_info.num_children,
366 parent: registry.script_to_actor(&node_info.parent),
367 shadow_root_mode: node_info
368 .shadow_root_mode
369 .as_ref()
370 .map(ShadowRootMode::to_string),
371 attrs: node_info
372 .attrs
373 .iter()
374 .map(|attr| AttrMsg {
375 name: attr.name.clone(),
376 value: attr.value.clone(),
377 })
378 .collect(),
379 name: node_info.doctype_name.clone(),
380 public_id: node_info.doctype_public_identifier.clone(),
381 system_id: node_info.doctype_system_identifier.clone(),
382 has_event_listeners: node_info.has_event_listeners,
383 ..Default::default()
384 }
385 }
386}
387
388impl From<EventListenerInfo> for DevtoolsEventListenerInfo {
389 fn from(event_listener_info: EventListenerInfo) -> Self {
390 Self {
391 r#type: event_listener_info.event_type,
392 handler: "todo".to_owned(),
393 capturing: event_listener_info.capturing,
394 origin: "todo".to_owned(),
395 tags: String::new(),
396 hide: Value::Object(Default::default()),
397 native: false,
398 source_actor: "todo".to_owned(),
399 enabled: true,
400 is_user_defined: false,
401 event_listener_info_id: "todo".to_owned(),
402 }
403 }
404}