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