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 html5ever::LocalName;
15use ipc_channel::ipc::IpcSender;
16use js::conversions::jsstr_to_string;
17use js::jsval::UndefinedValue;
18use js::rust::ToString;
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::cssstyledeclaration::ENABLED_LONGHAND_PROPERTIES;
39use crate::dom::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
400fn determine_auto_margins(node: &Node) -> AutoMargins {
401 let style = node.style().unwrap();
402 let margin = style.get_margin();
403 AutoMargins {
404 top: margin.margin_top.is_auto(),
405 right: margin.margin_right.is_auto(),
406 bottom: margin.margin_bottom.is_auto(),
407 left: margin.margin_left.is_auto(),
408 }
409}
410
411pub(crate) fn handle_modify_attribute(
412 documents: &DocumentCollection,
413 pipeline: PipelineId,
414 node_id: String,
415 modifications: Vec<AttrModification>,
416 can_gc: CanGc,
417) {
418 let Some(document) = documents.find_document(pipeline) else {
419 return warn!("document for pipeline id {} is not found", &pipeline);
420 };
421 let _realm = enter_realm(document.window());
422
423 let node = match find_node_by_unique_id(documents, pipeline, &node_id) {
424 None => {
425 return warn!(
426 "node id {} for pipeline id {} is not found",
427 &node_id, &pipeline
428 );
429 },
430 Some(found_node) => found_node,
431 };
432
433 let elem = node
434 .downcast::<Element>()
435 .expect("should be getting layout of element");
436
437 for modification in modifications {
438 match modification.new_value {
439 Some(string) => {
440 elem.set_attribute(
441 &LocalName::from(modification.attribute_name),
442 AttrValue::String(string),
443 can_gc,
444 );
445 },
446 None => elem.RemoveAttribute(DOMString::from(modification.attribute_name), can_gc),
447 }
448 }
449}
450
451pub(crate) fn handle_modify_rule(
452 documents: &DocumentCollection,
453 pipeline: PipelineId,
454 node_id: String,
455 modifications: Vec<RuleModification>,
456 can_gc: CanGc,
457) {
458 let Some(document) = documents.find_document(pipeline) else {
459 return warn!("Document for pipeline id {} is not found", &pipeline);
460 };
461 let _realm = enter_realm(document.window());
462
463 let Some(node) = find_node_by_unique_id(documents, pipeline, &node_id) else {
464 return warn!(
465 "Node id {} for pipeline id {} is not found",
466 &node_id, &pipeline
467 );
468 };
469
470 let elem = node
471 .downcast::<HTMLElement>()
472 .expect("This should be an HTMLElement");
473 let style = elem.Style(can_gc);
474
475 for modification in modifications {
476 let _ = style.SetProperty(
477 modification.name.into(),
478 modification.value.into(),
479 modification.priority.into(),
480 can_gc,
481 );
482 }
483}
484
485pub(crate) fn handle_wants_live_notifications(global: &GlobalScope, send_notifications: bool) {
486 global.set_devtools_wants_updates(send_notifications);
487}
488
489pub(crate) fn handle_set_timeline_markers(
490 documents: &DocumentCollection,
491 pipeline: PipelineId,
492 marker_types: Vec<TimelineMarkerType>,
493 reply: IpcSender<Option<TimelineMarker>>,
494) {
495 match documents.find_window(pipeline) {
496 None => reply.send(None).unwrap(),
497 Some(window) => window.set_devtools_timeline_markers(marker_types, reply),
498 }
499}
500
501pub(crate) fn handle_drop_timeline_markers(
502 documents: &DocumentCollection,
503 pipeline: PipelineId,
504 marker_types: Vec<TimelineMarkerType>,
505) {
506 if let Some(window) = documents.find_window(pipeline) {
507 window.drop_devtools_timeline_markers(marker_types);
508 }
509}
510
511pub(crate) fn handle_request_animation_frame(
512 documents: &DocumentCollection,
513 id: PipelineId,
514 actor_name: String,
515) {
516 if let Some(doc) = documents.find_document(id) {
517 doc.request_animation_frame(AnimationFrameCallback::DevtoolsFramerateTick { actor_name });
518 }
519}
520
521pub(crate) fn handle_reload(documents: &DocumentCollection, id: PipelineId, can_gc: CanGc) {
522 if let Some(win) = documents.find_window(id) {
523 win.Location().reload_without_origin_check(can_gc);
524 }
525}
526
527pub(crate) fn handle_get_css_database(reply: IpcSender<HashMap<String, CssDatabaseProperty>>) {
528 let database: HashMap<_, _> = ENABLED_LONGHAND_PROPERTIES
529 .iter()
530 .map(|l| {
531 (
532 l.name().into(),
533 CssDatabaseProperty {
534 is_inherited: l.inherited(),
535 values: vec![], supports: vec![],
537 subproperties: vec![l.name().into()],
538 },
539 )
540 })
541 .collect();
542 let _ = reply.send(database);
543}
544
545pub(crate) fn handle_highlight_dom_node(
546 documents: &DocumentCollection,
547 id: PipelineId,
548 node_id: Option<String>,
549) {
550 let node = node_id.and_then(|node_id| {
551 let node = find_node_by_unique_id(documents, id, &node_id);
552 if node.is_none() {
553 log::warn!("Node id {node_id} for pipeline id {id} is not found",);
554 }
555 node
556 });
557
558 if let Some(window) = documents.find_window(id) {
559 window.Document().highlight_dom_node(node.as_deref());
560 }
561}