devtools/actors/inspector/
page_style.rs1use std::collections::HashMap;
9use std::iter::once;
10use std::sync::Arc;
11
12use devtools_traits::DevtoolScriptControlMsg::{GetLayout, GetSelectors};
13use devtools_traits::{AutoMargins, ComputedNodeLayout, MatchedRule};
14use malloc_size_of_derive::MallocSizeOf;
15use serde::Serialize;
16use serde_json::{self, Map, Value};
17use servo_base::generic_channel::{self};
18
19use crate::StreamId;
20use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry, new_actor_name};
21use crate::actors::inspector::node::NodeActor;
22use crate::actors::inspector::style_rule::{AppliedRule, ComputedDeclaration, StyleRuleActor};
23use crate::actors::inspector::walker::{WalkerActor, find_child};
24use crate::protocol::ClientRequest;
25
26#[derive(Serialize)]
27struct GetAppliedReply {
28 entries: Vec<AppliedEntry>,
29 from: String,
30}
31
32#[derive(Serialize)]
33struct GetComputedReply {
34 computed: HashMap<String, ComputedDeclaration>,
35 from: String,
36}
37
38#[derive(Serialize)]
39#[serde(rename_all = "camelCase")]
40struct AppliedEntry {
41 rule: AppliedRule,
42 pseudo_element: Option<()>,
43 is_system: bool,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 inherited: Option<String>,
46}
47
48#[derive(Serialize)]
49struct DevtoolsAutoMargins {
50 #[serde(skip_serializing_if = "Option::is_none")]
51 top: Option<String>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 right: Option<String>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 bottom: Option<String>,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 left: Option<String>,
58}
59
60impl From<AutoMargins> for DevtoolsAutoMargins {
61 fn from(auto_margins: AutoMargins) -> Self {
62 const AUTO: &str = "auto";
63 Self {
64 top: auto_margins.top.then_some(AUTO.into()),
65 right: auto_margins.right.then_some(AUTO.into()),
66 bottom: auto_margins.bottom.then_some(AUTO.into()),
67 left: auto_margins.left.then_some(AUTO.into()),
68 }
69 }
70}
71
72#[derive(Serialize)]
73struct GetLayoutReply {
74 from: String,
75 #[serde(flatten)]
76 layout: ComputedNodeLayout,
77 #[serde(rename = "autoMargins")]
78 auto_margins: DevtoolsAutoMargins,
79}
80
81#[derive(Serialize)]
82pub(crate) struct IsPositionEditableReply {
83 from: String,
84 value: bool,
85}
86
87#[derive(Serialize)]
88pub(crate) struct PageStyleMsg {
89 pub actor: String,
90 pub traits: HashMap<String, bool>,
91}
92
93#[derive(MallocSizeOf)]
94pub(crate) struct PageStyleActor {
95 pub name: String,
96}
97
98impl Actor for PageStyleActor {
99 fn name(&self) -> &str {
100 &self.name
101 }
102
103 fn handle_message(
115 &self,
116 request: ClientRequest,
117 registry: &ActorRegistry,
118 msg_type: &str,
119 msg: &Map<String, Value>,
120 _id: StreamId,
121 ) -> Result<(), ActorError> {
122 match msg_type {
123 "getApplied" => self.get_applied(request, msg, registry),
124 "getComputed" => self.get_computed(request, msg, registry),
125 "getLayout" => self.get_layout(request, msg, registry),
126 "isPositionEditable" => self.is_position_editable(request),
127 _ => Err(ActorError::UnrecognizedPacketType),
128 }
129 }
130}
131
132impl PageStyleActor {
133 pub fn register(registry: &ActorRegistry) -> Arc<Self> {
134 let name = new_actor_name::<Self>();
135 let actor = Self { name };
136 registry.register::<Self>(actor)
137 }
138
139 fn get_applied(
140 &self,
141 request: ClientRequest,
142 msg: &Map<String, Value>,
143 registry: &ActorRegistry,
144 ) -> Result<(), ActorError> {
145 let node_name = msg
146 .get("node")
147 .ok_or(ActorError::MissingParameter)?
148 .as_str()
149 .ok_or(ActorError::BadParameterType)?;
150 let node_actor = registry.find::<NodeActor>(node_name);
151 let walker = registry.find::<WalkerActor>(&node_actor.walker_name);
152 let browsing_context_actor = walker.browsing_context_actor(registry);
153 let entries: Vec<_> = find_child(
154 &node_actor.walker_name,
155 registry,
156 &walker.root(registry)?.actor,
157 vec![],
158 |msg| msg.actor == node_name,
159 )
160 .unwrap_or_default()
161 .into_iter()
162 .flat_map(|node| {
163 let inherited = (node.actor != node_name).then(|| node.actor.clone());
164 let node_actor = registry.find::<NodeActor>(&node.actor);
165
166 let selectors = (|| {
168 let (selectors_sender, selector_receiver) = generic_channel::channel()?;
169 browsing_context_actor
170 .script_chan()
171 .send(GetSelectors(
172 browsing_context_actor.pipeline_id(),
173 registry.actor_to_script(node.actor.clone()),
174 selectors_sender,
175 ))
176 .ok()?;
177 selector_receiver.recv().ok()?
178 })()
179 .unwrap_or_default();
180
181 let style_attribute_rule = MatchedRule {
185 selector: String::new(),
186 stylesheet_index: usize::MAX,
187 block_id: 0,
188 ancestor_data: vec![],
189 };
190
191 once(style_attribute_rule)
192 .chain(selectors)
193 .filter_map(move |matched_rule| {
194 let style_rule_name = node_actor
195 .style_rules
196 .borrow_mut()
197 .entry(matched_rule.clone())
198 .or_insert_with(|| {
199 StyleRuleActor::register(
200 registry,
201 node_actor.name().into(),
202 (matched_rule.stylesheet_index != usize::MAX)
203 .then_some(matched_rule.clone()),
204 )
205 .name()
206 .into()
207 })
208 .clone();
209
210 let rule = registry
211 .find::<StyleRuleActor>(&style_rule_name)
212 .applied(registry)?;
213 if inherited.is_some() && rule.declarations.is_empty() {
214 return None;
215 }
216
217 Some(AppliedEntry {
218 rule,
219 pseudo_element: None,
221 is_system: false,
222 inherited: inherited.clone(),
223 })
224 })
225 })
226 .collect();
227 let msg = GetAppliedReply {
228 entries,
229 from: self.name().into(),
230 };
231 request.reply_final(&msg)
232 }
233
234 fn get_computed(
235 &self,
236 request: ClientRequest,
237 msg: &Map<String, Value>,
238 registry: &ActorRegistry,
239 ) -> Result<(), ActorError> {
240 let node_name = msg
241 .get("node")
242 .ok_or(ActorError::MissingParameter)?
243 .as_str()
244 .ok_or(ActorError::BadParameterType)?;
245 let node_actor = registry.find::<NodeActor>(node_name);
246 let style_attribute_rule = devtools_traits::MatchedRule {
247 selector: String::new(),
248 stylesheet_index: usize::MAX,
249 block_id: 0,
250 ancestor_data: vec![],
251 };
252
253 let style_rule_name = node_actor
254 .style_rules
255 .borrow_mut()
256 .entry(style_attribute_rule)
257 .or_insert_with(|| {
258 StyleRuleActor::register(registry, node_name.into(), None)
259 .name()
260 .into()
261 })
262 .clone();
263 let computed = registry
264 .find::<StyleRuleActor>(&style_rule_name)
265 .computed(registry)
266 .unwrap_or_default();
267
268 let msg = GetComputedReply {
269 computed,
270 from: self.name().into(),
271 };
272 request.reply_final(&msg)
273 }
274
275 fn get_layout(
276 &self,
277 request: ClientRequest,
278 msg: &Map<String, Value>,
279 registry: &ActorRegistry,
280 ) -> Result<(), ActorError> {
281 let node_name = msg
282 .get("node")
283 .ok_or(ActorError::MissingParameter)?
284 .as_str()
285 .ok_or(ActorError::BadParameterType)?;
286 let node_actor = registry.find::<NodeActor>(node_name);
287 let walker = registry.find::<WalkerActor>(&node_actor.walker_name);
288 let browsing_context_actor = walker.browsing_context_actor(registry);
289 let (tx, rx) = generic_channel::channel().ok_or(ActorError::Internal)?;
290 browsing_context_actor
291 .script_chan()
292 .send(GetLayout(
293 browsing_context_actor.pipeline_id(),
294 registry.actor_to_script(node_name.to_owned()),
295 tx,
296 ))
297 .map_err(|_| ActorError::Internal)?;
298 let (layout, auto_margins) = rx
299 .recv()
300 .map_err(|_| ActorError::Internal)?
301 .ok_or(ActorError::Internal)?;
302 request.reply_final(&GetLayoutReply {
303 from: self.name().into(),
304 layout,
305 auto_margins: auto_margins.into(),
306 })
307 }
308
309 fn is_position_editable(&self, request: ClientRequest) -> Result<(), ActorError> {
310 let msg = IsPositionEditableReply {
311 from: self.name().into(),
312 value: false,
313 };
314 request.reply_final(&msg)
315 }
316}
317
318impl ActorEncode<PageStyleMsg> for PageStyleActor {
319 fn encode(&self, _: &ActorRegistry) -> PageStyleMsg {
320 PageStyleMsg {
321 actor: self.name().into(),
322 traits: HashMap::from([
323 ("fontStretchLevel4".into(), true),
324 ("fontStyleLevel4".into(), true),
325 ("fontVariations".into(), true),
326 ("fontWeightLevel4".into(), true),
327 ]),
328 }
329 }
330}