Skip to main content

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;
10use std::sync::Arc;
11
12use devtools_traits::DevtoolScriptControlMsg::{
13    GetAttributeStyle, GetComputedStyle, GetDocumentElement, GetStylesheetStyle, ModifyRule,
14};
15use devtools_traits::{AncestorData, MatchedRule};
16use malloc_size_of_derive::MallocSizeOf;
17use serde::Serialize;
18use serde_json::{Map, Value};
19use servo_base::generic_channel;
20
21use crate::StreamId;
22use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry, new_actor_name};
23use crate::actors::inspector::node::NodeActor;
24use crate::actors::inspector::walker::WalkerActor;
25use crate::protocol::ClientRequest;
26
27const ELEMENT_STYLE_TYPE: u32 = 100;
28
29#[derive(Serialize)]
30#[serde(rename_all = "camelCase")]
31pub(crate) struct AppliedRule {
32    actor: String,
33    ancestor_data: Vec<AncestorData>,
34    authored_text: String,
35    css_text: String,
36    pub declarations: Vec<AppliedDeclaration>,
37    href: String,
38    #[serde(skip_serializing_if = "Vec::is_empty")]
39    selectors: Vec<String>,
40    #[serde(skip_serializing_if = "Vec::is_empty")]
41    selectors_specificity: Vec<u32>,
42    #[serde(rename = "type")]
43    type_: u32,
44    traits: StyleRuleActorTraits,
45}
46
47#[derive(Serialize)]
48pub(crate) struct IsUsed {
49    used: bool,
50}
51
52#[derive(Serialize)]
53#[serde(rename_all = "camelCase")]
54pub(crate) struct AppliedDeclaration {
55    colon_offsets: Vec<i32>,
56    is_name_valid: bool,
57    is_used: IsUsed,
58    is_valid: bool,
59    name: String,
60    offsets: Vec<i32>,
61    priority: String,
62    terminator: String,
63    value: String,
64}
65
66#[derive(Serialize)]
67pub(crate) struct ComputedDeclaration {
68    matched: bool,
69    value: String,
70}
71
72#[derive(Serialize)]
73#[serde(rename_all = "camelCase")]
74pub(crate) struct StyleRuleActorTraits {
75    pub can_set_rule_text: bool,
76}
77
78#[derive(Serialize)]
79pub(crate) struct StyleRuleActorMsg {
80    from: String,
81    rule: Option<AppliedRule>,
82}
83
84#[derive(MallocSizeOf)]
85pub(crate) struct StyleRuleActor {
86    name: String,
87    node_name: String,
88    selector: Option<MatchedRule>,
89}
90
91impl Actor for StyleRuleActor {
92    fn name(&self) -> &str {
93        &self.name
94    }
95
96    /// The style rule configuration actor can handle the following messages:
97    ///
98    /// - `setRuleText`: Applies a set of modifications to the css rules that this actor manages.
99    ///   There is also `modifyProperties`, which has a slightly different API to do the same, but
100    ///   this is preferred. Which one the devtools client sends is decided by the `traits` defined
101    ///   when returning the list of rules.
102    fn handle_message(
103        &self,
104        request: ClientRequest,
105        registry: &ActorRegistry,
106        msg_type: &str,
107        msg: &Map<String, Value>,
108        _id: StreamId,
109    ) -> Result<(), ActorError> {
110        match msg_type {
111            "setRuleText" => {
112                // Parse the modifications sent from the client
113                let mods = msg
114                    .get("modifications")
115                    .ok_or(ActorError::MissingParameter)?
116                    .as_array()
117                    .ok_or(ActorError::BadParameterType)?;
118                let modifications: Vec<_> = mods
119                    .iter()
120                    .filter_map(|json_mod| {
121                        serde_json::from_str(&serde_json::to_string(json_mod).ok()?).ok()
122                    })
123                    .collect();
124
125                // Query the rule modification
126                let node_actor = registry.find::<NodeActor>(&self.node_name);
127                let walker = registry.find::<WalkerActor>(&node_actor.walker_name);
128                let browsing_context_actor = walker.browsing_context_actor(registry);
129                browsing_context_actor
130                    .script_chan()
131                    .send(ModifyRule(
132                        browsing_context_actor.pipeline_id(),
133                        registry.actor_to_script(self.node_name.clone()),
134                        modifications,
135                    ))
136                    .map_err(|_| ActorError::Internal)?;
137
138                request.reply_final(&self.encode(registry))?
139            },
140            _ => return Err(ActorError::UnrecognizedPacketType),
141        };
142        Ok(())
143    }
144}
145
146impl StyleRuleActor {
147    pub fn register(
148        registry: &ActorRegistry,
149        node: String,
150        selector: Option<MatchedRule>,
151    ) -> Arc<Self> {
152        let name = new_actor_name::<Self>();
153        let actor = Self {
154            name,
155            node_name: node,
156            selector,
157        };
158        registry.register::<Self>(actor)
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_name);
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().into(),
197            ancestor_data: self
198                .selector
199                .as_ref()
200                .map(|r| r.ancestor_data.clone())
201                .unwrap_or_default(),
202            authored_text: String::new(),
203            css_text: String::new(), // 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: String::new(),
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_name);
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().into(),
270            rule: self.applied(registry),
271        }
272    }
273}