devtools/actors/inspector/
node.rs1use std::cell::RefCell;
9use std::collections::HashMap;
10
11use base::id::PipelineId;
12use devtools_traits::{DevtoolScriptControlMsg, NodeInfo, ShadowRootMode};
13use ipc_channel::ipc::{self, IpcSender};
14use serde::Serialize;
15use serde_json::{self, Map, Value};
16
17use crate::actor::{Actor, ActorError, ActorRegistry};
18use crate::actors::inspector::walker::WalkerActor;
19use crate::protocol::ClientRequest;
20use crate::{EmptyReplyMsg, StreamId};
21
22const TEXT_NODE: u16 = 3;
25
26const MAX_INLINE_LENGTH: usize = 50;
28
29#[derive(Serialize)]
30struct GetUniqueSelectorReply {
31 from: String,
32 value: String,
33}
34
35#[derive(Serialize)]
36struct GetXPathReply {
37 from: String,
38 value: String,
39}
40
41#[derive(Clone, Serialize)]
42struct AttrMsg {
43 name: String,
44 value: String,
45}
46
47#[derive(Clone, Serialize)]
48#[serde(rename_all = "camelCase")]
49pub struct NodeActorMsg {
50 pub actor: String,
51
52 host: Option<String>,
55 #[serde(rename = "baseURI")]
56 base_uri: String,
57 causes_overflow: bool,
58 container_type: Option<()>,
59 pub display_name: String,
60 display_type: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 inline_text_child: Option<Box<NodeActorMsg>>,
63 is_after_pseudo_element: bool,
64 is_anonymous: bool,
65 is_before_pseudo_element: bool,
66 is_direct_shadow_host_child: Option<bool>,
67 is_displayed: bool,
71 #[serde(rename = "isInHTMLDocument")]
72 is_in_html_document: Option<bool>,
73 is_marker_pseudo_element: bool,
74 is_native_anonymous: bool,
75 is_scrollable: bool,
76 is_shadow_host: bool,
77 is_shadow_root: bool,
78 is_top_level_document: bool,
79 node_name: String,
80 node_type: u16,
81 node_value: Option<String>,
82 pub num_children: usize,
83 #[serde(skip_serializing_if = "String::is_empty")]
84 parent: String,
85 shadow_root_mode: Option<String>,
86 traits: HashMap<String, ()>,
87 attrs: Vec<AttrMsg>,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
91 name: Option<String>,
92
93 #[serde(skip_serializing_if = "Option::is_none")]
95 public_id: Option<String>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
99 system_id: Option<String>,
100}
101
102pub struct NodeActor {
103 name: String,
104 pub script_chan: IpcSender<DevtoolScriptControlMsg>,
105 pub pipeline: PipelineId,
106 pub walker: String,
107 pub style_rules: RefCell<HashMap<(String, usize), String>>,
108}
109
110impl Actor for NodeActor {
111 fn name(&self) -> String {
112 self.name.clone()
113 }
114
115 fn handle_message(
122 &self,
123 mut request: ClientRequest,
124 registry: &ActorRegistry,
125 msg_type: &str,
126 msg: &Map<String, Value>,
127 _id: StreamId,
128 ) -> Result<(), ActorError> {
129 match msg_type {
130 "modifyAttributes" => {
131 let mods = msg
132 .get("modifications")
133 .ok_or(ActorError::MissingParameter)?
134 .as_array()
135 .ok_or(ActorError::BadParameterType)?;
136 let modifications: Vec<_> = mods
137 .iter()
138 .filter_map(|json_mod| {
139 serde_json::from_str(&serde_json::to_string(json_mod).ok()?).ok()
140 })
141 .collect();
142
143 let walker = registry.find::<WalkerActor>(&self.walker);
144 walker.new_mutations(&mut request, &self.name, &modifications);
145
146 self.script_chan
147 .send(DevtoolScriptControlMsg::ModifyAttribute(
148 self.pipeline,
149 registry.actor_to_script(self.name()),
150 modifications,
151 ))
152 .map_err(|_| ActorError::Internal)?;
153
154 let reply = EmptyReplyMsg { from: self.name() };
155 request.reply_final(&reply)?
156 },
157
158 "getUniqueSelector" => {
159 let (tx, rx) = ipc::channel().unwrap();
160 self.script_chan
161 .send(DevtoolScriptControlMsg::GetDocumentElement(
162 self.pipeline,
163 tx,
164 ))
165 .unwrap();
166 let doc_elem_info = rx
167 .recv()
168 .map_err(|_| ActorError::Internal)?
169 .ok_or(ActorError::Internal)?;
170 let node = doc_elem_info.encode(
171 registry,
172 self.script_chan.clone(),
173 self.pipeline,
174 self.walker.clone(),
175 );
176
177 let msg = GetUniqueSelectorReply {
178 from: self.name(),
179 value: node.display_name,
180 };
181 request.reply_final(&msg)?
182 },
183 "getXPath" => {
184 let target = msg
185 .get("to")
186 .ok_or(ActorError::MissingParameter)?
187 .as_str()
188 .ok_or(ActorError::BadParameterType)?;
189
190 let (tx, rx) = ipc::channel().unwrap();
191 self.script_chan
192 .send(DevtoolScriptControlMsg::GetXPath(
193 self.pipeline,
194 registry.actor_to_script(target.to_owned()),
195 tx,
196 ))
197 .unwrap();
198
199 let xpath_selector = rx.recv().map_err(|_| ActorError::Internal)?;
200 let msg = GetXPathReply {
201 from: self.name(),
202 value: xpath_selector,
203 };
204 request.reply_final(&msg)?
205 },
206
207 _ => return Err(ActorError::UnrecognizedPacketType),
208 };
209 Ok(())
210 }
211}
212
213pub trait NodeInfoToProtocol {
214 fn encode(
215 self,
216 actors: &ActorRegistry,
217 script_chan: IpcSender<DevtoolScriptControlMsg>,
218 pipeline: PipelineId,
219 walker: String,
220 ) -> NodeActorMsg;
221}
222
223impl NodeInfoToProtocol for NodeInfo {
224 fn encode(
225 self,
226 actors: &ActorRegistry,
227 script_chan: IpcSender<DevtoolScriptControlMsg>,
228 pipeline: PipelineId,
229 walker: String,
230 ) -> NodeActorMsg {
231 let get_or_register_node_actor = |id: &str| {
232 if !actors.script_actor_registered(id.to_string()) {
233 let name = actors.new_name("node");
234 actors.register_script_actor(id.to_string(), name.clone());
235
236 let node_actor = NodeActor {
237 name: name.clone(),
238 script_chan: script_chan.clone(),
239 pipeline,
240 walker: walker.clone(),
241 style_rules: RefCell::new(HashMap::new()),
242 };
243 actors.register_later(Box::new(node_actor));
244 name
245 } else {
246 actors.script_to_actor(id.to_string())
247 }
248 };
249
250 let actor = get_or_register_node_actor(&self.unique_id);
251 let host = self
252 .host
253 .as_ref()
254 .map(|host_id| get_or_register_node_actor(host_id));
255
256 let name = actors.actor_to_script(actor.clone());
257
258 let inline_text_child = (|| {
261 if self.num_children != 1 || self.node_name == "SLOT" {
263 return None;
264 }
265
266 let (tx, rx) = ipc::channel().ok()?;
267 script_chan
268 .send(DevtoolScriptControlMsg::GetChildren(
269 pipeline,
270 name.clone(),
271 tx,
272 ))
273 .unwrap();
274 let mut children = rx.recv().ok()??;
275
276 let child = children.pop()?;
277 let msg = child.encode(actors, script_chan.clone(), pipeline, walker);
278
279 if msg.node_type != TEXT_NODE {
281 return None;
282 }
283
284 if msg.node_value.clone().unwrap_or_default().len() > MAX_INLINE_LENGTH {
286 return None;
287 }
288
289 Some(Box::new(msg))
290 })();
291
292 NodeActorMsg {
293 actor,
294 host,
295 base_uri: self.base_uri,
296 causes_overflow: false,
297 container_type: None,
298 display_name: self.node_name.clone().to_lowercase(),
299 display_type: self.display,
300 inline_text_child,
301 is_after_pseudo_element: false,
302 is_anonymous: false,
303 is_before_pseudo_element: false,
304 is_direct_shadow_host_child: None,
305 is_displayed: self.is_displayed,
306 is_in_html_document: Some(true),
307 is_marker_pseudo_element: false,
308 is_native_anonymous: false,
309 is_scrollable: false,
310 is_shadow_host: self.is_shadow_host,
311 is_shadow_root: self.shadow_root_mode.is_some(),
312 is_top_level_document: self.is_top_level_document,
313 node_name: self.node_name,
314 node_type: self.node_type,
315 node_value: self.node_value,
316 num_children: self.num_children,
317 parent: actors.script_to_actor(self.parent.clone()),
318 shadow_root_mode: self
319 .shadow_root_mode
320 .as_ref()
321 .map(ShadowRootMode::to_string),
322 traits: HashMap::new(),
323 attrs: self
324 .attrs
325 .into_iter()
326 .map(|attr| AttrMsg {
327 name: attr.name,
328 value: attr.value,
329 })
330 .collect(),
331 name: self.doctype_name,
332 public_id: self.doctype_public_identifier,
333 system_id: self.doctype_system_identifier,
334 }
335 }
336}