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