Skip to main content

devtools/actors/inspector/
walker.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//! The walker actor is responsible for traversing the DOM tree in various ways to create new nodes
6
7use std::sync::Arc;
8
9use atomic_refcell::AtomicRefCell;
10use devtools_traits::DevtoolScriptControlMsg::{
11    GetChildren, GetDocumentElement, GetInnerOrOuterHTML, GetRootNode,
12};
13use devtools_traits::{DomMutation, GetHTMLType};
14use malloc_size_of_derive::MallocSizeOf;
15use serde::Serialize;
16use serde_json::{self, Map, Value};
17use servo_base::generic_channel;
18
19use crate::actor::{
20    Actor, ActorEncode, ActorError, ActorRegistry, DowncastableActorArc, new_actor_name,
21};
22use crate::actors::browsing_context::BrowsingContextActor;
23use crate::actors::inspector::layout::LayoutInspectorActor;
24use crate::actors::inspector::node::{NodeActor, NodeActorMsg};
25use crate::actors::long_string::{LongStringActor, LongStringObj};
26use crate::protocol::{ClientRequest, DevtoolsConnection, JsonPacketStream};
27use crate::{ActorMsg, EmptyReplyMsg, StreamId};
28
29#[derive(Serialize)]
30pub(crate) struct WalkerMsg {
31    actor: String,
32    root: NodeActorMsg,
33}
34
35#[derive(MallocSizeOf)]
36pub(crate) struct WalkerActor {
37    pub name: String,
38    pub mutations: AtomicRefCell<Vec<DomMutation>>,
39    /// Name of the [`BrowsingContextActor`] that owns this walker.
40    pub browsing_context_name: String,
41}
42
43#[derive(Serialize)]
44#[serde(rename_all = "camelCase")]
45struct QuerySelectorReply {
46    from: String,
47    node: NodeActorMsg,
48    new_parents: Vec<NodeActorMsg>,
49}
50
51#[derive(Serialize)]
52struct DocumentElementReply {
53    from: String,
54    node: NodeActorMsg,
55}
56
57#[derive(Serialize)]
58#[serde(rename_all = "camelCase")]
59struct ChildrenReply {
60    has_first: bool,
61    has_last: bool,
62    nodes: Vec<NodeActorMsg>,
63    from: String,
64}
65
66#[derive(Serialize)]
67struct GetLayoutInspectorReply {
68    from: String,
69    actor: ActorMsg,
70}
71
72#[derive(Serialize)]
73struct WatchRootNodeNotification {
74    #[serde(rename = "type")]
75    type_: String,
76    from: String,
77    node: NodeActorMsg,
78}
79
80#[derive(Serialize)]
81#[serde(rename_all = "camelCase")]
82struct MutationMsg {
83    #[serde(flatten)]
84    variant: MutationVariant,
85    #[serde(rename = "type")]
86    type_: String,
87    target: String,
88}
89
90#[derive(Serialize)]
91#[serde(untagged)]
92enum MutationVariant {
93    AttributeModified {
94        #[serde(rename = "attributeName")]
95        attribute_name: String,
96        #[serde(rename = "newValue")]
97        new_value: Option<String>,
98    },
99}
100
101#[derive(Serialize)]
102struct GetMutationsReply {
103    from: String,
104    mutations: Vec<MutationMsg>,
105}
106
107#[derive(Serialize)]
108struct GetOffsetParentReply {
109    from: String,
110    node: Option<()>,
111}
112
113#[derive(Serialize)]
114struct NewMutationsNotification {
115    from: String,
116    #[serde(rename = "type")]
117    type_: String,
118}
119
120#[derive(Serialize)]
121struct GetInnerOrOuterHTMLReply {
122    from: String,
123    value: LongStringObj,
124}
125
126impl Actor for WalkerActor {
127    fn name(&self) -> &str {
128        &self.name
129    }
130
131    /// The walker actor can handle the following messages:
132    ///
133    /// - `children`: Returns a list of children nodes of the specified node
134    ///
135    /// - `clearPseudoClassLocks`: Placeholder
136    ///
137    /// - `documentElement`: Returns the base document element node
138    ///
139    /// - `getLayoutInspector`: Returns the Layout inspector actor, placeholder
140    ///
141    /// - `getMutations`: Returns the list of attribute changes since it was last called
142    ///
143    /// - `getOffsetParent`: Placeholder
144    ///
145    /// - `querySelector`: Recursively looks for the specified selector in the tree, reutrning the
146    ///   node and its ascendents
147    ///
148    /// - `outerHTML`: Return outer html element or text from specified node
149    ///
150    /// - `innerHTML`: Return inner html element or text from specified node
151    fn handle_message(
152        &self,
153        mut 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 = self.browsing_context_actor(registry);
160        match msg_type {
161            "children" => {
162                let target = msg
163                    .get("node")
164                    .ok_or(ActorError::MissingParameter)?
165                    .as_str()
166                    .ok_or(ActorError::BadParameterType)?;
167                let Some((tx, rx)) = generic_channel::channel() else {
168                    return Err(ActorError::Internal);
169                };
170                browsing_context_actor
171                    .script_chan()
172                    .send(GetChildren(
173                        browsing_context_actor.pipeline_id(),
174                        registry.actor_to_script(target.into()),
175                        tx,
176                    ))
177                    .map_err(|_| ActorError::Internal)?;
178                let children = rx
179                    .recv()
180                    .map_err(|_| ActorError::Internal)?
181                    .ok_or(ActorError::Internal)?;
182
183                let msg = ChildrenReply {
184                    has_first: true,
185                    has_last: true,
186                    nodes: children
187                        .into_iter()
188                        .map(|child| {
189                            let node_actor =
190                                NodeActor::register_or_update(registry, &self.name, child);
191                            node_actor.encode(registry)
192                        })
193                        .collect(),
194                    from: self.name().into(),
195                };
196                request.reply_final(&msg)?
197            },
198            "clearPseudoClassLocks" => {
199                let msg = EmptyReplyMsg {
200                    from: self.name().into(),
201                };
202                request.reply_final(&msg)?
203            },
204            "documentElement" => {
205                let Some((tx, rx)) = generic_channel::channel() else {
206                    return Err(ActorError::Internal);
207                };
208                browsing_context_actor
209                    .script_chan()
210                    .send(GetDocumentElement(browsing_context_actor.pipeline_id(), tx))
211                    .map_err(|_| ActorError::Internal)?;
212                let node_info = rx
213                    .recv()
214                    .map_err(|_| ActorError::Internal)?
215                    .ok_or(ActorError::Internal)?;
216
217                let node_actor = NodeActor::register_or_update(registry, &self.name, node_info);
218                let node = node_actor.encode(registry);
219
220                let msg = DocumentElementReply {
221                    from: self.name().into(),
222                    node,
223                };
224                request.reply_final(&msg)?
225            },
226            "getLayoutInspector" => {
227                let layout_inspector_actor = LayoutInspectorActor::register(registry);
228                let msg = GetLayoutInspectorReply {
229                    from: self.name().into(),
230                    actor: layout_inspector_actor.encode(registry),
231                };
232                request.reply_final(&msg)?
233            },
234            "getMutations" => self.handle_get_mutations(request, registry)?,
235            "getOffsetParent" => {
236                let msg = GetOffsetParentReply {
237                    from: self.name().into(),
238                    node: None,
239                };
240                request.reply_final(&msg)?
241            },
242            "querySelector" => {
243                let selector = msg
244                    .get("selector")
245                    .ok_or(ActorError::MissingParameter)?
246                    .as_str()
247                    .ok_or(ActorError::BadParameterType)?;
248                let node_name = msg
249                    .get("node")
250                    .ok_or(ActorError::MissingParameter)?
251                    .as_str()
252                    .ok_or(ActorError::BadParameterType)?;
253                let mut hierarchy = find_child(&self.name, registry, node_name, vec![], |msg| {
254                    msg.display_name == selector
255                })
256                .map_err(|_| ActorError::Internal)?;
257                hierarchy.reverse();
258                let node = hierarchy.pop().ok_or(ActorError::Internal)?;
259
260                let msg = QuerySelectorReply {
261                    from: self.name().into(),
262                    node,
263                    new_parents: hierarchy,
264                };
265                request.reply_final(&msg)?
266            },
267            "watchRootNode" => {
268                let msg = WatchRootNodeNotification {
269                    type_: "root-available".into(),
270                    from: self.name().into(),
271                    node: self.root(registry)?,
272                };
273                let _ = request.write_json_packet(&msg);
274
275                let msg = EmptyReplyMsg {
276                    from: self.name().into(),
277                };
278                request.reply_final(&msg)?
279            },
280            "outerHTML" => {
281                self.handle_get_inner_or_outer_html(request, registry, msg, GetHTMLType::OuterHTML)?
282            },
283            "innerHTML" => {
284                self.handle_get_inner_or_outer_html(request, registry, msg, GetHTMLType::InnerHTML)?
285            },
286            _ => return Err(ActorError::UnrecognizedPacketType),
287        };
288        Ok(())
289    }
290}
291
292impl WalkerActor {
293    pub fn register(registry: &ActorRegistry, browsing_context_name: String) -> Arc<Self> {
294        let name = new_actor_name::<WalkerActor>();
295        let actor = WalkerActor {
296            name,
297            mutations: AtomicRefCell::new(vec![]),
298            browsing_context_name,
299        };
300        registry.register::<Self>(actor)
301    }
302
303    pub(crate) fn browsing_context_actor(
304        &self,
305        registry: &ActorRegistry,
306    ) -> DowncastableActorArc<BrowsingContextActor> {
307        registry.find::<BrowsingContextActor>(&self.browsing_context_name)
308    }
309
310    pub(crate) fn root(&self, registry: &ActorRegistry) -> Result<NodeActorMsg, ActorError> {
311        let browsing_context_actor = self.browsing_context_actor(registry);
312        let pipeline = browsing_context_actor.pipeline_id();
313        let (tx, rx) = generic_channel::channel().ok_or(ActorError::Internal)?;
314        browsing_context_actor
315            .script_chan()
316            .send(GetRootNode(pipeline, tx))
317            .map_err(|_| ActorError::Internal)?;
318        let node_info = rx
319            .recv()
320            .map_err(|_| ActorError::Internal)?
321            .ok_or(ActorError::Internal)?;
322
323        let node_actor = NodeActor::register_or_update(registry, &self.name, node_info);
324        Ok(node_actor.encode(registry))
325    }
326
327    pub(crate) fn handle_dom_mutation(
328        &self,
329        dom_mutation: DomMutation,
330        stream: &mut DevtoolsConnection,
331    ) -> Result<(), ActorError> {
332        let mut pending_mutations = self.mutations.borrow_mut();
333
334        // Discard all previous modifications to that same attribute
335        // which we didn't tell the devtools client about yet.
336        let DomMutation::AttributeModified {
337            node,
338            attribute_name,
339            ..
340        } = &dom_mutation;
341        pending_mutations.retain(|pending_mutation| match pending_mutation {
342            DomMutation::AttributeModified {
343                node: old_node,
344                attribute_name: old_attribute_name,
345                ..
346            } => old_node != node || old_attribute_name != attribute_name,
347        });
348
349        pending_mutations.push(dom_mutation);
350
351        stream.write_json_packet(&NewMutationsNotification {
352            from: self.name().into(),
353            type_: "newMutations".into(),
354        })
355    }
356
357    /// Handle the `getMutations` message from a devtools client.
358    fn handle_get_mutations(
359        &self,
360        request: ClientRequest,
361        registry: &ActorRegistry,
362    ) -> Result<(), ActorError> {
363        let msg = GetMutationsReply {
364            from: self.name().into(),
365            mutations: self
366                .mutations
367                .borrow_mut()
368                .drain(..)
369                .map(|mutation| match mutation {
370                    DomMutation::AttributeModified {
371                        node,
372                        attribute_name,
373                        new_value,
374                    } => MutationMsg {
375                        variant: MutationVariant::AttributeModified {
376                            attribute_name,
377                            new_value,
378                        },
379                        target: registry.script_to_actor(&node),
380                        type_: "attributes".to_owned(),
381                    },
382                })
383                .collect(),
384        };
385
386        request.reply_final(&msg)
387    }
388
389    fn handle_get_inner_or_outer_html(
390        &self,
391        request: ClientRequest,
392        registry: &ActorRegistry,
393        msg: &Map<String, Value>,
394        html_type: GetHTMLType,
395    ) -> Result<(), ActorError> {
396        let target = msg
397            .get("node")
398            .ok_or(ActorError::MissingParameter)?
399            .as_str()
400            .ok_or(ActorError::BadParameterType)?;
401
402        let Some((tx, rx)) = generic_channel::channel() else {
403            return Err(ActorError::Internal);
404        };
405
406        let browsing_context_actor = self.browsing_context_actor(registry);
407
408        let handle_get_inner_or_outer_html = GetInnerOrOuterHTML(
409            browsing_context_actor.pipeline_id(),
410            registry.actor_to_script(target.into()),
411            tx,
412            html_type,
413        );
414
415        browsing_context_actor
416            .script_chan()
417            .send(handle_get_inner_or_outer_html)
418            .map_err(|_| ActorError::Internal)?;
419
420        let html_text = rx
421            .recv()
422            .map_err(|_| ActorError::Internal)?
423            .ok_or(ActorError::Internal)?;
424
425        let long_string_actor = LongStringActor::register(registry, html_text);
426
427        let msg = GetInnerOrOuterHTMLReply {
428            from: self.name().into(),
429            value: long_string_actor.long_string_obj(),
430        };
431
432        request.reply_final(&msg)
433    }
434}
435
436/// Recursively searches for a child with the specified selector
437/// If it is found, returns a list with the child and all of its ancestors.
438/// TODO: Investigate how to cache this to some extent.
439pub fn find_child(
440    walker_name: &str,
441    registry: &ActorRegistry,
442    node_name: &str,
443    mut hierarchy: Vec<NodeActorMsg>,
444    compare_fn: impl Fn(&NodeActorMsg) -> bool + Clone,
445) -> Result<Vec<NodeActorMsg>, Vec<NodeActorMsg>> {
446    let walker = registry.find::<WalkerActor>(walker_name);
447    let browsing_context = walker.browsing_context_actor(registry);
448    let script_chan = browsing_context.script_chan();
449    let pipeline = browsing_context.pipeline_id();
450
451    let (tx, rx) = generic_channel::channel().unwrap();
452    script_chan
453        .send(GetChildren(
454            pipeline,
455            registry.actor_to_script(node_name.into()),
456            tx,
457        ))
458        .unwrap();
459    let children = rx.recv().unwrap().ok_or(vec![])?;
460
461    for child in children {
462        let node_actor = NodeActor::register_or_update(registry, walker_name, child);
463        let msg = node_actor.encode(registry);
464
465        if compare_fn(&msg) {
466            hierarchy.push(msg);
467            return Ok(hierarchy);
468        };
469
470        if msg.num_children == 0 {
471            continue;
472        }
473
474        match find_child(
475            walker_name,
476            registry,
477            &msg.actor,
478            hierarchy,
479            compare_fn.clone(),
480        ) {
481            Ok(mut hierarchy) => {
482                hierarchy.push(msg);
483                return Ok(hierarchy);
484            },
485            Err(e) => {
486                hierarchy = e;
487            },
488        }
489    }
490    Err(hierarchy)
491}
492
493impl ActorEncode<WalkerMsg> for WalkerActor {
494    fn encode(&self, registry: &ActorRegistry) -> WalkerMsg {
495        WalkerMsg {
496            actor: self.name().into(),
497            root: self.root(registry).unwrap(),
498        }
499    }
500}