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