devtools/actors/inspector/
node.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
5//! This actor represents one DOM node. It is created by the Walker actor when it is traversing the
6//! document tree.
7
8use 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::{self, GenericSender};
19use servo_base::id::PipelineId;
20
21use crate::actor::{Actor, ActorError, ActorRegistry};
22use crate::protocol::ClientRequest;
23use crate::{EmptyReplyMsg, StreamId};
24
25/// Text node type constant. This is defined again to avoid depending on `script`, where it is defined originally.
26/// See `script::dom::bindings::codegen::Bindings::NodeBinding::NodeConstants`.
27const TEXT_NODE: u16 = 3;
28
29/// The maximum length of a text node for it to appear as an inline child in the inspector.
30const MAX_INLINE_LENGTH: usize = 50;
31
32#[derive(Serialize)]
33struct GetEventListenerInfoReply {
34    from: String,
35    events: Vec<DevtoolsEventListenerInfo>,
36}
37
38#[derive(Serialize)]
39#[serde(rename_all = "camelCase")]
40struct DevtoolsEventListenerInfo {
41    r#type: String,
42    handler: String,
43    origin: String,
44    tags: String,
45    capturing: bool,
46    // This will always be an empty object, we just need a value that serializes to "{}".
47    hide: Value,
48    native: bool,
49    source_actor: String,
50    enabled: bool,
51    is_user_defined: bool,
52    event_listener_info_id: String,
53}
54
55#[derive(Serialize)]
56struct GetUniqueSelectorReply {
57    from: String,
58    value: String,
59}
60
61#[derive(Serialize)]
62struct GetXPathReply {
63    from: String,
64    value: String,
65}
66
67#[derive(Clone, Serialize)]
68struct AttrMsg {
69    name: String,
70    value: String,
71}
72
73#[derive(Clone, Serialize)]
74#[serde(rename_all = "camelCase")]
75pub(crate) struct NodeActorMsg {
76    pub actor: String,
77
78    /// The ID of the shadow host of this node, if it is
79    /// a shadow root
80    host: Option<String>,
81    #[serde(rename = "baseURI")]
82    base_uri: String,
83    causes_overflow: bool,
84    container_type: Option<()>,
85    pub display_name: String,
86    display_type: Option<String>,
87    #[serde(skip_serializing_if = "Option::is_none")]
88    inline_text_child: Option<Box<NodeActorMsg>>,
89    is_after_pseudo_element: bool,
90    is_anonymous: bool,
91    is_before_pseudo_element: bool,
92    is_direct_shadow_host_child: Option<bool>,
93    /// Whether or not this node is displayed.
94    ///
95    /// Setting this value to `false` will cause the devtools to render the node name in gray.
96    is_displayed: bool,
97    #[serde(rename = "isInHTMLDocument")]
98    is_in_html_document: Option<bool>,
99    is_marker_pseudo_element: bool,
100    is_native_anonymous: bool,
101    is_scrollable: bool,
102    is_shadow_host: bool,
103    is_shadow_root: bool,
104    is_top_level_document: bool,
105    node_name: String,
106    node_type: u16,
107    node_value: Option<String>,
108    pub num_children: usize,
109    #[serde(skip_serializing_if = "String::is_empty")]
110    parent: String,
111    shadow_root_mode: Option<String>,
112    traits: HashMap<String, ()>,
113    attrs: Vec<AttrMsg>,
114
115    /// The `DOCTYPE` name if this is a `DocumentType` node, `None` otherwise
116    #[serde(skip_serializing_if = "Option::is_none")]
117    name: Option<String>,
118
119    /// The `DOCTYPE` public identifier if this is a `DocumentType` node, `None` otherwise
120    #[serde(skip_serializing_if = "Option::is_none")]
121    public_id: Option<String>,
122
123    /// The `DOCTYPE` system identifier if this is a `DocumentType` node, `None` otherwise
124    #[serde(skip_serializing_if = "Option::is_none")]
125    system_id: Option<String>,
126
127    has_event_listeners: bool,
128}
129
130#[derive(MallocSizeOf)]
131pub(crate) struct NodeActor {
132    name: String,
133    pub script_chan: GenericSender<DevtoolScriptControlMsg>,
134    pub pipeline: PipelineId,
135    pub walker: String,
136    pub style_rules: AtomicRefCell<HashMap<MatchedRule, String>>,
137}
138
139impl Actor for NodeActor {
140    fn name(&self) -> String {
141        self.name.clone()
142    }
143
144    /// The node actor can handle the following messages:
145    ///
146    /// - `modifyAttributes`: Asks the script to change a value in the attribute of the
147    ///   corresponding node
148    ///
149    /// - `getUniqueSelector`: Returns the display name of this node
150    fn handle_message(
151        &self,
152        request: ClientRequest,
153        registry: &ActorRegistry,
154        msg_type: &str,
155        msg: &Map<String, Value>,
156        _id: StreamId,
157    ) -> Result<(), ActorError> {
158        match msg_type {
159            "modifyAttributes" => {
160                let mods = msg
161                    .get("modifications")
162                    .ok_or(ActorError::MissingParameter)?
163                    .as_array()
164                    .ok_or(ActorError::BadParameterType)?;
165                let modifications: Vec<AttrModification> = mods
166                    .iter()
167                    .filter_map(|json_mod| {
168                        serde_json::from_str(&serde_json::to_string(json_mod).ok()?).ok()
169                    })
170                    .collect();
171
172                self.script_chan
173                    .send(DevtoolScriptControlMsg::ModifyAttribute(
174                        self.pipeline,
175                        registry.actor_to_script(self.name()),
176                        modifications,
177                    ))
178                    .map_err(|_| ActorError::Internal)?;
179
180                let reply = EmptyReplyMsg { from: self.name() };
181                request.reply_final(&reply)?
182            },
183            "getEventListenerInfo" => {
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) = generic_channel::channel().ok_or(ActorError::Internal)?;
191                self.script_chan
192                    .send(DevtoolScriptControlMsg::GetEventListenerInfo(
193                        self.pipeline,
194                        registry.actor_to_script(target.to_owned()),
195                        tx,
196                    ))
197                    .unwrap();
198                let event_listeners = rx.recv().map_err(|_| ActorError::Internal)?;
199
200                let msg = GetEventListenerInfoReply {
201                    from: self.name(),
202                    events: event_listeners.into_iter().map(From::from).collect(),
203                };
204                request.reply_final(&msg)?
205            },
206            "getUniqueSelector" => {
207                let (tx, rx) = generic_channel::channel().unwrap();
208                self.script_chan
209                    .send(DevtoolScriptControlMsg::GetDocumentElement(
210                        self.pipeline,
211                        tx,
212                    ))
213                    .unwrap();
214                let doc_elem_info = rx
215                    .recv()
216                    .map_err(|_| ActorError::Internal)?
217                    .ok_or(ActorError::Internal)?;
218                let node = doc_elem_info.encode(
219                    registry,
220                    self.script_chan.clone(),
221                    self.pipeline,
222                    self.walker.clone(),
223                );
224
225                let msg = GetUniqueSelectorReply {
226                    from: self.name(),
227                    value: node.display_name,
228                };
229                request.reply_final(&msg)?
230            },
231            "getXPath" => {
232                let target = msg
233                    .get("to")
234                    .ok_or(ActorError::MissingParameter)?
235                    .as_str()
236                    .ok_or(ActorError::BadParameterType)?;
237
238                let (tx, rx) = generic_channel::channel().unwrap();
239                self.script_chan
240                    .send(DevtoolScriptControlMsg::GetXPath(
241                        self.pipeline,
242                        registry.actor_to_script(target.to_owned()),
243                        tx,
244                    ))
245                    .unwrap();
246
247                let xpath_selector = rx.recv().map_err(|_| ActorError::Internal)?;
248                let msg = GetXPathReply {
249                    from: self.name(),
250                    value: xpath_selector,
251                };
252                request.reply_final(&msg)?
253            },
254
255            _ => return Err(ActorError::UnrecognizedPacketType),
256        };
257        Ok(())
258    }
259}
260
261impl NodeActor {
262    pub fn register(
263        registry: &ActorRegistry,
264        script_id: String,
265        script_chan: GenericSender<DevtoolScriptControlMsg>,
266        pipeline: PipelineId,
267        walker: String,
268    ) -> String {
269        let name = registry.new_name::<Self>();
270
271        registry.register_script_actor(script_id, name.clone());
272
273        let actor = Self {
274            name: name.clone(),
275            script_chan,
276            pipeline,
277            walker,
278            style_rules: AtomicRefCell::new(HashMap::new()),
279        };
280
281        registry.register(actor);
282        name
283    }
284}
285pub trait NodeInfoToProtocol {
286    fn encode(
287        self,
288        registry: &ActorRegistry,
289        script_chan: GenericSender<DevtoolScriptControlMsg>,
290        pipeline: PipelineId,
291        walker: String,
292    ) -> NodeActorMsg;
293}
294
295impl NodeInfoToProtocol for NodeInfo {
296    fn encode(
297        self,
298        registry: &ActorRegistry,
299        script_chan: GenericSender<DevtoolScriptControlMsg>,
300        pipeline: PipelineId,
301        walker: String,
302    ) -> NodeActorMsg {
303        let get_or_register_node_actor = |id: &str| {
304            if !registry.script_actor_registered(id.to_string()) {
305                NodeActor::register(
306                    registry,
307                    id.to_string(),
308                    script_chan.clone(),
309                    pipeline,
310                    walker.clone(),
311                )
312            } else {
313                registry.script_to_actor(id.to_string())
314            }
315        };
316
317        let actor = get_or_register_node_actor(&self.unique_id);
318        let host = self
319            .host
320            .as_ref()
321            .map(|host_id| get_or_register_node_actor(host_id));
322
323        let name = registry.actor_to_script(actor.clone());
324
325        // If a node only has a single text node as a child whith a small enough text,
326        // return it with this node as an `inlineTextChild`.
327        let inline_text_child = (|| {
328            // TODO: Also return if this node is a flex element.
329            if self.num_children != 1 || self.node_name == "SLOT" {
330                return None;
331            }
332
333            let (tx, rx) = generic_channel::channel()?;
334            script_chan
335                .send(DevtoolScriptControlMsg::GetChildren(
336                    pipeline,
337                    name.clone(),
338                    tx,
339                ))
340                .unwrap();
341            let mut children = rx.recv().ok()??;
342
343            let child = children.pop()?;
344            let msg = child.encode(registry, script_chan.clone(), pipeline, walker);
345
346            // If the node child is not a text node, do not represent it inline.
347            if msg.node_type != TEXT_NODE {
348                return None;
349            }
350
351            // If the text node child is too big, do not represent it inline.
352            if msg.node_value.clone().unwrap_or_default().len() > MAX_INLINE_LENGTH {
353                return None;
354            }
355
356            Some(Box::new(msg))
357        })();
358
359        NodeActorMsg {
360            actor,
361            host,
362            base_uri: self.base_uri,
363            causes_overflow: false,
364            container_type: None,
365            display_name: self.node_name.clone().to_lowercase(),
366            display_type: self.display,
367            inline_text_child,
368            is_after_pseudo_element: false,
369            is_anonymous: false,
370            is_before_pseudo_element: false,
371            is_direct_shadow_host_child: None,
372            is_displayed: self.is_displayed,
373            is_in_html_document: Some(true),
374            is_marker_pseudo_element: false,
375            is_native_anonymous: false,
376            is_scrollable: false,
377            is_shadow_host: self.is_shadow_host,
378            is_shadow_root: self.shadow_root_mode.is_some(),
379            is_top_level_document: self.is_top_level_document,
380            node_name: self.node_name,
381            node_type: self.node_type,
382            node_value: self.node_value,
383            num_children: self.num_children,
384            parent: registry.script_to_actor(self.parent.clone()),
385            shadow_root_mode: self
386                .shadow_root_mode
387                .as_ref()
388                .map(ShadowRootMode::to_string),
389            traits: HashMap::new(),
390            attrs: self
391                .attrs
392                .into_iter()
393                .map(|attr| AttrMsg {
394                    name: attr.name,
395                    value: attr.value,
396                })
397                .collect(),
398            name: self.doctype_name,
399            public_id: self.doctype_public_identifier,
400            system_id: self.doctype_system_identifier,
401            has_event_listeners: self.has_event_listeners,
402        }
403    }
404}
405
406impl From<EventListenerInfo> for DevtoolsEventListenerInfo {
407    fn from(event_listener_info: EventListenerInfo) -> Self {
408        Self {
409            r#type: event_listener_info.event_type,
410            handler: "todo".to_owned(),
411            capturing: event_listener_info.capturing,
412            origin: "todo".to_owned(),
413            tags: "".to_owned(),
414            hide: Value::Object(Default::default()),
415            native: false,
416            source_actor: "todo".to_owned(),
417            enabled: true,
418            is_user_defined: false,
419            event_listener_info_id: "todo".to_owned(),
420        }
421    }
422}