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::node::{Node, NodeTraits, ShadowIncluding};
48use crate::dom::types::{CSSGroupingRule, CSSLayerBlockRule, EventTarget, HTMLElement};
49use crate::realms::enter_realm;
50use crate::script_runtime::CanGc;
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(|h| h.to_string()),
244 disabled: s.disabled(),
245 title: s.title().to_string(),
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(text.to_string());
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().to_string());
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_unrooted(cx.no_gc())
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(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(CanGc::from_cx(cx));
367
368 let msg = (0..style.Length())
369 .map(|i| {
370 let name = style.Item(i);
371 NodeStyle {
372 name: name.to_string(),
373 value: style.GetPropertyValue(name.clone()).to_string(),
374 priority: style.GetPropertyPriority(name).to_string(),
375 }
376 })
377 .collect();
378
379 reply.send(Some(msg)).unwrap();
380}
381
382fn build_rule_map(
383 cx: &mut JSContext,
384 list: &crate::dom::css::cssrulelist::CSSRuleList,
385 stylesheet_index: usize,
386 ancestors: &[AncestorData],
387 map: &mut HashMap<usize, MatchedRule>,
388) {
389 for i in 0..list.Length() {
390 let Some(rule) = list.Item(cx, i) else {
391 continue;
392 };
393
394 if let Some(style_rule) = rule.downcast::<CSSStyleRule>() {
395 let block_id = style_rule.block_id();
396 map.entry(block_id).or_insert_with(|| MatchedRule {
397 selector: style_rule.SelectorText().into(),
398 stylesheet_index,
399 block_id,
400 ancestor_data: ancestors.to_vec(),
401 });
402 continue;
403 }
404
405 if let Some(layer_rule) = rule.downcast::<CSSLayerBlockRule>() {
406 let name = layer_rule.Name().to_string();
407 let mut next = ancestors.to_vec();
408 next.push(AncestorData::Layer {
409 actor_id: None,
410 value: (!name.is_empty()).then_some(name),
411 });
412 let inner = layer_rule.upcast::<CSSGroupingRule>().CssRules(cx);
413 build_rule_map(cx, &inner, stylesheet_index, &next, map);
414 continue;
415 }
416
417 if let Some(group_rule) = rule.downcast::<CSSGroupingRule>() {
418 let inner = group_rule.CssRules(cx);
419 build_rule_map(cx, &inner, stylesheet_index, ancestors, map);
420 }
421 }
422}
423
424fn find_rule_by_block_id(
425 cx: &mut JSContext,
426 list: &crate::dom::css::cssrulelist::CSSRuleList,
427 target_block_id: usize,
428) -> Option<DomRoot<CSSStyleRule>> {
429 for i in 0..list.Length() {
430 let Some(rule) = list.Item(cx, i) else {
431 continue;
432 };
433
434 if let Some(style_rule) = rule.downcast::<CSSStyleRule>() {
435 if style_rule.block_id() == target_block_id {
436 return Some(DomRoot::from_ref(style_rule));
437 }
438 continue;
439 }
440
441 if let Some(group_rule) = rule.downcast::<CSSGroupingRule>() {
442 let inner = group_rule.CssRules(cx);
443 if let Some(found) = find_rule_by_block_id(cx, &inner, target_block_id) {
444 return Some(found);
445 }
446 }
447 }
448 None
449}
450
451#[cfg_attr(crown, expect(crown::unrooted_must_root))]
452pub(crate) fn handle_get_selectors(
453 cx: &mut JSContext,
454 state: &DevtoolsState,
455 documents: &DocumentCollection,
456 pipeline: PipelineId,
457 node_id: &str,
458 reply: GenericSender<Option<Vec<MatchedRule>>>,
459) {
460 let msg = (|| {
461 let node = state.find_node_by_unique_id(pipeline, node_id)?;
462 let elem = node.downcast::<Element>()?;
463 let document = documents.find_document(pipeline)?;
464 let _realm = enter_realm(document.window());
465 let owner = node.stylesheet_list_owner();
466
467 let mut decl_map = HashMap::new();
468 for i in 0..owner.stylesheet_count() {
469 let Some(stylesheet) = owner.stylesheet_at(i) else {
470 continue;
471 };
472 let Ok(list) = stylesheet.GetCssRules(cx) else {
473 continue;
474 };
475 build_rule_map(cx, &list, i, &[], &mut decl_map);
476 }
477
478 let mut rules = Vec::new();
479 let computed = elem.style()?;
480
481 if let Some(rule_node) = computed.rules.as_ref() {
482 for rn in rule_node.self_and_ancestors() {
483 if let Some(source) = rn.style_source() {
484 let ptr = source.get().raw_ptr().as_ptr() as usize;
485
486 if let Some(matched) = decl_map.get(&ptr) {
487 rules.push(matched.clone());
488 }
489 }
490 }
491 }
492
493 Some(rules)
494 })();
495
496 reply.send(msg).unwrap();
497}
498
499#[cfg_attr(crown, expect(crown::unrooted_must_root))]
500#[allow(clippy::too_many_arguments)]
501pub(crate) fn handle_get_stylesheet_style(
502 cx: &mut JSContext,
503 state: &DevtoolsState,
504 documents: &DocumentCollection,
505 pipeline: PipelineId,
506 node_id: &str,
507 matched_rule: MatchedRule,
508 reply: GenericSender<Option<Vec<NodeStyle>>>,
509) {
510 let msg = (|| {
511 let node = state.find_node_by_unique_id(pipeline, node_id)?;
512 let document = documents.find_document(pipeline)?;
513 let _realm = enter_realm(document.window());
514 let owner = node.stylesheet_list_owner();
515
516 let stylesheet = owner.stylesheet_at(matched_rule.stylesheet_index)?;
517 let list = stylesheet.GetCssRules(cx).ok()?;
518
519 let style_rule = find_rule_by_block_id(cx, &list, matched_rule.block_id)?;
520 let declaration = style_rule.Style(cx);
521
522 Some(
523 (0..declaration.Length())
524 .map(|i| {
525 let name = declaration.Item(i);
526 NodeStyle {
527 name: name.to_string(),
528 value: declaration.GetPropertyValue(name.clone()).to_string(),
529 priority: declaration.GetPropertyPriority(name).to_string(),
530 }
531 })
532 .collect(),
533 )
534 })();
535
536 reply.send(msg).unwrap();
537}
538
539pub(crate) fn handle_get_computed_style(
540 state: &DevtoolsState,
541 pipeline: PipelineId,
542 node_id: &str,
543 reply: GenericSender<Option<Vec<NodeStyle>>>,
544) {
545 let node = match state.find_node_by_unique_id(pipeline, node_id) {
546 None => return reply.send(None).unwrap(),
547 Some(found_node) => found_node,
548 };
549
550 let window = node.owner_window();
551 let elem = node
552 .downcast::<Element>()
553 .expect("This should be an element");
554 let computed_style = window.GetComputedStyle(elem, None);
555
556 let msg = (0..computed_style.Length())
557 .map(|i| {
558 let name = computed_style.Item(i);
559 NodeStyle {
560 name: name.to_string(),
561 value: computed_style.GetPropertyValue(name.clone()).to_string(),
562 priority: computed_style.GetPropertyPriority(name).to_string(),
563 }
564 })
565 .collect();
566
567 reply.send(Some(msg)).unwrap();
568}
569
570pub(crate) fn handle_get_layout(
571 cx: &mut JSContext,
572 state: &DevtoolsState,
573 pipeline: PipelineId,
574 node_id: &str,
575 reply: GenericSender<Option<(ComputedNodeLayout, AutoMargins)>>,
576) {
577 let node = match state.find_node_by_unique_id(pipeline, node_id) {
578 None => return reply.send(None).unwrap(),
579 Some(found_node) => found_node,
580 };
581
582 let element = node
583 .downcast::<Element>()
584 .expect("should be getting layout of element");
585
586 let rect = element.GetBoundingClientRect(cx);
587 let width = rect.Width() as f32;
588 let height = rect.Height() as f32;
589
590 let window = node.owner_window();
591 let computed_style = window.GetComputedStyle(element, None);
592 let computed_layout = ComputedNodeLayout {
593 display: computed_style.Display().into(),
594 position: computed_style.Position().into(),
595 z_index: computed_style.ZIndex().into(),
596 box_sizing: computed_style.BoxSizing().into(),
597 margin_top: computed_style.MarginTop().into(),
598 margin_right: computed_style.MarginRight().into(),
599 margin_bottom: computed_style.MarginBottom().into(),
600 margin_left: computed_style.MarginLeft().into(),
601 border_top_width: computed_style.BorderTopWidth().into(),
602 border_right_width: computed_style.BorderRightWidth().into(),
603 border_bottom_width: computed_style.BorderBottomWidth().into(),
604 border_left_width: computed_style.BorderLeftWidth().into(),
605 padding_top: computed_style.PaddingTop().into(),
606 padding_right: computed_style.PaddingRight().into(),
607 padding_bottom: computed_style.PaddingBottom().into(),
608 padding_left: computed_style.PaddingLeft().into(),
609 width,
610 height,
611 };
612
613 let auto_margins = element.determine_auto_margins();
614 reply.send(Some((computed_layout, auto_margins))).unwrap();
615}
616
617pub(crate) fn handle_get_xpath(
618 state: &DevtoolsState,
619 pipeline: PipelineId,
620 node_id: &str,
621 reply: GenericSender<String>,
622) {
623 let Some(node) = state.find_node_by_unique_id(pipeline, node_id) else {
624 return reply.send(Default::default()).unwrap();
625 };
626
627 let selector = node
628 .inclusive_ancestors(ShadowIncluding::Yes)
629 .filter_map(|ancestor| {
630 let Some(element) = ancestor.downcast::<Element>() else {
631 return None;
633 };
634
635 let mut result = "/".to_owned();
636 if *element.namespace() != ns!(html) {
637 result.push_str(element.namespace());
638 result.push(':');
639 }
640
641 result.push_str(element.local_name());
642
643 let would_node_also_match_selector = |sibling: &Node| {
644 let Some(sibling) = sibling.downcast::<Element>() else {
645 return false;
646 };
647 sibling.namespace() == element.namespace() &&
648 sibling.local_name() == element.local_name()
649 };
650
651 let matching_elements_before = ancestor
652 .preceding_siblings()
653 .filter(|node| would_node_also_match_selector(node))
654 .count();
655 let matching_elements_after = ancestor
656 .following_siblings()
657 .filter(|node| would_node_also_match_selector(node))
658 .count();
659
660 if matching_elements_before + matching_elements_after != 0 {
661 result.push_str(&format!("[{}]", matching_elements_before + 1));
663 }
664
665 Some(result)
666 })
667 .collect::<Vec<_>>()
668 .into_iter()
669 .rev()
670 .collect::<Vec<_>>()
671 .join("");
672
673 reply.send(selector).unwrap();
674}
675
676pub(crate) fn handle_modify_attribute(
677 cx: &mut JSContext,
678 state: &DevtoolsState,
679 documents: &DocumentCollection,
680 pipeline: PipelineId,
681 node_id: &str,
682 modifications: Vec<AttrModification>,
683) {
684 let Some(document) = documents.find_document(pipeline) else {
685 return warn!("document for pipeline id {} is not found", &pipeline);
686 };
687 let _realm = enter_realm(document.window());
688
689 let node = match state.find_node_by_unique_id(pipeline, node_id) {
690 None => {
691 return warn!(
692 "node id {} for pipeline id {} is not found",
693 &node_id, &pipeline
694 );
695 },
696 Some(found_node) => found_node,
697 };
698
699 let elem = node
700 .downcast::<Element>()
701 .expect("should be getting layout of element");
702
703 for modification in modifications {
704 match modification.new_value {
705 Some(string) => {
706 elem.set_attribute(
707 cx,
708 &LocalName::from(modification.attribute_name),
709 AttrValue::String(string),
710 );
711 },
712 None => elem.RemoveAttribute(cx, DOMString::from(modification.attribute_name)),
713 }
714 }
715}
716
717pub(crate) fn handle_modify_rule(
718 cx: &mut JSContext,
719 state: &DevtoolsState,
720 documents: &DocumentCollection,
721 pipeline: PipelineId,
722 node_id: &str,
723 modifications: Vec<RuleModification>,
724) {
725 let Some(document) = documents.find_document(pipeline) else {
726 return warn!("Document for pipeline id {} is not found", &pipeline);
727 };
728 let _realm = enter_realm(document.window());
729
730 let Some(node) = state.find_node_by_unique_id(pipeline, node_id) else {
731 return warn!(
732 "Node id {} for pipeline id {} is not found",
733 &node_id, &pipeline
734 );
735 };
736
737 let elem = node
738 .downcast::<HTMLElement>()
739 .expect("This should be an HTMLElement");
740 let style = elem.Style(CanGc::from_cx(cx));
741
742 for modification in modifications {
743 let _ = style.SetProperty(
744 cx,
745 modification.name.into(),
746 modification.value.into(),
747 modification.priority.into(),
748 );
749 }
750}
751
752pub(crate) fn handle_highlight_dom_node(
753 state: &DevtoolsState,
754 documents: &DocumentCollection,
755 id: PipelineId,
756 node_id: Option<&str>,
757) {
758 let node = node_id.and_then(|node_id| {
759 let node = state.find_node_by_unique_id(id, node_id);
760 if node.is_none() {
761 log::warn!("Node id {node_id} for pipeline id {id} is not found",);
762 }
763 node
764 });
765
766 if let Some(window) = documents.find_window(id) {
767 window.Document().highlight_dom_node(node.as_deref());
768 }
769}
770
771impl Element {
772 fn determine_auto_margins(&self) -> AutoMargins {
773 let Some(style) = self.style() else {
774 return AutoMargins::default();
775 };
776 let margin = style.get_margin();
777 AutoMargins {
778 top: margin.margin_top.is_auto(),
779 right: margin.margin_right.is_auto(),
780 bottom: margin.margin_bottom.is_auto(),
781 left: margin.margin_left.is_auto(),
782 }
783 }
784}