devtools/actors/inspector/
page_style.rs1use std::collections::HashMap;
9use std::collections::hash_map::Entry;
10use std::iter::once;
11
12use devtools_traits::DevtoolScriptControlMsg::{GetLayout, GetSelectors};
13use devtools_traits::{AutoMargins, ComputedNodeLayout};
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};
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) -> String {
100 self.name.clone()
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 fn get_applied(
134 &self,
135 request: ClientRequest,
136 msg: &Map<String, Value>,
137 registry: &ActorRegistry,
138 ) -> Result<(), ActorError> {
139 let target = msg
140 .get("node")
141 .ok_or(ActorError::MissingParameter)?
142 .as_str()
143 .ok_or(ActorError::BadParameterType)?;
144 let node_actor = registry.find::<NodeActor>(target);
145 let walker = registry.find::<WalkerActor>(&node_actor.walker);
146 let browsing_context_actor = walker.browsing_context_actor(registry);
147 let entries: Vec<_> = find_child(
148 &node_actor.script_chan,
149 node_actor.pipeline,
150 target,
151 registry,
152 &walker.root(registry)?.actor,
153 vec![],
154 |msg| msg.actor == target,
155 )
156 .unwrap_or_default()
157 .into_iter()
158 .flat_map(|node| {
159 let inherited = (node.actor != target).then(|| node.actor.clone());
160 let node_actor = registry.find::<NodeActor>(&node.actor);
161
162 let selectors = (|| {
164 let (selectors_sender, selector_receiver) = generic_channel::channel()?;
165 browsing_context_actor
166 .script_chan()
167 .send(GetSelectors(
168 browsing_context_actor.pipeline_id(),
169 registry.actor_to_script(node.actor.clone()),
170 selectors_sender,
171 ))
172 .ok()?;
173 selector_receiver.recv().ok()?
174 })()
175 .unwrap_or_default();
176
177 once(("".into(), usize::MAX))
181 .chain(selectors)
182 .filter_map(move |selector| {
183 let rule = match node_actor.style_rules.borrow_mut().entry(selector) {
184 Entry::Vacant(e) => {
185 let name = registry.new_name::<StyleRuleActor>();
186 let actor = StyleRuleActor::new(
187 name.clone(),
188 node_actor.name(),
189 (!e.key().0.is_empty()).then_some(e.key().clone()),
190 );
191 let rule = actor.applied(registry)?;
192
193 registry.register(actor);
194 e.insert(name);
195 rule
196 },
197 Entry::Occupied(e) => {
198 let actor = registry.find::<StyleRuleActor>(e.get());
199 actor.applied(registry)?
200 },
201 };
202 if inherited.is_some() && rule.declarations.is_empty() {
203 return None;
204 }
205
206 Some(AppliedEntry {
207 rule,
208 pseudo_element: None,
210 is_system: false,
211 inherited: inherited.clone(),
212 })
213 })
214 })
215 .collect();
216 let msg = GetAppliedReply {
217 entries,
218 from: self.name(),
219 };
220 request.reply_final(&msg)
221 }
222
223 fn get_computed(
224 &self,
225 request: ClientRequest,
226 msg: &Map<String, Value>,
227 registry: &ActorRegistry,
228 ) -> Result<(), ActorError> {
229 let target = msg
230 .get("node")
231 .ok_or(ActorError::MissingParameter)?
232 .as_str()
233 .ok_or(ActorError::BadParameterType)?;
234 let node_actor = registry.find::<NodeActor>(target);
235 let computed = (|| match node_actor
236 .style_rules
237 .borrow_mut()
238 .entry(("".into(), usize::MAX))
239 {
240 Entry::Vacant(e) => {
241 let name = registry.new_name::<StyleRuleActor>();
242 let actor = StyleRuleActor::new(name.clone(), target.into(), None);
243 let computed = actor.computed(registry)?;
244 registry.register(actor);
245 e.insert(name);
246 Some(computed)
247 },
248 Entry::Occupied(e) => {
249 let actor = registry.find::<StyleRuleActor>(e.get());
250 Some(actor.computed(registry)?)
251 },
252 })()
253 .unwrap_or_default();
254 let msg = GetComputedReply {
255 computed,
256 from: self.name(),
257 };
258 request.reply_final(&msg)
259 }
260
261 fn get_layout(
262 &self,
263 request: ClientRequest,
264 msg: &Map<String, Value>,
265 registry: &ActorRegistry,
266 ) -> Result<(), ActorError> {
267 let target = msg
268 .get("node")
269 .ok_or(ActorError::MissingParameter)?
270 .as_str()
271 .ok_or(ActorError::BadParameterType)?;
272 let node_actor = registry.find::<NodeActor>(target);
273 let walker = registry.find::<WalkerActor>(&node_actor.walker);
274 let browsing_context_actor = walker.browsing_context_actor(registry);
275 let (tx, rx) = generic_channel::channel().ok_or(ActorError::Internal)?;
276 browsing_context_actor
277 .script_chan()
278 .send(GetLayout(
279 browsing_context_actor.pipeline_id(),
280 registry.actor_to_script(target.to_owned()),
281 tx,
282 ))
283 .map_err(|_| ActorError::Internal)?;
284 let (layout, auto_margins) = rx
285 .recv()
286 .map_err(|_| ActorError::Internal)?
287 .ok_or(ActorError::Internal)?;
288 request.reply_final(&GetLayoutReply {
289 from: self.name(),
290 layout,
291 auto_margins: auto_margins.into(),
292 })
293 }
294
295 fn is_position_editable(&self, request: ClientRequest) -> Result<(), ActorError> {
296 let msg = IsPositionEditableReply {
297 from: self.name(),
298 value: false,
299 };
300 request.reply_final(&msg)
301 }
302}
303
304impl ActorEncode<PageStyleMsg> for PageStyleActor {
305 fn encode(&self, _: &ActorRegistry) -> PageStyleMsg {
306 PageStyleMsg {
307 actor: self.name(),
308 traits: HashMap::from([
309 ("fontStretchLevel4".into(), true),
310 ("fontStyleLevel4".into(), true),
311 ("fontVariations".into(), true),
312 ("fontWeightLevel4".into(), true),
313 ]),
314 }
315 }
316}