1use std::cell::{Ref, RefCell, RefMut};
6use std::collections::HashMap;
7use std::str;
8
9use devtools_traits::{
10 AncestorData, AttrModification, AutoMargins, ComputedNodeLayout, CssDatabaseProperty,
11 EventListenerInfo, MatchedRule, NodeInfo, NodeStyle, RuleModification, StyleSheetInfo,
12 TimelineMarker, TimelineMarkerType,
13};
14use js::context::JSContext;
15use markup5ever::{LocalName, ns};
16use rustc_hash::FxHashMap;
17use script_bindings::codegen::GenericBindings::CSSRuleBinding::CSSRuleMethods;
18use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
19use script_bindings::root::Dom;
20use servo_base::generic_channel::GenericSender;
21use servo_base::id::PipelineId;
22use servo_config::pref;
23use style::attr::AttrValue;
24use style::stylesheets::Origin;
25
26use crate::document_collection::DocumentCollection;
27use crate::dom::bindings::codegen::Bindings::CSSGroupingRuleBinding::CSSGroupingRuleMethods;
28use crate::dom::bindings::codegen::Bindings::CSSLayerBlockRuleBinding::CSSLayerBlockRuleMethods;
29use crate::dom::bindings::codegen::Bindings::CSSRuleListBinding::CSSRuleListMethods;
30use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
31use crate::dom::bindings::codegen::Bindings::CSSStyleRuleBinding::CSSStyleRuleMethods;
32use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::CSSStyleSheetMethods;
33use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
34use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
35use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
36use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
37use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeConstants;
38use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
39use crate::dom::bindings::inheritance::Castable;
40use crate::dom::bindings::root::DomRoot;
41use crate::dom::bindings::str::DOMString;
42use crate::dom::bindings::trace::NoTrace;
43use crate::dom::css::cssstyledeclaration::ENABLED_LONGHAND_PROPERTIES;
44use crate::dom::css::cssstylerule::CSSStyleRule;
45use crate::dom::document::AnimationFrameCallback;
46use crate::dom::element::Element;
47use crate::dom::iterators::ShadowIncluding;
48use crate::dom::node::{Node, NodeTraits};
49use crate::dom::types::{CSSGroupingRule, CSSLayerBlockRule, EventTarget, HTMLElement};
50use crate::realms::enter_realm;
51
52#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
53#[derive(JSTraceable)]
54pub(crate) struct PerPipelineState {
55 #[no_trace]
56 pipeline: PipelineId,
57
58 known_nodes: FxHashMap<String, Dom<Node>>,
60}
61
62#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
63#[derive(JSTraceable, Default)]
64pub(crate) struct DevtoolsState {
65 per_pipeline_state: RefCell<FxHashMap<NoTrace<PipelineId>, PerPipelineState>>,
66}
67
68impl PerPipelineState {
69 fn register_node(&mut self, node: &Node) {
70 let unique_id = node.unique_id(self.pipeline);
71 self.known_nodes
72 .entry(unique_id)
73 .or_insert_with(|| Dom::from_ref(node));
74 }
75}
76
77impl DevtoolsState {
78 pub(crate) fn notify_pipeline_created(&self, pipeline: PipelineId) {
79 self.per_pipeline_state.borrow_mut().insert(
80 NoTrace(pipeline),
81 PerPipelineState {
82 pipeline,
83 known_nodes: Default::default(),
84 },
85 );
86 }
87 pub(crate) fn notify_pipeline_exited(&self, pipeline: PipelineId) {
88 self.per_pipeline_state
89 .borrow_mut()
90 .remove(&NoTrace(pipeline));
91 }
92
93 fn pipeline_state_for(&self, pipeline: PipelineId) -> Option<Ref<'_, PerPipelineState>> {
94 Ref::filter_map(self.per_pipeline_state.borrow(), |state| {
95 state.get(&NoTrace(pipeline))
96 })
97 .ok()
98 }
99
100 fn mut_pipeline_state_for(&self, pipeline: PipelineId) -> Option<RefMut<'_, PerPipelineState>> {
101 RefMut::filter_map(self.per_pipeline_state.borrow_mut(), |state| {
102 state.get_mut(&NoTrace(pipeline))
103 })
104 .ok()
105 }
106
107 pub(crate) fn wants_updates_for_node(&self, pipeline: PipelineId, node: &Node) -> bool {
108 let Some(unique_id) = node.unique_id_if_already_present() else {
109 return false;
112 };
113 self.pipeline_state_for(pipeline)
114 .is_some_and(|pipeline_state| pipeline_state.known_nodes.contains_key(&unique_id))
115 }
116
117 fn find_node_by_unique_id(&self, pipeline: PipelineId, node_id: &str) -> Option<DomRoot<Node>> {
118 self.pipeline_state_for(pipeline)?
119 .known_nodes
120 .get(node_id)
121 .map(|node: &Dom<Node>| node.as_rooted())
122 }
123}
124
125pub(crate) fn handle_set_timeline_markers(
126 documents: &DocumentCollection,
127 pipeline: PipelineId,
128 marker_types: Vec<TimelineMarkerType>,
129 reply: GenericSender<Option<TimelineMarker>>,
130) {
131 match documents.find_window(pipeline) {
132 None => reply.send(None).unwrap(),
133 Some(window) => window.set_devtools_timeline_markers(marker_types, reply),
134 }
135}
136
137pub(crate) fn handle_drop_timeline_markers(
138 documents: &DocumentCollection,
139 pipeline: PipelineId,
140 marker_types: Vec<TimelineMarkerType>,
141) {
142 if let Some(window) = documents.find_window(pipeline) {
143 window.drop_devtools_timeline_markers(marker_types);
144 }
145}
146
147pub(crate) fn handle_request_animation_frame(
148 documents: &DocumentCollection,
149 id: PipelineId,
150 actor_name: String,
151) {
152 if let Some(doc) = documents.find_document(id) {
153 doc.request_animation_frame(AnimationFrameCallback::DevtoolsFramerateTick { actor_name });
154 }
155}
156
157pub(crate) fn handle_get_css_database(reply: GenericSender<HashMap<String, CssDatabaseProperty>>) {
158 let database: HashMap<_, _> = ENABLED_LONGHAND_PROPERTIES
159 .iter()
160 .map(|l| {
161 (
162 l.name().into(),
163 CssDatabaseProperty {
164 is_inherited: l.inherited(),
165 values: vec![], supports: vec![],
167 subproperties: vec![l.name().into()],
168 },
169 )
170 })
171 .collect();
172 let _ = reply.send(database);
173}
174
175pub(crate) fn handle_get_event_listener_info(
176 state: &DevtoolsState,
177 pipeline: PipelineId,
178 node_id: &str,
179 reply: GenericSender<Vec<EventListenerInfo>>,
180) {
181 let Some(node) = state.find_node_by_unique_id(pipeline, node_id) else {
182 reply.send(vec![]).unwrap();
183 return;
184 };
185
186 let event_listeners = node
187 .upcast::<EventTarget>()
188 .summarize_event_listeners_for_devtools();
189 reply.send(event_listeners).unwrap();
190}
191
192pub(crate) fn handle_get_root_node(
193 cx: &mut JSContext,
194 state: &DevtoolsState,
195 documents: &DocumentCollection,
196 pipeline: PipelineId,
197 reply: GenericSender<Option<NodeInfo>>,
198) {
199 let info = documents
200 .find_document(pipeline)
201 .map(DomRoot::upcast::<Node>)
202 .inspect(|node| {
203 state
204 .mut_pipeline_state_for(pipeline)
205 .unwrap()
206 .register_node(node)
207 })
208 .map(|document| document.upcast::<Node>().summarize(cx));
209 reply.send(info).unwrap();
210}
211
212pub(crate) fn handle_get_document_element(
213 cx: &mut JSContext,
214 state: &DevtoolsState,
215 documents: &DocumentCollection,
216 pipeline: PipelineId,
217 reply: GenericSender<Option<NodeInfo>>,
218) {
219 let info = documents
220 .find_document(pipeline)
221 .and_then(|document| document.GetDocumentElement())
222 .inspect(|element| {
223 state
224 .mut_pipeline_state_for(pipeline)
225 .unwrap()
226 .register_node(element.upcast())
227 })
228 .map(|element| element.upcast::<Node>().summarize(cx));
229 reply.send(info).unwrap();
230}
231
232pub(crate) fn handle_get_stylesheets(
233 documents: &DocumentCollection,
234 pipeline: PipelineId,
235 reply: GenericSender<Vec<StyleSheetInfo>>,
236) {
237 let mut stylesheets = vec![];
238 if let Some(document) = documents.find_document(pipeline) {
239 let node = document.upcast::<Node>();
240 for i in 0..node.stylesheet_list_owner().stylesheet_count() {
241 if let Some(s) = node.stylesheet_list_owner().stylesheet_at(i) {
242 stylesheets.push(StyleSheetInfo {
243 href: s.href().map(String::from),
244 disabled: s.disabled(),
245 title: String::from(s.title()),
246 style_sheet_index: i as i32,
247 system: s.origin() == Origin::UserAgent,
248 rule_count: s.get_rule_count(),
249 });
250 }
251 }
252 }
253 reply.send(stylesheets).unwrap();
254}
255
256pub(crate) fn handle_get_stylesheet_text(
257 cx: &mut JSContext,
258 documents: &DocumentCollection,
259 pipeline: PipelineId,
260 index: i32,
261 reply: GenericSender<Option<String>>,
262) {
263 let text = (|| {
264 let document = documents.find_document(pipeline)?;
265 let stylesheet = document
266 .upcast::<Node>()
267 .stylesheet_list_owner()
268 .stylesheet_at(index as usize)?;
269
270 if let Some(node) = stylesheet.owner_node() {
272 let text = node.upcast::<Node>().GetTextContent().unwrap_or_default();
273 if !text.is_empty() {
274 return Some(String::from(text));
275 }
276 }
277
278 let rules = stylesheet.rulelist(cx);
280 let mut css_text = String::new();
281 for i in 0..rules.Length() {
282 if let Some(rule) = rules.Item(cx, i) {
283 css_text.push_str(&rule.CssText().str());
284 css_text.push('\n');
285 }
286 }
287 Some(css_text)
288 })();
289 reply.send(text).unwrap();
290}
291
292pub(crate) fn handle_get_children(
293 cx: &mut JSContext,
294 state: &DevtoolsState,
295 pipeline: PipelineId,
296 node_id: &str,
297 reply: GenericSender<Option<Vec<NodeInfo>>>,
298) {
299 let Some(parent) = state.find_node_by_unique_id(pipeline, node_id) else {
300 reply.send(None).unwrap();
301 return;
302 };
303 let is_whitespace = |node: &NodeInfo| {
304 node.node_type == NodeConstants::TEXT_NODE &&
305 node.node_value.as_ref().is_none_or(|v| v.trim().is_empty())
306 };
307 let mut pipeline_state = state.mut_pipeline_state_for(pipeline).unwrap();
308
309 let inline: Vec<_> = parent
310 .children()
311 .map(|child| {
312 let window = child.owner_window();
313 let Some(elem) = child.downcast::<Element>() else {
314 return false;
315 };
316 let computed_style = window.GetComputedStyle(cx, elem, None);
317 let display = computed_style.Display();
318 display == "inline"
319 })
320 .collect();
321
322 let mut children = vec![];
323 if let Some(shadow_root) = parent.downcast::<Element>().and_then(Element::shadow_root) &&
324 (!shadow_root.is_user_agent_widget() ||
325 pref!(inspector_show_servo_internal_shadow_roots))
326 {
327 children.push(shadow_root.upcast::<Node>().summarize(cx));
328 }
329 let children_iter = parent.children().enumerate().filter_map(|(i, child)| {
330 let prev_inline = i > 0 && inline[i - 1];
333 let next_inline = i < inline.len() - 1 && inline[i + 1];
334 let is_inline_level = prev_inline && next_inline;
335
336 let info = child.summarize(cx);
337 if is_whitespace(&info) && !is_inline_level {
338 return None;
339 }
340 pipeline_state.register_node(&child);
341
342 Some(info)
343 });
344 children.extend(children_iter);
345
346 reply.send(Some(children)).unwrap();
347}
348
349pub(crate) fn handle_get_attribute_style(
350 cx: &mut JSContext,
351 state: &DevtoolsState,
352 pipeline: PipelineId,
353 node_id: &str,
354 reply: GenericSender<Option<Vec<NodeStyle>>>,
355) {
356 let node = match state.find_node_by_unique_id(pipeline, node_id) {
357 None => return reply.send(None).unwrap(),
358 Some(found_node) => found_node,
359 };
360
361 let Some(elem) = node.downcast::<HTMLElement>() else {
362 reply.send(None).unwrap();
364 return;
365 };
366 let style = elem.Style(cx);
367
368 let msg = (0..style.Length())
369 .map(|i| {
370 let name = style.Item(i);
371 NodeStyle {
372 value: String::from(style.GetPropertyValue(name.clone())),
379 priority: String::from(style.GetPropertyPriority(name.clone())),
380 name: String::from(name),
381 }
382 })
383 .collect();
384
385 reply.send(Some(msg)).unwrap();
386}
387
388fn build_rule_map(
389 cx: &mut JSContext,
390 list: &crate::dom::css::cssrulelist::CSSRuleList,
391 stylesheet_index: usize,
392 ancestors: &[AncestorData],
393 map: &mut HashMap<usize, MatchedRule>,
394) {
395 for i in 0..list.Length() {
396 let Some(rule) = list.Item(cx, i) else {
397 continue;
398 };
399
400 if let Some(style_rule) = rule.downcast::<CSSStyleRule>() {
401 let block_id = style_rule.block_id();
402 map.entry(block_id).or_insert_with(|| MatchedRule {
403 selector: style_rule.SelectorText().into(),
404 stylesheet_index,
405 block_id,
406 ancestor_data: ancestors.to_vec(),
407 });
408 continue;
409 }
410
411 if let Some(layer_rule) = rule.downcast::<CSSLayerBlockRule>() {
412 let name = String::from(layer_rule.Name());
413 let mut next = ancestors.to_vec();
414 next.push(AncestorData::Layer {
415 actor_id: None,
416 value: (!name.is_empty()).then_some(name),
417 });
418 let inner = layer_rule.upcast::<CSSGroupingRule>().CssRules(cx);
419 build_rule_map(cx, &inner, stylesheet_index, &next, map);
420 continue;
421 }
422
423 if let Some(group_rule) = rule.downcast::<CSSGroupingRule>() {
424 let inner = group_rule.CssRules(cx);
425 build_rule_map(cx, &inner, stylesheet_index, ancestors, map);
426 }
427 }
428}
429
430fn find_rule_by_block_id(
431 cx: &mut JSContext,
432 list: &crate::dom::css::cssrulelist::CSSRuleList,
433 target_block_id: usize,
434) -> Option<DomRoot<CSSStyleRule>> {
435 for i in 0..list.Length() {
436 let Some(rule) = list.Item(cx, i) else {
437 continue;
438 };
439
440 if let Some(style_rule) = rule.downcast::<CSSStyleRule>() {
441 if style_rule.block_id() == target_block_id {
442 return Some(DomRoot::from_ref(style_rule));
443 }
444 continue;
445 }
446
447 if let Some(group_rule) = rule.downcast::<CSSGroupingRule>() {
448 let inner = group_rule.CssRules(cx);
449 if let Some(found) = find_rule_by_block_id(cx, &inner, target_block_id) {
450 return Some(found);
451 }
452 }
453 }
454 None
455}
456
457#[cfg_attr(crown, expect(crown::unrooted_must_root))]
458pub(crate) fn handle_get_selectors(
459 cx: &mut JSContext,
460 state: &DevtoolsState,
461 documents: &DocumentCollection,
462 pipeline: PipelineId,
463 node_id: &str,
464 reply: GenericSender<Option<Vec<MatchedRule>>>,
465) {
466 let msg = (|| {
467 let node = state.find_node_by_unique_id(pipeline, node_id)?;
468 let elem = node.downcast::<Element>()?;
469 let document = documents.find_document(pipeline)?;
470 let _realm = enter_realm(document.window());
471 let owner = node.stylesheet_list_owner();
472
473 let mut decl_map = HashMap::new();
474 for i in 0..owner.stylesheet_count() {
475 let Some(stylesheet) = owner.stylesheet_at(i) else {
476 continue;
477 };
478 let Ok(list) = stylesheet.GetCssRules(cx) else {
479 continue;
480 };
481 build_rule_map(cx, &list, i, &[], &mut decl_map);
482 }
483
484 let mut rules = Vec::new();
485 let computed = elem.style()?;
486
487 if let Some(rule_node) = computed.rules.as_ref() {
488 for rn in rule_node.self_and_ancestors() {
489 if let Some(source) = rn.style_source() {
490 let ptr = source.get().raw_ptr().as_ptr() as usize;
491
492 if let Some(matched) = decl_map.get(&ptr) {
493 rules.push(matched.clone());
494 }
495 }
496 }
497 }
498
499 Some(rules)
500 })();
501
502 reply.send(msg).unwrap();
503}
504
505#[cfg_attr(crown, expect(crown::unrooted_must_root))]
506#[allow(clippy::too_many_arguments)]
507pub(crate) fn handle_get_stylesheet_style(
508 cx: &mut JSContext,
509 state: &DevtoolsState,
510 documents: &DocumentCollection,
511 pipeline: PipelineId,
512 node_id: &str,
513 matched_rule: MatchedRule,
514 reply: GenericSender<Option<Vec<NodeStyle>>>,
515) {
516 let msg = (|| {
517 let node = state.find_node_by_unique_id(pipeline, node_id)?;
518 let document = documents.find_document(pipeline)?;
519 let _realm = enter_realm(document.window());
520 let owner = node.stylesheet_list_owner();
521
522 let stylesheet = owner.stylesheet_at(matched_rule.stylesheet_index)?;
523 let list = stylesheet.GetCssRules(cx).ok()?;
524
525 let style_rule = find_rule_by_block_id(cx, &list, matched_rule.block_id)?;
526 let declaration = style_rule.Style(cx);
527
528 Some(
529 (0..declaration.Length())
530 .map(|i| {
531 let name = declaration.Item(i);
532 NodeStyle {
533 value: String::from(declaration.GetPropertyValue(name.clone())),
534 priority: String::from(declaration.GetPropertyPriority(name.clone())),
535 name: String::from(name),
536 }
537 })
538 .collect(),
539 )
540 })();
541
542 reply.send(msg).unwrap();
543}
544
545pub(crate) fn handle_get_computed_style(
546 cx: &mut JSContext,
547 state: &DevtoolsState,
548 pipeline: PipelineId,
549 node_id: &str,
550 reply: GenericSender<Option<Vec<NodeStyle>>>,
551) {
552 let node = match state.find_node_by_unique_id(pipeline, node_id) {
553 None => return reply.send(None).unwrap(),
554 Some(found_node) => found_node,
555 };
556
557 let window = node.owner_window();
558 let elem = node
559 .downcast::<Element>()
560 .expect("This should be an element");
561 let computed_style = window.GetComputedStyle(cx, elem, None);
562
563 let msg = (0..computed_style.Length())
564 .map(|i| {
565 let name = computed_style.Item(i);
566 NodeStyle {
567 value: String::from(computed_style.GetPropertyValue(name.clone())),
568 priority: String::from(computed_style.GetPropertyPriority(name.clone())),
569 name: String::from(name),
570 }
571 })
572 .collect();
573
574 reply.send(Some(msg)).unwrap();
575}
576
577pub(crate) fn handle_get_layout(
578 cx: &mut JSContext,
579 state: &DevtoolsState,
580 pipeline: PipelineId,
581 node_id: &str,
582 reply: GenericSender<Option<(ComputedNodeLayout, AutoMargins)>>,
583) {
584 let node = match state.find_node_by_unique_id(pipeline, node_id) {
585 None => return reply.send(None).unwrap(),
586 Some(found_node) => found_node,
587 };
588
589 let element = node
590 .downcast::<Element>()
591 .expect("should be getting layout of element");
592
593 let rect = element.GetBoundingClientRect(cx);
594 let width = rect.Width() as f32;
595 let height = rect.Height() as f32;
596
597 let window = node.owner_window();
598 let computed_style = window.GetComputedStyle(cx, element, None);
599 let computed_layout = ComputedNodeLayout {
600 display: computed_style.Display().into(),
601 position: computed_style.Position().into(),
602 z_index: computed_style.ZIndex().into(),
603 box_sizing: computed_style.BoxSizing().into(),
604 margin_top: computed_style.MarginTop().into(),
605 margin_right: computed_style.MarginRight().into(),
606 margin_bottom: computed_style.MarginBottom().into(),
607 margin_left: computed_style.MarginLeft().into(),
608 border_top_width: computed_style.BorderTopWidth().into(),
609 border_right_width: computed_style.BorderRightWidth().into(),
610 border_bottom_width: computed_style.BorderBottomWidth().into(),
611 border_left_width: computed_style.BorderLeftWidth().into(),
612 padding_top: computed_style.PaddingTop().into(),
613 padding_right: computed_style.PaddingRight().into(),
614 padding_bottom: computed_style.PaddingBottom().into(),
615 padding_left: computed_style.PaddingLeft().into(),
616 width,
617 height,
618 };
619
620 let auto_margins = element.determine_auto_margins();
621 reply.send(Some((computed_layout, auto_margins))).unwrap();
622}
623
624pub(crate) fn handle_get_xpath(
625 state: &DevtoolsState,
626 pipeline: PipelineId,
627 node_id: &str,
628 reply: GenericSender<String>,
629) {
630 let Some(node) = state.find_node_by_unique_id(pipeline, node_id) else {
631 return reply.send(Default::default()).unwrap();
632 };
633
634 let selector = node
635 .inclusive_ancestors(ShadowIncluding::Yes)
636 .filter_map(|ancestor| {
637 let Some(element) = ancestor.downcast::<Element>() else {
638 return None;
640 };
641
642 let mut result = "/".to_owned();
643 if *element.namespace() != ns!(html) {
644 result.push_str(element.namespace());
645 result.push(':');
646 }
647
648 result.push_str(element.local_name());
649
650 let would_node_also_match_selector = |sibling: &Node| {
651 let Some(sibling) = sibling.downcast::<Element>() else {
652 return false;
653 };
654 sibling.namespace() == element.namespace() &&
655 sibling.local_name() == element.local_name()
656 };
657
658 let matching_elements_before = ancestor
659 .preceding_siblings()
660 .filter(|node| would_node_also_match_selector(node))
661 .count();
662 let matching_elements_after = ancestor
663 .following_siblings()
664 .filter(|node| would_node_also_match_selector(node))
665 .count();
666
667 if matching_elements_before + matching_elements_after != 0 {
668 result.push_str(&format!("[{}]", matching_elements_before + 1));
670 }
671
672 Some(result)
673 })
674 .collect::<Vec<_>>()
675 .into_iter()
676 .rev()
677 .collect::<Vec<_>>()
678 .join("");
679
680 reply.send(selector).unwrap();
681}
682
683pub(crate) fn handle_modify_attribute(
684 cx: &mut JSContext,
685 state: &DevtoolsState,
686 documents: &DocumentCollection,
687 pipeline: PipelineId,
688 node_id: &str,
689 modifications: Vec<AttrModification>,
690) {
691 let Some(document) = documents.find_document(pipeline) else {
692 return warn!("document for pipeline id {} is not found", &pipeline);
693 };
694 let _realm = enter_realm(document.window());
695
696 let node = match state.find_node_by_unique_id(pipeline, node_id) {
697 None => {
698 return warn!(
699 "node id {} for pipeline id {} is not found",
700 &node_id, &pipeline
701 );
702 },
703 Some(found_node) => found_node,
704 };
705
706 let elem = node
707 .downcast::<Element>()
708 .expect("should be getting layout of element");
709
710 for modification in modifications {
711 match modification.new_value {
712 Some(string) => {
713 elem.set_attribute(
714 cx,
715 &LocalName::from(modification.attribute_name),
716 AttrValue::String(string),
717 );
718 },
719 None => elem.RemoveAttribute(cx, DOMString::from(modification.attribute_name)),
720 }
721 }
722}
723
724pub(crate) fn handle_modify_rule(
725 cx: &mut JSContext,
726 state: &DevtoolsState,
727 documents: &DocumentCollection,
728 pipeline: PipelineId,
729 node_id: &str,
730 modifications: Vec<RuleModification>,
731) {
732 let Some(document) = documents.find_document(pipeline) else {
733 return warn!("Document for pipeline id {} is not found", &pipeline);
734 };
735 let _realm = enter_realm(document.window());
736
737 let Some(node) = state.find_node_by_unique_id(pipeline, node_id) else {
738 return warn!(
739 "Node id {} for pipeline id {} is not found",
740 &node_id, &pipeline
741 );
742 };
743
744 let elem = node
745 .downcast::<HTMLElement>()
746 .expect("This should be an HTMLElement");
747 let style = elem.Style(cx);
748
749 for modification in modifications {
750 let _ = style.SetProperty(
751 cx,
752 modification.name.into(),
753 modification.value.into(),
754 modification.priority.into(),
755 );
756 }
757}
758
759pub(crate) fn handle_highlight_dom_node(
760 state: &DevtoolsState,
761 documents: &DocumentCollection,
762 id: PipelineId,
763 node_id: Option<&str>,
764) {
765 let node = node_id.and_then(|node_id| {
766 let node = state.find_node_by_unique_id(id, node_id);
767 if node.is_none() {
768 log::warn!("Node id {node_id} for pipeline id {id} is not found",);
769 }
770 node
771 });
772
773 if let Some(window) = documents.find_window(id) {
774 window.Document().highlight_dom_node(node.as_deref());
775 }
776}
777
778impl Element {
779 fn determine_auto_margins(&self) -> AutoMargins {
780 let Some(style) = self.style() else {
781 return AutoMargins::default();
782 };
783 let margin = style.get_margin();
784 AutoMargins {
785 top: margin.margin_top.is_auto(),
786 right: margin.margin_right.is_auto(),
787 bottom: margin.margin_bottom.is_auto(),
788 left: margin.margin_left.is_auto(),
789 }
790 }
791}