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