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 cx: &mut JSContext,
237 documents: &DocumentCollection,
238 pipeline: PipelineId,
239 reply: GenericSender<Vec<StyleSheetInfo>>,
240) {
241 let mut stylesheets = vec![];
242 if let Some(document) = documents.find_document(pipeline) {
243 let node = document.upcast::<Node>();
244 for i in 0..node.stylesheet_list_owner().stylesheet_count() {
245 if let Some(s) = node.stylesheet_list_owner().stylesheet_at(cx, i) {
246 stylesheets.push(StyleSheetInfo {
247 href: s.href().map(String::from),
248 disabled: s.disabled(),
249 title: String::from(s.title()),
250 style_sheet_index: i as i32,
251 system: s.origin() == Origin::UserAgent,
252 rule_count: s.get_rule_count(),
253 });
254 }
255 }
256 }
257 reply.send(stylesheets).unwrap();
258}
259
260pub(crate) fn handle_get_stylesheet_text(
261 cx: &mut JSContext,
262 documents: &DocumentCollection,
263 pipeline: PipelineId,
264 index: i32,
265 reply: GenericSender<Option<String>>,
266) {
267 let text = (|| {
268 let document = documents.find_document(pipeline)?;
269 let stylesheet = document
270 .upcast::<Node>()
271 .stylesheet_list_owner()
272 .stylesheet_at(cx, index as usize)?;
273
274 if let Some(node) = stylesheet.owner_node() {
276 let text = node.upcast::<Node>().GetTextContent().unwrap_or_default();
277 if !text.is_empty() {
278 return Some(String::from(text));
279 }
280 }
281
282 let rules = stylesheet.rulelist(cx);
284 let mut css_text = String::new();
285 for i in 0..rules.Length() {
286 if let Some(rule) = rules.Item(cx, i) {
287 css_text.push_str(&rule.CssText().str());
288 css_text.push('\n');
289 }
290 }
291 Some(css_text)
292 })();
293 reply.send(text).unwrap();
294}
295
296pub(crate) fn handle_get_children(
297 cx: &mut JSContext,
298 state: &DevtoolsState,
299 pipeline: PipelineId,
300 node_id: &str,
301 reply: GenericSender<Option<Vec<NodeInfo>>>,
302) {
303 let Some(parent) = state.find_node_by_unique_id(pipeline, node_id) else {
304 reply.send(None).unwrap();
305 return;
306 };
307 let is_whitespace = |node: &NodeInfo| {
308 node.node_type == NodeConstants::TEXT_NODE &&
309 node.node_value.as_ref().is_none_or(|v| v.trim().is_empty())
310 };
311 let mut pipeline_state = state.mut_pipeline_state_for(pipeline).unwrap();
312
313 let inline: Vec<_> = parent
314 .children()
315 .map(|child| {
316 let window = child.owner_window();
317 let Some(elem) = child.downcast::<Element>() else {
318 return false;
319 };
320 let computed_style = window.GetComputedStyle(cx, elem, None);
321 let display = computed_style.Display();
322 display == "inline"
323 })
324 .collect();
325
326 let mut children = vec![];
327 if let Some(shadow_root) = parent.downcast::<Element>().and_then(Element::shadow_root) &&
328 (!shadow_root.is_user_agent_widget() ||
329 pref!(inspector_show_servo_internal_shadow_roots))
330 {
331 children.push(shadow_root.upcast::<Node>().summarize(cx));
332 }
333 let children_iter = parent.children().enumerate().filter_map(|(i, child)| {
334 let prev_inline = i > 0 && inline[i - 1];
337 let next_inline = i < inline.len() - 1 && inline[i + 1];
338 let is_inline_level = prev_inline && next_inline;
339
340 let info = child.summarize(cx);
341 if is_whitespace(&info) && !is_inline_level {
342 return None;
343 }
344 pipeline_state.register_node(&child);
345
346 Some(info)
347 });
348 children.extend(children_iter);
349
350 reply.send(Some(children)).unwrap();
351}
352
353pub(crate) fn handle_get_attribute_style(
354 cx: &mut JSContext,
355 state: &DevtoolsState,
356 pipeline: PipelineId,
357 node_id: &str,
358 reply: GenericSender<Option<Vec<NodeStyle>>>,
359) {
360 let node = match state.find_node_by_unique_id(pipeline, node_id) {
361 None => return reply.send(None).unwrap(),
362 Some(found_node) => found_node,
363 };
364
365 let Some(elem) = node.downcast::<HTMLElement>() else {
366 reply.send(None).unwrap();
368 return;
369 };
370 let style = elem.Style(cx);
371
372 let msg = (0..style.Length())
373 .map(|i| {
374 let name = style.Item(i);
375 NodeStyle {
376 value: String::from(style.GetPropertyValue(name.clone())),
383 priority: String::from(style.GetPropertyPriority(name.clone())),
384 name: String::from(name),
385 }
386 })
387 .collect();
388
389 reply.send(Some(msg)).unwrap();
390}
391
392fn build_rule_map(
393 cx: &mut JSContext,
394 list: &crate::dom::css::cssrulelist::CSSRuleList,
395 stylesheet_index: usize,
396 ancestors: &[AncestorData],
397 map: &mut HashMap<usize, MatchedRule>,
398) {
399 for i in 0..list.Length() {
400 let Some(rule) = list.Item(cx, i) else {
401 continue;
402 };
403
404 if let Some(style_rule) = rule.downcast::<CSSStyleRule>() {
405 let block_id = style_rule.block_id();
406 map.entry(block_id).or_insert_with(|| MatchedRule {
407 selector: style_rule.SelectorText().into(),
408 stylesheet_index,
409 block_id,
410 ancestor_data: ancestors.to_vec(),
411 });
412 continue;
413 }
414
415 if let Some(layer_rule) = rule.downcast::<CSSLayerBlockRule>() {
416 let name = String::from(layer_rule.Name());
417 let mut next = ancestors.to_vec();
418 next.push(AncestorData::Layer {
419 actor_id: None,
420 value: (!name.is_empty()).then_some(name),
421 });
422 let inner = layer_rule.upcast::<CSSGroupingRule>().CssRules(cx);
423 build_rule_map(cx, &inner, stylesheet_index, &next, map);
424 continue;
425 }
426
427 if let Some(group_rule) = rule.downcast::<CSSGroupingRule>() {
428 let inner = group_rule.CssRules(cx);
429 build_rule_map(cx, &inner, stylesheet_index, ancestors, map);
430 }
431 }
432}
433
434fn find_rule_by_block_id(
435 cx: &mut JSContext,
436 list: &crate::dom::css::cssrulelist::CSSRuleList,
437 target_block_id: usize,
438) -> Option<DomRoot<CSSStyleRule>> {
439 for i in 0..list.Length() {
440 let Some(rule) = list.Item(cx, i) else {
441 continue;
442 };
443
444 if let Some(style_rule) = rule.downcast::<CSSStyleRule>() {
445 if style_rule.block_id() == target_block_id {
446 return Some(DomRoot::from_ref(style_rule));
447 }
448 continue;
449 }
450
451 if let Some(group_rule) = rule.downcast::<CSSGroupingRule>() {
452 let inner = group_rule.CssRules(cx);
453 if let Some(found) = find_rule_by_block_id(cx, &inner, target_block_id) {
454 return Some(found);
455 }
456 }
457 }
458 None
459}
460
461#[cfg_attr(crown, expect(crown::unrooted_must_root))]
462pub(crate) fn handle_get_selectors(
463 cx: &mut JSContext,
464 state: &DevtoolsState,
465 documents: &DocumentCollection,
466 pipeline: PipelineId,
467 node_id: &str,
468 reply: GenericSender<Option<Vec<MatchedRule>>>,
469) {
470 let msg = (|| {
471 let node = state.find_node_by_unique_id(pipeline, node_id)?;
472 let elem = node.downcast::<Element>()?;
473 let document = documents.find_document(pipeline)?;
474 let mut realm = enter_auto_realm(cx, document.window());
475 let cx = &mut realm.current_realm();
476 let owner = node.stylesheet_list_owner();
477
478 let mut decl_map = HashMap::new();
479 for i in 0..owner.stylesheet_count() {
480 let Some(stylesheet) = owner.stylesheet_at(cx, i) else {
481 continue;
482 };
483 let Ok(list) = stylesheet.GetCssRules(cx) else {
484 continue;
485 };
486 build_rule_map(cx, &list, i, &[], &mut decl_map);
487 }
488
489 let mut rules = Vec::new();
490 let computed = elem.style()?;
491
492 if let Some(rule_node) = computed.rules.as_ref() {
493 for rn in rule_node.self_and_ancestors() {
494 if let Some(source) = rn.style_source() {
495 let ptr = source.get().raw_ptr().as_ptr() as usize;
496
497 if let Some(matched) = decl_map.get(&ptr) {
498 rules.push(matched.clone());
499 }
500 }
501 }
502 }
503
504 Some(rules)
505 })();
506
507 reply.send(msg).unwrap();
508}
509
510#[cfg_attr(crown, expect(crown::unrooted_must_root))]
511#[allow(clippy::too_many_arguments)]
512pub(crate) fn handle_get_stylesheet_style(
513 cx: &mut JSContext,
514 state: &DevtoolsState,
515 documents: &DocumentCollection,
516 pipeline: PipelineId,
517 node_id: &str,
518 matched_rule: MatchedRule,
519 reply: GenericSender<Option<Vec<NodeStyle>>>,
520) {
521 let msg = (|| {
522 let node = state.find_node_by_unique_id(pipeline, node_id)?;
523 let document = documents.find_document(pipeline)?;
524 let mut realm = enter_auto_realm(cx, document.window());
525 let cx = &mut realm.current_realm();
526 let owner = node.stylesheet_list_owner();
527
528 let stylesheet = owner.stylesheet_at(cx, matched_rule.stylesheet_index)?;
529 let list = stylesheet.GetCssRules(cx).ok()?;
530
531 let style_rule = find_rule_by_block_id(cx, &list, matched_rule.block_id)?;
532 let declaration = style_rule.Style(cx);
533
534 Some(
535 (0..declaration.Length())
536 .map(|i| {
537 let name = declaration.Item(i);
538 NodeStyle {
539 value: String::from(declaration.GetPropertyValue(name.clone())),
540 priority: String::from(declaration.GetPropertyPriority(name.clone())),
541 name: String::from(name),
542 }
543 })
544 .collect(),
545 )
546 })();
547
548 reply.send(msg).unwrap();
549}
550
551pub(crate) fn handle_get_computed_style(
552 cx: &mut JSContext,
553 state: &DevtoolsState,
554 pipeline: PipelineId,
555 node_id: &str,
556 reply: GenericSender<Option<Vec<NodeStyle>>>,
557) {
558 let node = match state.find_node_by_unique_id(pipeline, node_id) {
559 None => return reply.send(None).unwrap(),
560 Some(found_node) => found_node,
561 };
562
563 let window = node.owner_window();
564 let elem = node
565 .downcast::<Element>()
566 .expect("This should be an element");
567 let computed_style = window.GetComputedStyle(cx, elem, None);
568
569 let msg = (0..computed_style.Length())
570 .map(|i| {
571 let name = computed_style.Item(i);
572 NodeStyle {
573 value: String::from(computed_style.GetPropertyValue(name.clone())),
574 priority: String::from(computed_style.GetPropertyPriority(name.clone())),
575 name: String::from(name),
576 }
577 })
578 .collect();
579
580 reply.send(Some(msg)).unwrap();
581}
582
583pub(crate) fn handle_get_layout(
584 cx: &mut JSContext,
585 state: &DevtoolsState,
586 pipeline: PipelineId,
587 node_id: &str,
588 reply: GenericSender<Option<(ComputedNodeLayout, AutoMargins)>>,
589) {
590 let node = match state.find_node_by_unique_id(pipeline, node_id) {
591 None => return reply.send(None).unwrap(),
592 Some(found_node) => found_node,
593 };
594
595 let element = node
596 .downcast::<Element>()
597 .expect("should be getting layout of element");
598
599 let rect = element.GetBoundingClientRect(cx);
600 let width = rect.Width() as f32;
601 let height = rect.Height() as f32;
602
603 let window = node.owner_window();
604 let computed_style = window.GetComputedStyle(cx, element, None);
605 let computed_layout = ComputedNodeLayout {
606 display: computed_style.Display().into(),
607 position: computed_style.Position().into(),
608 z_index: computed_style.ZIndex().into(),
609 box_sizing: computed_style.BoxSizing().into(),
610 margin_top: computed_style.MarginTop().into(),
611 margin_right: computed_style.MarginRight().into(),
612 margin_bottom: computed_style.MarginBottom().into(),
613 margin_left: computed_style.MarginLeft().into(),
614 border_top_width: computed_style.BorderTopWidth().into(),
615 border_right_width: computed_style.BorderRightWidth().into(),
616 border_bottom_width: computed_style.BorderBottomWidth().into(),
617 border_left_width: computed_style.BorderLeftWidth().into(),
618 padding_top: computed_style.PaddingTop().into(),
619 padding_right: computed_style.PaddingRight().into(),
620 padding_bottom: computed_style.PaddingBottom().into(),
621 padding_left: computed_style.PaddingLeft().into(),
622 width,
623 height,
624 };
625
626 let auto_margins = element.determine_auto_margins();
627 reply.send(Some((computed_layout, auto_margins))).unwrap();
628}
629
630pub(crate) fn handle_get_xpath(
631 state: &DevtoolsState,
632 pipeline: PipelineId,
633 node_id: &str,
634 reply: GenericSender<String>,
635) {
636 let Some(node) = state.find_node_by_unique_id(pipeline, node_id) else {
637 return reply.send(Default::default()).unwrap();
638 };
639
640 let selector = node
641 .inclusive_ancestors(ShadowIncluding::Yes)
642 .filter_map(|ancestor| {
643 let Some(element) = ancestor.downcast::<Element>() else {
644 return None;
646 };
647
648 let mut result = "/".to_owned();
649 if *element.namespace() != ns!(html) {
650 result.push_str(element.namespace());
651 result.push(':');
652 }
653
654 result.push_str(element.local_name());
655
656 let would_node_also_match_selector = |sibling: &Node| {
657 let Some(sibling) = sibling.downcast::<Element>() else {
658 return false;
659 };
660 sibling.namespace() == element.namespace() &&
661 sibling.local_name() == element.local_name()
662 };
663
664 let matching_elements_before = ancestor
665 .preceding_siblings()
666 .filter(|node| would_node_also_match_selector(node))
667 .count();
668 let matching_elements_after = ancestor
669 .following_siblings()
670 .filter(|node| would_node_also_match_selector(node))
671 .count();
672
673 if matching_elements_before + matching_elements_after != 0 {
674 result.push_str(&format!("[{}]", matching_elements_before + 1));
676 }
677
678 Some(result)
679 })
680 .collect::<Vec<_>>()
681 .into_iter()
682 .rev()
683 .collect::<Vec<_>>()
684 .join("");
685
686 reply.send(selector).unwrap();
687}
688
689pub(crate) fn handle_get_inner_or_outer_html(
690 cx: &mut JSContext,
691 state: &DevtoolsState,
692 pipeline_id: PipelineId,
693 node_id: &str,
694 reply: GenericSender<Option<String>>,
695 html_type: GetHTMLType,
696) {
697 let node = state.find_node_by_unique_id(pipeline_id, node_id);
698
699 let selector = node.and_then(|node| {
700 let element = node.downcast::<Element>();
701
702 if let Some(element) = element {
703 let inner_or_outer_html = match html_type {
704 GetHTMLType::InnerHTML => element.GetInnerHTML(cx),
705 GetHTMLType::OuterHTML => element.GetOuterHTML(cx),
706 };
707
708 if let Ok(trusted_html) = inner_or_outer_html {
709 let trusted_html_or_string = trusted_html.convert();
710
711 let Ok(html_dom_string) = TrustedHTML::get_trusted_type_compliant_string(
712 cx,
713 &element.owner_global(),
714 trusted_html_or_string,
715 "Devtools GetInnerOrOuterHTML",
716 ) else {
717 return None;
718 };
719
720 return Some(html_dom_string.to_string());
721 };
722 }
723 Some("".to_owned())
724 });
725
726 reply.send(selector).unwrap();
727}
728
729pub(crate) fn handle_modify_attribute(
730 cx: &mut JSContext,
731 state: &DevtoolsState,
732 documents: &DocumentCollection,
733 pipeline: PipelineId,
734 node_id: &str,
735 modifications: Vec<AttrModification>,
736) {
737 let Some(document) = documents.find_document(pipeline) else {
738 return warn!("document for pipeline id {} is not found", &pipeline);
739 };
740 let mut realm = enter_auto_realm(cx, document.window());
741 let cx = &mut realm.current_realm();
742
743 let node = match state.find_node_by_unique_id(pipeline, node_id) {
744 None => {
745 return warn!(
746 "node id {} for pipeline id {} is not found",
747 &node_id, &pipeline
748 );
749 },
750 Some(found_node) => found_node,
751 };
752
753 let elem = node
754 .downcast::<Element>()
755 .expect("should be getting layout of element");
756
757 for modification in modifications {
758 match modification.new_value {
759 Some(string) => {
760 elem.set_attribute(
761 cx,
762 &LocalName::from(modification.attribute_name),
763 AttrValue::String(string),
764 );
765 },
766 None => elem.RemoveAttribute(cx, DOMString::from(modification.attribute_name)),
767 }
768 }
769}
770
771pub(crate) fn handle_modify_rule(
772 cx: &mut JSContext,
773 state: &DevtoolsState,
774 documents: &DocumentCollection,
775 pipeline: PipelineId,
776 node_id: &str,
777 modifications: Vec<RuleModification>,
778) {
779 let Some(document) = documents.find_document(pipeline) else {
780 return warn!("Document for pipeline id {} is not found", &pipeline);
781 };
782 let mut realm = enter_auto_realm(cx, document.window());
783 let cx = &mut realm.current_realm();
784
785 let Some(node) = state.find_node_by_unique_id(pipeline, node_id) else {
786 return warn!(
787 "Node id {} for pipeline id {} is not found",
788 &node_id, &pipeline
789 );
790 };
791
792 let elem = node
793 .downcast::<HTMLElement>()
794 .expect("This should be an HTMLElement");
795 let style = elem.Style(cx);
796
797 for modification in modifications {
798 let _ = style.SetProperty(
799 cx,
800 modification.name.into(),
801 modification.value.into(),
802 modification.priority.into(),
803 );
804 }
805}
806
807pub(crate) fn handle_highlight_dom_node(
808 state: &DevtoolsState,
809 documents: &DocumentCollection,
810 id: PipelineId,
811 node_id: Option<&str>,
812) {
813 let node = node_id.and_then(|node_id| {
814 let node = state.find_node_by_unique_id(id, node_id);
815 if node.is_none() {
816 log::warn!("Node id {node_id} for pipeline id {id} is not found",);
817 }
818 node
819 });
820
821 if let Some(window) = documents.find_window(id) {
822 window.Document().highlight_dom_node(node.as_deref());
823 }
824}
825
826impl Element {
827 fn determine_auto_margins(&self) -> AutoMargins {
828 let Some(style) = self.style() else {
829 return AutoMargins::default();
830 };
831 let margin = style.get_margin();
832 AutoMargins {
833 top: margin.margin_top.is_auto(),
834 right: margin.margin_right.is_auto(),
835 bottom: margin.margin_bottom.is_auto(),
836 left: margin.margin_left.is_auto(),
837 }
838 }
839}