Skip to main content

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;
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
27/// Text node type constant. This is defined again to avoid depending on `script`, where it is defined originally.
28/// See `script::dom::bindings::codegen::Bindings::NodeBinding::NodeConstants`.
29const TEXT_NODE: u16 = 3;
30
31/// The maximum length of a text node for it to appear as an inline child in the inspector.
32const 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    // This will always be an empty object, we just need a value that serializes to "{}".
49    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    /// The ID of the shadow host of this node, if it is
81    /// a shadow root
82    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    /// Whether or not this node is displayed.
96    ///
97    /// Setting this value to `false` will cause the devtools to render the node name in gray.
98    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    /// The `DOCTYPE` name if this is a `DocumentType` node, `None` otherwise
118    #[serde(skip_serializing_if = "Option::is_none")]
119    name: Option<String>,
120
121    /// The `DOCTYPE` public identifier if this is a `DocumentType` node, `None` otherwise
122    #[serde(skip_serializing_if = "Option::is_none")]
123    public_id: Option<String>,
124
125    /// The `DOCTYPE` system identifier if this is a `DocumentType` node, `None` otherwise
126    #[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    /// The node actor can handle the following messages:
146    ///
147    /// - `modifyAttributes`: Asks the script to change a value in the attribute of the
148    ///   corresponding node
149    ///
150    /// - `getUniqueSelector`: Returns the display name of this node
151    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        // If a node only has a single text node as a child with a small enough text,
316        // return it with this node as an `inlineTextChild`.
317        let inline_text_child = (|| {
318            // TODO: Also return if this node is a flex element.
319            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 the node child is not a text node, do not represent it inline.
338            if msg.node_type != TEXT_NODE {
339                return None;
340            }
341
342            // If the text node child is too big, do not represent it inline.
343            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}