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