1use std::collections::HashMap;
6use std::rc::Rc;
7use std::str;
8
9use base::id::PipelineId;
10use devtools_traits::{
11 AttrModification, AutoMargins, ComputedNodeLayout, CssDatabaseProperty, EvaluateJSReply,
12 NodeInfo, NodeStyle, RuleModification, TimelineMarker, TimelineMarkerType,
13};
14use ipc_channel::ipc::IpcSender;
15use js::conversions::jsstr_to_string;
16use js::jsval::UndefinedValue;
17use js::rust::ToString;
18use markup5ever::{LocalName, ns};
19use servo_config::pref;
20use style::attr::AttrValue;
21use uuid::Uuid;
22
23use crate::document_collection::DocumentCollection;
24use crate::dom::bindings::codegen::Bindings::CSSRuleListBinding::CSSRuleListMethods;
25use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
26use crate::dom::bindings::codegen::Bindings::CSSStyleRuleBinding::CSSStyleRuleMethods;
27use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::CSSStyleSheetMethods;
28use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
29use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
30use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
31use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
32use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeConstants;
33use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
34use crate::dom::bindings::conversions::{ConversionResult, FromJSValConvertible};
35use crate::dom::bindings::inheritance::Castable;
36use crate::dom::bindings::root::DomRoot;
37use crate::dom::bindings::str::DOMString;
38use crate::dom::css::cssstyledeclaration::ENABLED_LONGHAND_PROPERTIES;
39use crate::dom::css::cssstylerule::CSSStyleRule;
40use crate::dom::document::AnimationFrameCallback;
41use crate::dom::element::Element;
42use crate::dom::globalscope::GlobalScope;
43use crate::dom::html::htmlscriptelement::SourceCode;
44use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
45use crate::dom::types::HTMLElement;
46use crate::realms::enter_realm;
47use crate::script_module::ScriptFetchOptions;
48use crate::script_runtime::{CanGc, IntroductionType};
49
50#[allow(unsafe_code)]
51pub(crate) fn handle_evaluate_js(
52 global: &GlobalScope,
53 eval: String,
54 reply: IpcSender<EvaluateJSReply>,
55 can_gc: CanGc,
56) {
57 let result = unsafe {
59 let cx = GlobalScope::get_cx();
60 let _ac = enter_realm(global);
61 rooted!(in(*cx) let mut rval = UndefinedValue());
62 let source_code = SourceCode::Text(Rc::new(DOMString::from_string(eval)));
63 _ = global.evaluate_script_on_global_with_result(
66 &source_code,
67 "<eval>",
68 rval.handle_mut(),
69 1,
70 ScriptFetchOptions::default_classic_script(global),
71 global.api_base_url(),
72 can_gc,
73 Some(IntroductionType::DEBUGGER_EVAL),
74 );
75
76 if rval.is_undefined() {
77 EvaluateJSReply::VoidValue
78 } else if rval.is_boolean() {
79 EvaluateJSReply::BooleanValue(rval.to_boolean())
80 } else if rval.is_double() || rval.is_int32() {
81 EvaluateJSReply::NumberValue(
82 match FromJSValConvertible::from_jsval(*cx, rval.handle(), ()) {
83 Ok(ConversionResult::Success(v)) => v,
84 _ => unreachable!(),
85 },
86 )
87 } else if rval.is_string() {
88 let jsstr = std::ptr::NonNull::new(rval.to_string()).unwrap();
89 EvaluateJSReply::StringValue(jsstr_to_string(*cx, jsstr))
90 } else if rval.is_null() {
91 EvaluateJSReply::NullValue
92 } else {
93 assert!(rval.is_object());
94
95 let jsstr = std::ptr::NonNull::new(ToString(*cx, rval.handle())).unwrap();
96 let class_name = jsstr_to_string(*cx, jsstr);
97
98 EvaluateJSReply::ActorValue {
99 class: class_name,
100 uuid: Uuid::new_v4().to_string(),
101 }
102 }
103 };
104 reply.send(result).unwrap();
105}
106
107pub(crate) fn handle_get_root_node(
108 documents: &DocumentCollection,
109 pipeline: PipelineId,
110 reply: IpcSender<Option<NodeInfo>>,
111 can_gc: CanGc,
112) {
113 let info = documents
114 .find_document(pipeline)
115 .map(|document| document.upcast::<Node>().summarize(can_gc));
116 reply.send(info).unwrap();
117}
118
119pub(crate) fn handle_get_document_element(
120 documents: &DocumentCollection,
121 pipeline: PipelineId,
122 reply: IpcSender<Option<NodeInfo>>,
123 can_gc: CanGc,
124) {
125 let info = documents
126 .find_document(pipeline)
127 .and_then(|document| document.GetDocumentElement())
128 .map(|element| element.upcast::<Node>().summarize(can_gc));
129 reply.send(info).unwrap();
130}
131
132fn find_node_by_unique_id(
133 documents: &DocumentCollection,
134 pipeline: PipelineId,
135 node_id: &str,
136) -> Option<DomRoot<Node>> {
137 documents.find_document(pipeline).and_then(|document| {
138 document
139 .upcast::<Node>()
140 .traverse_preorder(ShadowIncluding::Yes)
141 .find(|candidate| candidate.unique_id(pipeline) == node_id)
142 })
143}
144
145pub(crate) fn handle_get_children(
146 documents: &DocumentCollection,
147 pipeline: PipelineId,
148 node_id: String,
149 reply: IpcSender<Option<Vec<NodeInfo>>>,
150 can_gc: CanGc,
151) {
152 match find_node_by_unique_id(documents, pipeline, &node_id) {
153 None => reply.send(None).unwrap(),
154 Some(parent) => {
155 let is_whitespace = |node: &NodeInfo| {
156 node.node_type == NodeConstants::TEXT_NODE &&
157 node.node_value.as_ref().is_none_or(|v| v.trim().is_empty())
158 };
159
160 let inline: Vec<_> = parent
161 .children()
162 .map(|child| {
163 let window = child.owner_window();
164 let Some(elem) = child.downcast::<Element>() else {
165 return false;
166 };
167 let computed_style = window.GetComputedStyle(elem, None);
168 let display = computed_style.Display();
169 display == "inline"
170 })
171 .collect();
172
173 let mut children = vec![];
174 if let Some(shadow_root) = parent.downcast::<Element>().and_then(Element::shadow_root) {
175 if !shadow_root.is_user_agent_widget() ||
176 pref!(inspector_show_servo_internal_shadow_roots)
177 {
178 children.push(shadow_root.upcast::<Node>().summarize(can_gc));
179 }
180 }
181 let children_iter = parent.children().enumerate().filter_map(|(i, child)| {
182 let prev_inline = i > 0 && inline[i - 1];
185 let next_inline = i < inline.len() - 1 && inline[i + 1];
186
187 let info = child.summarize(can_gc);
188 if !is_whitespace(&info) {
189 return Some(info);
190 }
191
192 (prev_inline && next_inline).then_some(info)
193 });
194 children.extend(children_iter);
195
196 reply.send(Some(children)).unwrap();
197 },
198 };
199}
200
201pub(crate) fn handle_get_attribute_style(
202 documents: &DocumentCollection,
203 pipeline: PipelineId,
204 node_id: String,
205 reply: IpcSender<Option<Vec<NodeStyle>>>,
206 can_gc: CanGc,
207) {
208 let node = match find_node_by_unique_id(documents, pipeline, &node_id) {
209 None => return reply.send(None).unwrap(),
210 Some(found_node) => found_node,
211 };
212
213 let Some(elem) = node.downcast::<HTMLElement>() else {
214 reply.send(None).unwrap();
216 return;
217 };
218 let style = elem.Style(can_gc);
219
220 let msg = (0..style.Length())
221 .map(|i| {
222 let name = style.Item(i);
223 NodeStyle {
224 name: name.to_string(),
225 value: style.GetPropertyValue(name.clone()).to_string(),
226 priority: style.GetPropertyPriority(name).to_string(),
227 }
228 })
229 .collect();
230
231 reply.send(Some(msg)).unwrap();
232}
233
234#[cfg_attr(crown, allow(crown::unrooted_must_root))]
235pub(crate) fn handle_get_stylesheet_style(
236 documents: &DocumentCollection,
237 pipeline: PipelineId,
238 node_id: String,
239 selector: String,
240 stylesheet: usize,
241 reply: IpcSender<Option<Vec<NodeStyle>>>,
242 can_gc: CanGc,
243) {
244 let msg = (|| {
245 let node = find_node_by_unique_id(documents, pipeline, &node_id)?;
246
247 let document = documents.find_document(pipeline)?;
248 let _realm = enter_realm(document.window());
249 let owner = node.stylesheet_list_owner();
250
251 let stylesheet = owner.stylesheet_at(stylesheet)?;
252 let list = stylesheet.GetCssRules(can_gc).ok()?;
253
254 let styles = (0..list.Length())
255 .filter_map(move |i| {
256 let rule = list.Item(i, can_gc)?;
257 let style = rule.downcast::<CSSStyleRule>()?;
258 if selector != style.SelectorText() {
259 return None;
260 };
261 Some(style.Style(can_gc))
262 })
263 .flat_map(|style| {
264 (0..style.Length()).map(move |i| {
265 let name = style.Item(i);
266 NodeStyle {
267 name: name.to_string(),
268 value: style.GetPropertyValue(name.clone()).to_string(),
269 priority: style.GetPropertyPriority(name).to_string(),
270 }
271 })
272 })
273 .collect();
274
275 Some(styles)
276 })();
277
278 reply.send(msg).unwrap();
279}
280
281#[cfg_attr(crown, allow(crown::unrooted_must_root))]
282pub(crate) fn handle_get_selectors(
283 documents: &DocumentCollection,
284 pipeline: PipelineId,
285 node_id: String,
286 reply: IpcSender<Option<Vec<(String, usize)>>>,
287 can_gc: CanGc,
288) {
289 let msg = (|| {
290 let node = find_node_by_unique_id(documents, pipeline, &node_id)?;
291
292 let document = documents.find_document(pipeline)?;
293 let _realm = enter_realm(document.window());
294 let owner = node.stylesheet_list_owner();
295
296 let rules = (0..owner.stylesheet_count())
297 .filter_map(|i| {
298 let stylesheet = owner.stylesheet_at(i)?;
299 let list = stylesheet.GetCssRules(can_gc).ok()?;
300 let elem = node.downcast::<Element>()?;
301
302 Some((0..list.Length()).filter_map(move |j| {
303 let rule = list.Item(j, can_gc)?;
304 let style = rule.downcast::<CSSStyleRule>()?;
305 let selector = style.SelectorText();
306 elem.Matches(selector.clone()).ok()?.then_some(())?;
307 Some((selector.into(), i))
308 }))
309 })
310 .flatten()
311 .collect();
312
313 Some(rules)
314 })();
315
316 reply.send(msg).unwrap();
317}
318
319pub(crate) fn handle_get_computed_style(
320 documents: &DocumentCollection,
321 pipeline: PipelineId,
322 node_id: String,
323 reply: IpcSender<Option<Vec<NodeStyle>>>,
324) {
325 let node = match find_node_by_unique_id(documents, pipeline, &node_id) {
326 None => return reply.send(None).unwrap(),
327 Some(found_node) => found_node,
328 };
329
330 let window = node.owner_window();
331 let elem = node
332 .downcast::<Element>()
333 .expect("This should be an element");
334 let computed_style = window.GetComputedStyle(elem, None);
335
336 let msg = (0..computed_style.Length())
337 .map(|i| {
338 let name = computed_style.Item(i);
339 NodeStyle {
340 name: name.to_string(),
341 value: computed_style.GetPropertyValue(name.clone()).to_string(),
342 priority: computed_style.GetPropertyPriority(name).to_string(),
343 }
344 })
345 .collect();
346
347 reply.send(Some(msg)).unwrap();
348}
349
350pub(crate) fn handle_get_layout(
351 documents: &DocumentCollection,
352 pipeline: PipelineId,
353 node_id: String,
354 reply: IpcSender<Option<ComputedNodeLayout>>,
355 can_gc: CanGc,
356) {
357 let node = match find_node_by_unique_id(documents, pipeline, &node_id) {
358 None => return reply.send(None).unwrap(),
359 Some(found_node) => found_node,
360 };
361
362 let elem = node
363 .downcast::<Element>()
364 .expect("should be getting layout of element");
365 let rect = elem.GetBoundingClientRect(can_gc);
366 let width = rect.Width() as f32;
367 let height = rect.Height() as f32;
368
369 let window = node.owner_window();
370 let elem = node
371 .downcast::<Element>()
372 .expect("should be getting layout of element");
373 let computed_style = window.GetComputedStyle(elem, None);
374
375 reply
376 .send(Some(ComputedNodeLayout {
377 display: String::from(computed_style.Display()),
378 position: String::from(computed_style.Position()),
379 z_index: String::from(computed_style.ZIndex()),
380 box_sizing: String::from(computed_style.BoxSizing()),
381 auto_margins: determine_auto_margins(&node),
382 margin_top: String::from(computed_style.MarginTop()),
383 margin_right: String::from(computed_style.MarginRight()),
384 margin_bottom: String::from(computed_style.MarginBottom()),
385 margin_left: String::from(computed_style.MarginLeft()),
386 border_top_width: String::from(computed_style.BorderTopWidth()),
387 border_right_width: String::from(computed_style.BorderRightWidth()),
388 border_bottom_width: String::from(computed_style.BorderBottomWidth()),
389 border_left_width: String::from(computed_style.BorderLeftWidth()),
390 padding_top: String::from(computed_style.PaddingTop()),
391 padding_right: String::from(computed_style.PaddingRight()),
392 padding_bottom: String::from(computed_style.PaddingBottom()),
393 padding_left: String::from(computed_style.PaddingLeft()),
394 width,
395 height,
396 }))
397 .unwrap();
398}
399
400pub(crate) fn handle_get_xpath(
401 documents: &DocumentCollection,
402 pipeline: PipelineId,
403 node_id: String,
404 reply: IpcSender<String>,
405) {
406 let Some(node) = find_node_by_unique_id(documents, pipeline, &node_id) else {
407 return reply.send(Default::default()).unwrap();
408 };
409
410 let selector = node
411 .inclusive_ancestors(ShadowIncluding::Yes)
412 .filter_map(|ancestor| {
413 let Some(element) = ancestor.downcast::<Element>() else {
414 return None;
416 };
417
418 let mut result = "/".to_owned();
419 if *element.namespace() != ns!(html) {
420 result.push_str(element.namespace());
421 result.push(':');
422 }
423
424 result.push_str(element.local_name());
425
426 let would_node_also_match_selector = |sibling: &Node| {
427 let Some(sibling) = sibling.downcast::<Element>() else {
428 return false;
429 };
430 sibling.namespace() == element.namespace() &&
431 sibling.local_name() == element.local_name()
432 };
433
434 let matching_elements_before = ancestor
435 .preceding_siblings()
436 .filter(|node| would_node_also_match_selector(node))
437 .count();
438 let matching_elements_after = ancestor
439 .following_siblings()
440 .filter(|node| would_node_also_match_selector(node))
441 .count();
442
443 if matching_elements_before + matching_elements_after != 0 {
444 result.push_str(&format!("[{}]", matching_elements_before + 1));
446 }
447
448 Some(result)
449 })
450 .collect::<Vec<_>>()
451 .into_iter()
452 .rev()
453 .collect::<Vec<_>>()
454 .join("");
455
456 reply.send(selector).unwrap();
457}
458
459fn determine_auto_margins(node: &Node) -> AutoMargins {
460 let style = node.style().unwrap();
461 let margin = style.get_margin();
462 AutoMargins {
463 top: margin.margin_top.is_auto(),
464 right: margin.margin_right.is_auto(),
465 bottom: margin.margin_bottom.is_auto(),
466 left: margin.margin_left.is_auto(),
467 }
468}
469
470pub(crate) fn handle_modify_attribute(
471 documents: &DocumentCollection,
472 pipeline: PipelineId,
473 node_id: String,
474 modifications: Vec<AttrModification>,
475 can_gc: CanGc,
476) {
477 let Some(document) = documents.find_document(pipeline) else {
478 return warn!("document for pipeline id {} is not found", &pipeline);
479 };
480 let _realm = enter_realm(document.window());
481
482 let node = match find_node_by_unique_id(documents, pipeline, &node_id) {
483 None => {
484 return warn!(
485 "node id {} for pipeline id {} is not found",
486 &node_id, &pipeline
487 );
488 },
489 Some(found_node) => found_node,
490 };
491
492 let elem = node
493 .downcast::<Element>()
494 .expect("should be getting layout of element");
495
496 for modification in modifications {
497 match modification.new_value {
498 Some(string) => {
499 elem.set_attribute(
500 &LocalName::from(modification.attribute_name),
501 AttrValue::String(string),
502 can_gc,
503 );
504 },
505 None => elem.RemoveAttribute(DOMString::from(modification.attribute_name), can_gc),
506 }
507 }
508}
509
510pub(crate) fn handle_modify_rule(
511 documents: &DocumentCollection,
512 pipeline: PipelineId,
513 node_id: String,
514 modifications: Vec<RuleModification>,
515 can_gc: CanGc,
516) {
517 let Some(document) = documents.find_document(pipeline) else {
518 return warn!("Document for pipeline id {} is not found", &pipeline);
519 };
520 let _realm = enter_realm(document.window());
521
522 let Some(node) = find_node_by_unique_id(documents, pipeline, &node_id) else {
523 return warn!(
524 "Node id {} for pipeline id {} is not found",
525 &node_id, &pipeline
526 );
527 };
528
529 let elem = node
530 .downcast::<HTMLElement>()
531 .expect("This should be an HTMLElement");
532 let style = elem.Style(can_gc);
533
534 for modification in modifications {
535 let _ = style.SetProperty(
536 modification.name.into(),
537 modification.value.into(),
538 modification.priority.into(),
539 can_gc,
540 );
541 }
542}
543
544pub(crate) fn handle_wants_live_notifications(global: &GlobalScope, send_notifications: bool) {
545 global.set_devtools_wants_updates(send_notifications);
546}
547
548pub(crate) fn handle_set_timeline_markers(
549 documents: &DocumentCollection,
550 pipeline: PipelineId,
551 marker_types: Vec<TimelineMarkerType>,
552 reply: IpcSender<Option<TimelineMarker>>,
553) {
554 match documents.find_window(pipeline) {
555 None => reply.send(None).unwrap(),
556 Some(window) => window.set_devtools_timeline_markers(marker_types, reply),
557 }
558}
559
560pub(crate) fn handle_drop_timeline_markers(
561 documents: &DocumentCollection,
562 pipeline: PipelineId,
563 marker_types: Vec<TimelineMarkerType>,
564) {
565 if let Some(window) = documents.find_window(pipeline) {
566 window.drop_devtools_timeline_markers(marker_types);
567 }
568}
569
570pub(crate) fn handle_request_animation_frame(
571 documents: &DocumentCollection,
572 id: PipelineId,
573 actor_name: String,
574) {
575 if let Some(doc) = documents.find_document(id) {
576 doc.request_animation_frame(AnimationFrameCallback::DevtoolsFramerateTick { actor_name });
577 }
578}
579
580pub(crate) fn handle_reload(documents: &DocumentCollection, id: PipelineId, can_gc: CanGc) {
581 if let Some(win) = documents.find_window(id) {
582 win.Location().reload_without_origin_check(can_gc);
583 }
584}
585
586pub(crate) fn handle_get_css_database(reply: IpcSender<HashMap<String, CssDatabaseProperty>>) {
587 let database: HashMap<_, _> = ENABLED_LONGHAND_PROPERTIES
588 .iter()
589 .map(|l| {
590 (
591 l.name().into(),
592 CssDatabaseProperty {
593 is_inherited: l.inherited(),
594 values: vec![], supports: vec![],
596 subproperties: vec![l.name().into()],
597 },
598 )
599 })
600 .collect();
601 let _ = reply.send(database);
602}
603
604pub(crate) fn handle_highlight_dom_node(
605 documents: &DocumentCollection,
606 id: PipelineId,
607 node_id: Option<String>,
608) {
609 let node = node_id.and_then(|node_id| {
610 let node = find_node_by_unique_id(documents, id, &node_id);
611 if node.is_none() {
612 log::warn!("Node id {node_id} for pipeline id {id} is not found",);
613 }
614 node
615 });
616
617 if let Some(window) = documents.find_window(id) {
618 window.Document().highlight_dom_node(node.as_deref());
619 }
620}