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