script/layout_dom/
node.rs1#![expect(unsafe_code)]
6
7use std::borrow::Cow;
8use std::fmt;
9use std::iter::FusedIterator;
10
11use base::id::{BrowsingContextId, PipelineId};
12use layout_api::wrapper_traits::{
13 LayoutDataTrait, LayoutNode, PseudoElementChain, SharedSelection, ThreadSafeLayoutElement,
14 ThreadSafeLayoutNode,
15};
16use layout_api::{
17 GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType,
18 SVGElementData, StyleData, TrustedNodeAddress,
19};
20use net_traits::image_cache::Image;
21use pixels::ImageMetadata;
22use selectors::Element as _;
23use servo_arc::Arc;
24use servo_url::ServoUrl;
25use style;
26use style::dom::{NodeInfo, TElement, TNode, TShadowRoot};
27use style::properties::ComputedValues;
28use style::selector_parser::PseudoElement;
29
30use super::{
31 ServoLayoutDocument, ServoLayoutElement, ServoShadowRoot, ServoThreadSafeLayoutElement,
32};
33use crate::dom::bindings::inheritance::NodeTypeId;
34use crate::dom::bindings::root::LayoutDom;
35use crate::dom::element::{Element, LayoutElementHelpers};
36use crate::dom::node::{LayoutNodeHelpers, Node, NodeFlags, NodeTypeIdWrapper};
37
38#[derive(Clone, Copy, PartialEq)]
44#[repr(transparent)]
45pub struct ServoLayoutNode<'dom> {
46 pub(super) node: LayoutDom<'dom, Node>,
48}
49
50unsafe impl Send for ServoLayoutNode<'_> {}
57unsafe impl Sync for ServoLayoutNode<'_> {}
58
59impl fmt::Debug for ServoLayoutNode<'_> {
60 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61 if let Some(el) = self.as_element() {
62 el.fmt(f)
63 } else if self.is_text_node() {
64 write!(f, "<text node> ({:#x})", self.opaque().0)
65 } else {
66 write!(f, "<non-text node> ({:#x})", self.opaque().0)
67 }
68 }
69}
70
71impl<'dom> ServoLayoutNode<'dom> {
72 pub(super) fn from_layout_js(n: LayoutDom<'dom, Node>) -> Self {
73 ServoLayoutNode { node: n }
74 }
75
76 pub unsafe fn new(address: &TrustedNodeAddress) -> Self {
82 let node = unsafe { LayoutDom::from_trusted_node_address(*address) };
83 ServoLayoutNode::from_layout_js(node)
84 }
85
86 pub(super) fn script_type_id(&self) -> NodeTypeId {
87 self.node.type_id_for_layout()
88 }
89
90 pub(crate) fn get_jsmanaged(self) -> LayoutDom<'dom, Node> {
92 self.node
93 }
94
95 pub(crate) fn assigned_slot(self) -> Option<ServoLayoutElement<'dom>> {
96 self.node
97 .assigned_slot_for_layout()
98 .as_ref()
99 .map(LayoutDom::upcast)
100 .map(ServoLayoutElement::from_layout_js)
101 }
102}
103
104impl style::dom::NodeInfo for ServoLayoutNode<'_> {
105 fn is_element(&self) -> bool {
106 self.node.is_element_for_layout()
107 }
108
109 fn is_text_node(&self) -> bool {
110 self.node.is_text_node_for_layout()
111 }
112}
113
114impl<'dom> style::dom::TNode for ServoLayoutNode<'dom> {
115 type ConcreteDocument = ServoLayoutDocument<'dom>;
116 type ConcreteElement = ServoLayoutElement<'dom>;
117 type ConcreteShadowRoot = ServoShadowRoot<'dom>;
118
119 fn parent_node(&self) -> Option<Self> {
120 self.node.parent_node_ref().map(Self::from_layout_js)
121 }
122
123 fn first_child(&self) -> Option<Self> {
124 self.node.first_child_ref().map(Self::from_layout_js)
125 }
126
127 fn last_child(&self) -> Option<Self> {
128 self.node.last_child_ref().map(Self::from_layout_js)
129 }
130
131 fn prev_sibling(&self) -> Option<Self> {
132 self.node.prev_sibling_ref().map(Self::from_layout_js)
133 }
134
135 fn next_sibling(&self) -> Option<Self> {
136 self.node.next_sibling_ref().map(Self::from_layout_js)
137 }
138
139 fn owner_doc(&self) -> Self::ConcreteDocument {
140 ServoLayoutDocument::from_layout_js(self.node.owner_doc_for_layout())
141 }
142
143 fn traversal_parent(&self) -> Option<ServoLayoutElement<'dom>> {
144 if let Some(assigned_slot) = self.assigned_slot() {
145 return Some(assigned_slot);
146 }
147 let parent = self.parent_node()?;
148 if let Some(shadow) = parent.as_shadow_root() {
149 return Some(shadow.host());
150 };
151 parent.as_element()
152 }
153
154 fn opaque(&self) -> style::dom::OpaqueNode {
155 self.get_jsmanaged().opaque()
156 }
157
158 fn debug_id(self) -> usize {
159 self.opaque().0
160 }
161
162 fn as_element(&self) -> Option<ServoLayoutElement<'dom>> {
163 self.node.downcast().map(ServoLayoutElement::from_layout_js)
164 }
165
166 fn as_document(&self) -> Option<ServoLayoutDocument<'dom>> {
167 self.node
168 .downcast()
169 .map(ServoLayoutDocument::from_layout_js)
170 }
171
172 fn as_shadow_root(&self) -> Option<ServoShadowRoot<'dom>> {
173 self.node.downcast().map(ServoShadowRoot::from_layout_js)
174 }
175
176 fn is_in_document(&self) -> bool {
177 unsafe { self.node.get_flag(NodeFlags::IS_IN_A_DOCUMENT_TREE) }
178 }
179}
180
181impl<'dom> LayoutNode<'dom> for ServoLayoutNode<'dom> {
182 type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'dom>;
183
184 fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode {
185 ServoThreadSafeLayoutNode::new(*self)
186 }
187
188 fn type_id(&self) -> LayoutNodeType {
189 NodeTypeIdWrapper(self.script_type_id()).into()
190 }
191
192 unsafe fn initialize_style_and_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
193 let inner = self.get_jsmanaged();
194 if inner.style_data().is_none() {
195 unsafe { inner.initialize_style_data() };
196 }
197 if inner.layout_data().is_none() {
198 unsafe { inner.initialize_layout_data(Box::<RequestedLayoutDataType>::default()) };
199 }
200 }
201
202 fn is_connected(&self) -> bool {
203 unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) }
204 }
205
206 fn style_data(&self) -> Option<&'dom StyleData> {
207 self.get_jsmanaged().style_data()
208 }
209
210 fn layout_data(&self) -> Option<&'dom GenericLayoutData> {
211 self.get_jsmanaged().layout_data()
212 }
213}
214
215#[derive(Clone, Copy, Debug, PartialEq)]
219pub struct ServoThreadSafeLayoutNode<'dom> {
220 pub(super) node: ServoLayoutNode<'dom>,
222
223 pub(super) pseudo_element_chain: PseudoElementChain,
225}
226
227impl<'dom> ServoThreadSafeLayoutNode<'dom> {
228 pub fn new(node: ServoLayoutNode<'dom>) -> Self {
230 ServoThreadSafeLayoutNode {
231 node,
232 pseudo_element_chain: Default::default(),
233 }
234 }
235
236 unsafe fn get_jsmanaged(&self) -> LayoutDom<'dom, Node> {
239 self.node.get_jsmanaged()
240 }
241
242 unsafe fn dangerous_first_child(&self) -> Option<Self> {
245 let js_managed = unsafe { self.get_jsmanaged() };
246 js_managed
247 .first_child_ref()
248 .map(ServoLayoutNode::from_layout_js)
249 .map(Self::new)
250 }
251
252 unsafe fn dangerous_next_sibling(&self) -> Option<Self> {
255 let js_managed = unsafe { self.get_jsmanaged() };
256 js_managed
257 .next_sibling_ref()
258 .map(ServoLayoutNode::from_layout_js)
259 .map(Self::new)
260 }
261
262 pub fn is_single_line_text_input(&self) -> bool {
270 self.type_id() == Some(LayoutNodeType::Element(LayoutElementType::HTMLInputElement)) ||
271 (self.pseudo_element_chain.is_empty() &&
272 self.node.node.is_text_container_of_single_line_input())
273 }
274
275 pub fn selected_style(&self) -> Arc<ComputedValues> {
276 let Some(element) = self.as_element() else {
277 debug_assert!(self.is_text_node());
279 return self.parent_style();
280 };
281
282 let style_data = &element.style_data().styles;
283 let get_selected_style = || {
284 if self.node.node.is_in_ua_widget() {
288 return Some(element.containing_shadow_host()?.as_node().selected_style());
289 }
290 style_data.pseudos.get(&PseudoElement::Selection).cloned()
291 };
292
293 get_selected_style().unwrap_or_else(|| style_data.primary().clone())
294 }
295}
296
297impl style::dom::NodeInfo for ServoThreadSafeLayoutNode<'_> {
298 fn is_element(&self) -> bool {
299 self.node.is_element()
300 }
301
302 fn is_text_node(&self) -> bool {
303 self.node.is_text_node()
304 }
305}
306
307impl<'dom> ThreadSafeLayoutNode<'dom> for ServoThreadSafeLayoutNode<'dom> {
308 type ConcreteNode = ServoLayoutNode<'dom>;
309 type ConcreteThreadSafeLayoutElement = ServoThreadSafeLayoutElement<'dom>;
310 type ConcreteElement = ServoLayoutElement<'dom>;
311 type ChildrenIterator = ServoThreadSafeLayoutNodeChildrenIterator<'dom>;
312
313 fn opaque(&self) -> style::dom::OpaqueNode {
314 unsafe { self.get_jsmanaged().opaque() }
315 }
316
317 fn pseudo_element_chain(&self) -> PseudoElementChain {
318 self.pseudo_element_chain
319 }
320
321 fn type_id(&self) -> Option<LayoutNodeType> {
322 if self.pseudo_element_chain.is_empty() {
323 Some(self.node.type_id())
324 } else {
325 None
326 }
327 }
328
329 fn parent_style(&self) -> Arc<ComputedValues> {
330 let parent_element = self.node.traversal_parent().unwrap();
331 let parent_data = parent_element.borrow_data().unwrap();
332 parent_data.styles.primary().clone()
333 }
334
335 fn initialize_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
336 let inner = self.node.get_jsmanaged();
337 if inner.layout_data().is_none() {
338 unsafe {
339 inner.initialize_layout_data(Box::<RequestedLayoutDataType>::default());
340 }
341 }
342 }
343
344 fn debug_id(self) -> usize {
345 self.node.debug_id()
346 }
347
348 fn children(&self) -> style::dom::LayoutIterator<Self::ChildrenIterator> {
349 style::dom::LayoutIterator(ServoThreadSafeLayoutNodeChildrenIterator::new(*self))
350 }
351
352 fn as_element(&self) -> Option<ServoThreadSafeLayoutElement<'dom>> {
353 self.node
354 .as_element()
355 .map(|el| ServoThreadSafeLayoutElement {
356 element: el,
357 pseudo_element_chain: self.pseudo_element_chain,
358 })
359 }
360
361 fn as_html_element(&self) -> Option<ServoThreadSafeLayoutElement<'dom>> {
362 self.as_element()
363 .filter(|element| element.element.is_html_element())
364 }
365
366 fn style_data(&self) -> Option<&'dom StyleData> {
367 self.node.style_data()
368 }
369
370 fn layout_data(&self) -> Option<&'dom GenericLayoutData> {
371 self.node.layout_data()
372 }
373
374 fn unsafe_get(self) -> Self::ConcreteNode {
375 self.node
376 }
377
378 fn text_content(self) -> Cow<'dom, str> {
379 unsafe { self.get_jsmanaged().text_content() }
380 }
381
382 fn selection(&self) -> Option<SharedSelection> {
383 let this = unsafe { self.get_jsmanaged() };
384 this.selection()
385 }
386
387 fn image_url(&self) -> Option<ServoUrl> {
388 let this = unsafe { self.get_jsmanaged() };
389 this.image_url()
390 }
391
392 fn image_density(&self) -> Option<f64> {
393 let this = unsafe { self.get_jsmanaged() };
394 this.image_density()
395 }
396
397 fn showing_broken_image_icon(&self) -> bool {
398 let this = unsafe { self.get_jsmanaged() };
399 this.showing_broken_image_icon()
400 }
401
402 fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
403 let this = unsafe { self.get_jsmanaged() };
404 this.image_data()
405 }
406
407 fn canvas_data(&self) -> Option<HTMLCanvasData> {
408 let this = unsafe { self.get_jsmanaged() };
409 this.canvas_data()
410 }
411
412 fn media_data(&self) -> Option<HTMLMediaData> {
413 let this = unsafe { self.get_jsmanaged() };
414 this.media_data()
415 }
416
417 fn svg_data(&self) -> Option<SVGElementData<'dom>> {
418 let this = unsafe { self.get_jsmanaged() };
419 this.svg_data()
420 }
421
422 fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId> {
424 let this = unsafe { self.get_jsmanaged() };
425 this.iframe_browsing_context_id()
426 }
427
428 fn iframe_pipeline_id(&self) -> Option<PipelineId> {
430 let this = unsafe { self.get_jsmanaged() };
431 this.iframe_pipeline_id()
432 }
433
434 fn get_span(&self) -> Option<u32> {
435 unsafe {
436 self.get_jsmanaged()
437 .downcast::<Element>()
438 .unwrap()
439 .get_span()
440 }
441 }
442
443 fn get_colspan(&self) -> Option<u32> {
444 unsafe {
445 self.get_jsmanaged()
446 .downcast::<Element>()
447 .unwrap()
448 .get_colspan()
449 }
450 }
451
452 fn get_rowspan(&self) -> Option<u32> {
453 unsafe {
454 self.get_jsmanaged()
455 .downcast::<Element>()
456 .unwrap()
457 .get_rowspan()
458 }
459 }
460
461 fn with_pseudo_element_chain(&self, pseudo_element_chain: PseudoElementChain) -> Self {
462 Self {
463 node: self.node,
464 pseudo_element_chain,
465 }
466 }
467
468 fn set_uses_content_attribute_with_attr(&self, uses_content_attribute_with_attr: bool) {
473 unsafe {
474 self.node.node.set_flag(
475 NodeFlags::USES_ATTR_IN_CONTENT_ATTRIBUTE,
476 uses_content_attribute_with_attr,
477 )
478 }
479 }
480}
481
482pub enum ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
483 Node(Option<ServoThreadSafeLayoutNode<'dom>>),
485 Slottables(<Vec<ServoLayoutNode<'dom>> as IntoIterator>::IntoIter),
487}
488
489impl<'dom> ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
490 #[expect(unsafe_code)]
491 fn new(
492 parent: ServoThreadSafeLayoutNode<'dom>,
493 ) -> ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
494 if let Some(element) = parent.as_element() {
495 if let Some(shadow) = element.shadow_root() {
496 return Self::new(shadow.as_node().to_threadsafe());
497 };
498
499 let slotted_nodes = element.slotted_nodes();
500 if !slotted_nodes.is_empty() {
501 #[expect(clippy::unnecessary_to_owned)] return Self::Slottables(slotted_nodes.to_owned().into_iter());
503 }
504 }
505
506 Self::Node(unsafe { parent.dangerous_first_child() })
507 }
508}
509
510impl<'dom> Iterator for ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
511 type Item = ServoThreadSafeLayoutNode<'dom>;
512
513 fn next(&mut self) -> Option<Self::Item> {
514 match self {
515 Self::Node(node) => {
516 let next_sibling = unsafe { (*node)?.dangerous_next_sibling() };
517 std::mem::replace(node, next_sibling)
518 },
519 Self::Slottables(slots) => slots.next().map(|node| node.to_threadsafe()),
520 }
521 }
522}
523
524impl FusedIterator for ServoThreadSafeLayoutNodeChildrenIterator<'_> {}