devtools/actors/inspector/
style_rule.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//! Liberally derived from <https://searchfox.org/mozilla-central/source/devtools/server/actors/thread-configuration.js>
6//! This actor represents one css rule group from a node, allowing the inspector to view it and change it.
7//! A group is either the html style attribute or one selector from one stylesheet.
8
9use std::collections::HashMap;
10
11use devtools_traits::DevtoolScriptControlMsg::{
12    GetAttributeStyle, GetComputedStyle, GetDocumentElement, GetStylesheetStyle, ModifyRule,
13};
14use ipc_channel::ipc;
15use serde::Serialize;
16use serde_json::{Map, Value};
17
18use crate::StreamId;
19use crate::actor::{Actor, ActorError, ActorRegistry};
20use crate::actors::inspector::node::NodeActor;
21use crate::actors::inspector::walker::WalkerActor;
22use crate::protocol::ClientRequest;
23
24const ELEMENT_STYLE_TYPE: u32 = 100;
25
26#[derive(Serialize)]
27#[serde(rename_all = "camelCase")]
28pub struct AppliedRule {
29    actor: String,
30    ancestor_data: Vec<()>,
31    authored_text: String,
32    css_text: String,
33    pub declarations: Vec<AppliedDeclaration>,
34    href: String,
35    #[serde(skip_serializing_if = "Vec::is_empty")]
36    selectors: Vec<String>,
37    #[serde(skip_serializing_if = "Vec::is_empty")]
38    selectors_specificity: Vec<u32>,
39    #[serde(rename = "type")]
40    type_: u32,
41    traits: StyleRuleActorTraits,
42}
43
44#[derive(Serialize)]
45pub struct IsUsed {
46    pub used: bool,
47}
48
49#[derive(Serialize)]
50#[serde(rename_all = "camelCase")]
51pub struct AppliedDeclaration {
52    colon_offsets: Vec<i32>,
53    is_name_valid: bool,
54    is_used: IsUsed,
55    is_valid: bool,
56    name: String,
57    offsets: Vec<i32>,
58    priority: String,
59    terminator: String,
60    value: String,
61}
62
63#[derive(Serialize)]
64pub struct ComputedDeclaration {
65    matched: bool,
66    value: String,
67}
68
69#[derive(Serialize)]
70#[serde(rename_all = "camelCase")]
71pub struct StyleRuleActorTraits {
72    pub can_set_rule_text: bool,
73}
74
75#[derive(Serialize)]
76pub struct StyleRuleActorMsg {
77    from: String,
78    rule: Option<AppliedRule>,
79}
80
81pub struct StyleRuleActor {
82    name: String,
83    node: String,
84    selector: Option<(String, usize)>,
85}
86
87impl Actor for StyleRuleActor {
88    fn name(&self) -> String {
89        self.name.clone()
90    }
91
92    /// The style rule configuration actor can handle the following messages:
93    ///
94    /// - `setRuleText`: Applies a set of modifications to the css rules that this actor manages.
95    ///   There is also `modifyProperties`, which has a slightly different API to do the same, but
96    ///   this is preferred. Which one the devtools client sends is decided by the `traits` defined
97    ///   when returning the list of rules.
98    fn handle_message(
99        &self,
100        request: ClientRequest,
101        registry: &ActorRegistry,
102        msg_type: &str,
103        msg: &Map<String, Value>,
104        _id: StreamId,
105    ) -> Result<(), ActorError> {
106        match msg_type {
107            "setRuleText" => {
108                // Parse the modifications sent from the client
109                let mods = msg
110                    .get("modifications")
111                    .ok_or(ActorError::MissingParameter)?
112                    .as_array()
113                    .ok_or(ActorError::BadParameterType)?;
114                let modifications: Vec<_> = mods
115                    .iter()
116                    .filter_map(|json_mod| {
117                        serde_json::from_str(&serde_json::to_string(json_mod).ok()?).ok()
118                    })
119                    .collect();
120
121                // Query the rule modification
122                let node = registry.find::<NodeActor>(&self.node);
123                let walker = registry.find::<WalkerActor>(&node.walker);
124                walker
125                    .script_chan
126                    .send(ModifyRule(
127                        walker.pipeline,
128                        registry.actor_to_script(self.node.clone()),
129                        modifications,
130                    ))
131                    .map_err(|_| ActorError::Internal)?;
132
133                request.reply_final(&self.encodable(registry))?
134            },
135            _ => return Err(ActorError::UnrecognizedPacketType),
136        };
137        Ok(())
138    }
139}
140
141impl StyleRuleActor {
142    pub fn new(name: String, node: String, selector: Option<(String, usize)>) -> Self {
143        Self {
144            name,
145            node,
146            selector,
147        }
148    }
149
150    pub fn applied(&self, registry: &ActorRegistry) -> Option<AppliedRule> {
151        let node = registry.find::<NodeActor>(&self.node);
152        let walker = registry.find::<WalkerActor>(&node.walker);
153
154        let (document_sender, document_receiver) = ipc::channel().ok()?;
155        walker
156            .script_chan
157            .send(GetDocumentElement(walker.pipeline, document_sender))
158            .ok()?;
159        let node = document_receiver.recv().ok()??;
160
161        // Gets the style definitions. If there is a selector, query the relevant stylesheet, if
162        // not, this represents the style attribute.
163        let (style_sender, style_receiver) = ipc::channel().ok()?;
164        let req = match &self.selector {
165            Some(selector) => {
166                let (selector, stylesheet) = selector.clone();
167                GetStylesheetStyle(
168                    walker.pipeline,
169                    registry.actor_to_script(self.node.clone()),
170                    selector,
171                    stylesheet,
172                    style_sender,
173                )
174            },
175            None => GetAttributeStyle(
176                walker.pipeline,
177                registry.actor_to_script(self.node.clone()),
178                style_sender,
179            ),
180        };
181        walker.script_chan.send(req).ok()?;
182        let style = style_receiver.recv().ok()??;
183
184        Some(AppliedRule {
185            actor: self.name(),
186            ancestor_data: vec![], // TODO: Fill with hierarchy
187            authored_text: "".into(),
188            css_text: "".into(), // TODO: Specify the css text
189            declarations: style
190                .into_iter()
191                .map(|decl| {
192                    AppliedDeclaration {
193                        colon_offsets: vec![],
194                        is_name_valid: true,
195                        is_used: IsUsed { used: true },
196                        is_valid: true,
197                        name: decl.name,
198                        offsets: vec![], // TODO: Get the source of the declaration
199                        priority: decl.priority,
200                        terminator: "".into(),
201                        value: decl.value,
202                    }
203                })
204                .collect(),
205            href: node.base_uri.clone(),
206            selectors: self.selector.iter().map(|(s, _)| s).cloned().collect(),
207            selectors_specificity: self.selector.iter().map(|_| 1).collect(),
208            type_: ELEMENT_STYLE_TYPE,
209            traits: StyleRuleActorTraits {
210                can_set_rule_text: true,
211            },
212        })
213    }
214
215    pub fn computed(
216        &self,
217        registry: &ActorRegistry,
218    ) -> Option<HashMap<String, ComputedDeclaration>> {
219        let node = registry.find::<NodeActor>(&self.node);
220        let walker = registry.find::<WalkerActor>(&node.walker);
221
222        let (style_sender, style_receiver) = ipc::channel().ok()?;
223        walker
224            .script_chan
225            .send(GetComputedStyle(
226                walker.pipeline,
227                registry.actor_to_script(self.node.clone()),
228                style_sender,
229            ))
230            .ok()?;
231        let style = style_receiver.recv().ok()??;
232
233        Some(
234            style
235                .into_iter()
236                .map(|s| {
237                    (
238                        s.name,
239                        ComputedDeclaration {
240                            matched: true,
241                            value: s.value,
242                        },
243                    )
244                })
245                .collect(),
246        )
247    }
248
249    pub fn encodable(&self, registry: &ActorRegistry) -> StyleRuleActorMsg {
250        StyleRuleActorMsg {
251            from: self.name(),
252            rule: self.applied(registry),
253        }
254    }
255}