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