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::cell::RefCell;
8
9use base::id::PipelineId;
10use devtools_traits::DevtoolScriptControlMsg::{GetChildren, GetDocumentElement};
11use devtools_traits::{AttrModification, DevtoolScriptControlMsg};
12use ipc_channel::ipc::{self, IpcSender};
13use serde::Serialize;
14use serde_json::{self, Map, Value};
15
16use crate::actor::{Actor, ActorError, ActorRegistry};
17use crate::actors::inspector::layout::{LayoutInspectorActor, LayoutInspectorActorMsg};
18use crate::actors::inspector::node::{NodeActorMsg, NodeInfoToProtocol};
19use crate::protocol::{ClientRequest, JsonPacketStream};
20use crate::{EmptyReplyMsg, StreamId};
21
22#[derive(Serialize)]
23pub struct WalkerMsg {
24    pub actor: String,
25    pub root: NodeActorMsg,
26}
27
28pub struct WalkerActor {
29    pub name: String,
30    pub script_chan: IpcSender<DevtoolScriptControlMsg>,
31    pub pipeline: PipelineId,
32    pub root_node: NodeActorMsg,
33    pub mutations: RefCell<Vec<(AttrModification, String)>>,
34}
35
36#[derive(Serialize)]
37#[serde(rename_all = "camelCase")]
38struct QuerySelectorReply {
39    from: String,
40    node: NodeActorMsg,
41    new_parents: Vec<NodeActorMsg>,
42}
43
44#[derive(Serialize)]
45struct DocumentElementReply {
46    from: String,
47    node: NodeActorMsg,
48}
49
50#[derive(Serialize)]
51#[serde(rename_all = "camelCase")]
52struct ChildrenReply {
53    has_first: bool,
54    has_last: bool,
55    nodes: Vec<NodeActorMsg>,
56    from: String,
57}
58
59#[derive(Serialize)]
60struct GetLayoutInspectorReply {
61    actor: LayoutInspectorActorMsg,
62    from: String,
63}
64
65#[derive(Serialize)]
66struct WatchRootNodeNotification {
67    #[serde(rename = "type")]
68    type_: String,
69    from: String,
70    node: NodeActorMsg,
71}
72
73#[derive(Serialize)]
74#[serde(rename_all = "camelCase")]
75struct MutationMsg {
76    attribute_name: String,
77    new_value: Option<String>,
78    target: String,
79    #[serde(rename = "type")]
80    type_: String,
81}
82
83#[derive(Serialize)]
84struct GetMutationsReply {
85    from: String,
86    mutations: Vec<MutationMsg>,
87}
88
89#[derive(Serialize)]
90struct GetOffsetParentReply {
91    from: String,
92    node: Option<()>,
93}
94
95#[derive(Serialize)]
96struct NewMutationsNotification {
97    from: String,
98    #[serde(rename = "type")]
99    type_: String,
100}
101
102impl Actor for WalkerActor {
103    fn name(&self) -> String {
104        self.name.clone()
105    }
106
107    /// The walker actor can handle the following messages:
108    ///
109    /// - `children`: Returns a list of children nodes of the specified node
110    ///
111    /// - `clearPseudoClassLocks`: Placeholder
112    ///
113    /// - `documentElement`: Returns the base document element node
114    ///
115    /// - `getLayoutInspector`: Returns the Layout inspector actor, placeholder
116    ///
117    /// - `getMutations`: Returns the list of attribute changes since it was last called
118    ///
119    /// - `getOffsetParent`: Placeholder
120    ///
121    /// - `querySelector`: Recursively looks for the specified selector in the tree, reutrning the
122    ///   node and its ascendents
123    fn handle_message(
124        &self,
125        mut request: ClientRequest,
126        registry: &ActorRegistry,
127        msg_type: &str,
128        msg: &Map<String, Value>,
129        _id: StreamId,
130    ) -> Result<(), ActorError> {
131        match msg_type {
132            "children" => {
133                let target = msg
134                    .get("node")
135                    .ok_or(ActorError::MissingParameter)?
136                    .as_str()
137                    .ok_or(ActorError::BadParameterType)?;
138                let (tx, rx) = ipc::channel().map_err(|_| ActorError::Internal)?;
139                self.script_chan
140                    .send(GetChildren(
141                        self.pipeline,
142                        registry.actor_to_script(target.into()),
143                        tx,
144                    ))
145                    .map_err(|_| ActorError::Internal)?;
146                let children = rx
147                    .recv()
148                    .map_err(|_| ActorError::Internal)?
149                    .ok_or(ActorError::Internal)?;
150
151                let msg = ChildrenReply {
152                    has_first: true,
153                    has_last: true,
154                    nodes: children
155                        .into_iter()
156                        .map(|child| {
157                            child.encode(
158                                registry,
159                                self.script_chan.clone(),
160                                self.pipeline,
161                                self.name(),
162                            )
163                        })
164                        .collect(),
165                    from: self.name(),
166                };
167                request.reply_final(&msg)?
168            },
169            "clearPseudoClassLocks" => {
170                let msg = EmptyReplyMsg { from: self.name() };
171                request.reply_final(&msg)?
172            },
173            "documentElement" => {
174                let (tx, rx) = ipc::channel().map_err(|_| ActorError::Internal)?;
175                self.script_chan
176                    .send(GetDocumentElement(self.pipeline, tx))
177                    .map_err(|_| ActorError::Internal)?;
178                let doc_elem_info = rx
179                    .recv()
180                    .map_err(|_| ActorError::Internal)?
181                    .ok_or(ActorError::Internal)?;
182                let node = doc_elem_info.encode(
183                    registry,
184                    self.script_chan.clone(),
185                    self.pipeline,
186                    self.name(),
187                );
188
189                let msg = DocumentElementReply {
190                    from: self.name(),
191                    node,
192                };
193                request.reply_final(&msg)?
194            },
195            "getLayoutInspector" => {
196                // TODO: Create actual layout inspector actor
197                let layout = LayoutInspectorActor::new(registry.new_name("layout"));
198                let actor = layout.encodable();
199                registry.register_later(Box::new(layout));
200
201                let msg = GetLayoutInspectorReply {
202                    from: self.name(),
203                    actor,
204                };
205                request.reply_final(&msg)?
206            },
207            "getMutations" => {
208                let msg = GetMutationsReply {
209                    from: self.name(),
210                    mutations: self
211                        .mutations
212                        .borrow_mut()
213                        .drain(..)
214                        .map(|(mutation, target)| MutationMsg {
215                            attribute_name: mutation.attribute_name,
216                            new_value: mutation.new_value,
217                            target,
218                            type_: "attributes".into(),
219                        })
220                        .collect(),
221                };
222                request.reply_final(&msg)?
223            },
224            "getOffsetParent" => {
225                let msg = GetOffsetParentReply {
226                    from: self.name(),
227                    node: None,
228                };
229                request.reply_final(&msg)?
230            },
231            "querySelector" => {
232                let selector = msg
233                    .get("selector")
234                    .ok_or(ActorError::MissingParameter)?
235                    .as_str()
236                    .ok_or(ActorError::BadParameterType)?;
237                let node = msg
238                    .get("node")
239                    .ok_or(ActorError::MissingParameter)?
240                    .as_str()
241                    .ok_or(ActorError::BadParameterType)?;
242                let mut hierarchy = find_child(
243                    &self.script_chan,
244                    self.pipeline,
245                    &self.name,
246                    registry,
247                    node,
248                    vec![],
249                    |msg| msg.display_name == selector,
250                )
251                .map_err(|_| ActorError::Internal)?;
252                hierarchy.reverse();
253                let node = hierarchy.pop().ok_or(ActorError::Internal)?;
254
255                let msg = QuerySelectorReply {
256                    from: self.name(),
257                    node,
258                    new_parents: hierarchy,
259                };
260                request.reply_final(&msg)?
261            },
262            "watchRootNode" => {
263                let msg = WatchRootNodeNotification {
264                    type_: "root-available".into(),
265                    from: self.name(),
266                    node: self.root_node.clone(),
267                };
268                let _ = request.write_json_packet(&msg);
269
270                let msg = EmptyReplyMsg { from: self.name() };
271                request.reply_final(&msg)?
272            },
273            _ => return Err(ActorError::UnrecognizedPacketType),
274        };
275        Ok(())
276    }
277}
278
279impl WalkerActor {
280    pub(crate) fn new_mutations(
281        &self,
282        request: &mut ClientRequest,
283        target: &str,
284        modifications: &[AttrModification],
285    ) {
286        {
287            let mut mutations = self.mutations.borrow_mut();
288            mutations.extend(modifications.iter().cloned().map(|m| (m, target.into())));
289        }
290        let _ = request.write_json_packet(&NewMutationsNotification {
291            from: self.name(),
292            type_: "newMutations".into(),
293        });
294    }
295}
296
297/// Recursively searches for a child with the specified selector
298/// If it is found, returns a list with the child and all of its ancestors.
299/// TODO: Investigate how to cache this to some extent.
300pub fn find_child(
301    script_chan: &IpcSender<DevtoolScriptControlMsg>,
302    pipeline: PipelineId,
303    name: &str,
304    registry: &ActorRegistry,
305    node: &str,
306    mut hierarchy: Vec<NodeActorMsg>,
307    compare_fn: impl Fn(&NodeActorMsg) -> bool + Clone,
308) -> Result<Vec<NodeActorMsg>, Vec<NodeActorMsg>> {
309    let (tx, rx) = ipc::channel().unwrap();
310    script_chan
311        .send(GetChildren(
312            pipeline,
313            registry.actor_to_script(node.into()),
314            tx,
315        ))
316        .unwrap();
317    let children = rx.recv().unwrap().ok_or(vec![])?;
318
319    for child in children {
320        let msg = child.encode(registry, script_chan.clone(), pipeline, name.into());
321        if compare_fn(&msg) {
322            hierarchy.push(msg);
323            return Ok(hierarchy);
324        };
325
326        if msg.num_children == 0 {
327            continue;
328        }
329
330        match find_child(
331            script_chan,
332            pipeline,
333            name,
334            registry,
335            &msg.actor,
336            hierarchy,
337            compare_fn.clone(),
338        ) {
339            Ok(mut hierarchy) => {
340                hierarchy.push(msg);
341                return Ok(hierarchy);
342            },
343            Err(e) => {
344                hierarchy = e;
345            },
346        }
347    }
348    Err(hierarchy)
349}