script/layout_dom/
element.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::hash::Hash;
6use std::sync::atomic::Ordering;
7use std::{fmt, slice};
8
9use atomic_refcell::{AtomicRef, AtomicRefMut};
10use embedder_traits::UntrustedNodeAddress;
11use html5ever::{LocalName, Namespace, local_name, ns};
12use js::jsapi::JSObject;
13use layout_api::wrapper_traits::{
14    LayoutNode, PseudoElementChain, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
15};
16use layout_api::{LayoutDamage, LayoutNodeType, StyleData};
17use selectors::Element as _;
18use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
19use selectors::bloom::{BLOOM_HASH_MASK, BloomFilter};
20use selectors::matching::{ElementSelectorFlags, MatchingContext, VisitedHandlingMode};
21use selectors::sink::Push;
22use servo_arc::{Arc, ArcBorrow};
23use style::CaseSensitivityExt;
24use style::animation::AnimationSetKey;
25use style::applicable_declarations::ApplicableDeclarationBlock;
26use style::attr::AttrValue;
27use style::bloom::each_relevant_element_hash;
28use style::context::SharedStyleContext;
29use style::data::ElementData;
30use style::dom::{DomChildren, LayoutIterator, TDocument, TElement, TNode, TShadowRoot};
31use style::properties::{ComputedValues, PropertyDeclarationBlock};
32use style::selector_parser::{
33    AttrValue as SelectorAttrValue, Lang, NonTSPseudoClass, PseudoElement, RestyleDamage,
34    SelectorImpl, extended_filtering,
35};
36use style::shared_lock::Locked as StyleLocked;
37use style::stylesheets::scope_rule::ImplicitScopeRoot;
38use style::values::computed::{Display, Image};
39use style::values::specified::align::AlignFlags;
40use style::values::specified::box_::{DisplayInside, DisplayOutside};
41use style::values::{AtomIdent, AtomString};
42use stylo_atoms::Atom;
43use stylo_dom::ElementState;
44
45use crate::dom::attr::AttrHelpersForLayout;
46use crate::dom::bindings::inheritance::{
47    Castable, CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, HTMLElementTypeId,
48    NodeTypeId, TextTypeId,
49};
50use crate::dom::bindings::root::LayoutDom;
51use crate::dom::characterdata::LayoutCharacterDataHelpers;
52use crate::dom::element::{Element, LayoutElementHelpers};
53use crate::dom::html::htmlslotelement::HTMLSlotElement;
54use crate::dom::node::{LayoutNodeHelpers, Node, NodeFlags};
55use crate::layout_dom::{ServoLayoutNode, ServoShadowRoot, ServoThreadSafeLayoutNode};
56
57/// A wrapper around elements that ensures layout can only ever access safe properties.
58#[derive(Clone, Copy, Eq, Hash, PartialEq)]
59pub struct ServoLayoutElement<'dom> {
60    /// The wrapped private DOM Element.
61    element: LayoutDom<'dom, Element>,
62}
63
64impl fmt::Debug for ServoLayoutElement<'_> {
65    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66        write!(f, "<{}", self.element.local_name())?;
67        if let Some(id) = self.id() {
68            write!(f, " id={}", id)?;
69        }
70        write!(f, "> ({:#x})", self.as_node().opaque().0)
71    }
72}
73
74impl<'dom> ServoLayoutElement<'dom> {
75    pub(super) fn from_layout_js(el: LayoutDom<'dom, Element>) -> Self {
76        ServoLayoutElement { element: el }
77    }
78
79    pub(super) fn is_html_element(&self) -> bool {
80        self.element.is_html_element()
81    }
82
83    #[inline]
84    fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
85        self.element.get_attr_for_layout(namespace, name)
86    }
87
88    #[inline]
89    fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str> {
90        self.element.get_attr_val_for_layout(namespace, name)
91    }
92
93    fn get_style_data(&self) -> Option<&StyleData> {
94        self.as_node().style_data()
95    }
96
97    /// Unset the snapshot flags on the underlying DOM object for this element.
98    ///
99    /// # Safety
100    ///
101    /// This function accesses and modifies the underlying DOM object and should
102    /// not be used by more than a single thread at once.
103    pub unsafe fn unset_snapshot_flags(&self) {
104        unsafe {
105            self.as_node()
106                .node
107                .set_flag(NodeFlags::HAS_SNAPSHOT | NodeFlags::HANDLED_SNAPSHOT, false);
108        }
109    }
110
111    /// Unset the snapshot flags on the underlying DOM object for this element.
112    ///
113    /// # Safety
114    ///
115    /// This function accesses and modifies the underlying DOM object and should
116    /// not be used by more than a single thread at once.
117    pub unsafe fn set_has_snapshot(&self) {
118        unsafe {
119            self.as_node().node.set_flag(NodeFlags::HAS_SNAPSHOT, true);
120        }
121    }
122
123    /// Returns true if this element is the body child of an html element root element.
124    fn is_body_element_of_html_element_root(&self) -> bool {
125        if self.element.local_name() != &local_name!("body") {
126            return false;
127        }
128
129        self.parent_element().is_some_and(|element| {
130            element.is_root() && element.element.local_name() == &local_name!("html")
131        })
132    }
133
134    /// Returns the parent element of this element, if it has one.
135    fn parent_element(&self) -> Option<Self> {
136        self.element
137            .upcast()
138            .composed_parent_node_ref()
139            .and_then(|node| node.downcast().map(ServoLayoutElement::from_layout_js))
140    }
141
142    fn is_root(&self) -> bool {
143        match self.as_node().parent_node() {
144            None => false,
145            Some(node) => matches!(node.script_type_id(), NodeTypeId::Document(_)),
146        }
147    }
148}
149
150pub enum DOMDescendantIterator<E>
151where
152    E: TElement,
153{
154    /// Iterating over the children of a node, including children of a potential
155    /// [ShadowRoot](crate::dom::shadow_root::ShadowRoot)
156    Children(DomChildren<E::ConcreteNode>),
157    /// Iterating over the content's of a [`<slot>`](HTMLSlotElement) element.
158    Slottables { slot: E, index: usize },
159}
160
161impl<E> Iterator for DOMDescendantIterator<E>
162where
163    E: TElement,
164{
165    type Item = E::ConcreteNode;
166
167    fn next(&mut self) -> Option<Self::Item> {
168        match self {
169            Self::Children(children) => children.next(),
170            Self::Slottables { slot, index } => {
171                let slottables = slot.slotted_nodes();
172                let slot = slottables.get(*index)?;
173                *index += 1;
174                Some(*slot)
175            },
176        }
177    }
178}
179
180impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
181    type ConcreteNode = ServoLayoutNode<'dom>;
182    type TraversalChildrenIterator = DOMDescendantIterator<Self>;
183
184    fn as_node(&self) -> ServoLayoutNode<'dom> {
185        ServoLayoutNode::from_layout_js(self.element.upcast())
186    }
187
188    fn traversal_children(&self) -> LayoutIterator<Self::TraversalChildrenIterator> {
189        let iterator = if self.slotted_nodes().is_empty() {
190            let children = if let Some(shadow_root) = self.shadow_root() {
191                shadow_root.as_node().dom_children()
192            } else {
193                self.as_node().dom_children()
194            };
195            DOMDescendantIterator::Children(children)
196        } else {
197            DOMDescendantIterator::Slottables {
198                slot: *self,
199                index: 0,
200            }
201        };
202
203        LayoutIterator(iterator)
204    }
205
206    fn traversal_parent(&self) -> Option<Self> {
207        self.as_node().traversal_parent()
208    }
209
210    fn inheritance_parent(&self) -> Option<Self> {
211        if self.is_pseudo_element() {
212            // The inheritance parent of an implemented pseudo-element should be the
213            // originating element, except if `is_element_backed()` is true, then it should
214            // be the flat tree parent. Note `is_element_backed()` differs from the CSS term.
215            // At the current time, `is_element_backed()` is always false in Servo.
216            //
217            // FIXME: handle the cases of element-backed pseudo-elements.
218            return self.pseudo_element_originating_element();
219        }
220
221        // FIXME: By default the inheritance parent would be the Self::parent_element
222        //        but probably we should use the flattened tree parent.
223        self.parent_element()
224    }
225
226    fn is_html_element(&self) -> bool {
227        ServoLayoutElement::is_html_element(self)
228    }
229
230    fn is_mathml_element(&self) -> bool {
231        *self.element.namespace() == ns!(mathml)
232    }
233
234    fn is_svg_element(&self) -> bool {
235        *self.element.namespace() == ns!(svg)
236    }
237
238    fn has_part_attr(&self) -> bool {
239        self.element
240            .get_attr_for_layout(&ns!(), &local_name!("part"))
241            .is_some()
242    }
243
244    fn exports_any_part(&self) -> bool {
245        self.element
246            .get_attr_for_layout(&ns!(), &local_name!("exportparts"))
247            .is_some()
248    }
249
250    fn style_attribute(&self) -> Option<ArcBorrow<'_, StyleLocked<PropertyDeclarationBlock>>> {
251        unsafe {
252            (*self.element.style_attribute())
253                .as_ref()
254                .map(|x| x.borrow_arc())
255        }
256    }
257
258    fn may_have_animations(&self) -> bool {
259        true
260    }
261
262    fn animation_rule(
263        &self,
264        context: &SharedStyleContext,
265    ) -> Option<Arc<StyleLocked<PropertyDeclarationBlock>>> {
266        let node = self.as_node();
267        let document = node.owner_doc();
268        context.animations.get_animation_declarations(
269            &AnimationSetKey::new_for_non_pseudo(node.opaque()),
270            context.current_time_for_animations,
271            document.style_shared_lock(),
272        )
273    }
274
275    fn transition_rule(
276        &self,
277        context: &SharedStyleContext,
278    ) -> Option<Arc<StyleLocked<PropertyDeclarationBlock>>> {
279        let node = self.as_node();
280        let document = node.owner_doc();
281        context.animations.get_transition_declarations(
282            &AnimationSetKey::new_for_non_pseudo(node.opaque()),
283            context.current_time_for_animations,
284            document.style_shared_lock(),
285        )
286    }
287
288    fn state(&self) -> ElementState {
289        self.element.get_state_for_layout()
290    }
291
292    #[inline]
293    fn id(&self) -> Option<&Atom> {
294        unsafe { (*self.element.id_attribute()).as_ref() }
295    }
296
297    #[inline(always)]
298    fn each_class<F>(&self, mut callback: F)
299    where
300        F: FnMut(&AtomIdent),
301    {
302        if let Some(classes) = self.element.get_classes_for_layout() {
303            for class in classes {
304                callback(AtomIdent::cast(class))
305            }
306        }
307    }
308
309    #[inline(always)]
310    fn each_attr_name<F>(&self, mut callback: F)
311    where
312        F: FnMut(&style::LocalName),
313    {
314        for attr in self.element.attrs() {
315            callback(style::values::GenericAtomIdent::cast(attr.local_name()))
316        }
317    }
318
319    fn each_part<F>(&self, mut callback: F)
320    where
321        F: FnMut(&AtomIdent),
322    {
323        if let Some(parts) = self.element.get_parts_for_layout() {
324            for part in parts {
325                callback(AtomIdent::cast(part))
326            }
327        }
328    }
329
330    fn each_exported_part<F>(&self, name: &AtomIdent, callback: F)
331    where
332        F: FnMut(&AtomIdent),
333    {
334        let Some(exported_parts) = self
335            .element
336            .get_attr_for_layout(&ns!(), &local_name!("exportparts"))
337        else {
338            return;
339        };
340        exported_parts
341            .as_shadow_parts()
342            .for_each_exported_part(AtomIdent::cast(name), callback);
343    }
344
345    fn has_dirty_descendants(&self) -> bool {
346        unsafe {
347            self.as_node()
348                .node
349                .get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS)
350        }
351    }
352
353    fn has_snapshot(&self) -> bool {
354        unsafe { self.as_node().node.get_flag(NodeFlags::HAS_SNAPSHOT) }
355    }
356
357    fn handled_snapshot(&self) -> bool {
358        unsafe { self.as_node().node.get_flag(NodeFlags::HANDLED_SNAPSHOT) }
359    }
360
361    unsafe fn set_handled_snapshot(&self) {
362        unsafe {
363            self.as_node()
364                .node
365                .set_flag(NodeFlags::HANDLED_SNAPSHOT, true);
366        }
367    }
368
369    unsafe fn set_dirty_descendants(&self) {
370        debug_assert!(self.as_node().is_connected());
371        unsafe {
372            self.as_node()
373                .node
374                .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true)
375        }
376    }
377
378    unsafe fn unset_dirty_descendants(&self) {
379        unsafe {
380            self.as_node()
381                .node
382                .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, false)
383        }
384    }
385
386    /// Whether this element should match user and content rules.
387    /// We would like to match rules from the same tree in all cases and optimize computation.
388    /// UA Widget is an exception since we could have a pseudo element selector inside it.
389    #[inline]
390    fn matches_user_and_content_rules(&self) -> bool {
391        !self.as_node().node.is_in_ua_widget()
392    }
393
394    /// Returns the pseudo-element implemented by this element, if any. In other words,
395    /// the element will match the specified pseudo element throughout the style computation.
396    #[inline]
397    fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
398        self.as_node().node.implemented_pseudo_element()
399    }
400
401    fn store_children_to_process(&self, n: isize) {
402        let data = self.get_style_data().unwrap();
403        data.parallel
404            .children_to_process
405            .store(n, Ordering::Relaxed);
406    }
407
408    fn did_process_child(&self) -> isize {
409        let data = self.get_style_data().unwrap();
410        let old_value = data
411            .parallel
412            .children_to_process
413            .fetch_sub(1, Ordering::Relaxed);
414        debug_assert!(old_value >= 1);
415        old_value - 1
416    }
417
418    unsafe fn clear_data(&self) {
419        unsafe { self.as_node().get_jsmanaged().clear_style_and_layout_data() }
420    }
421
422    unsafe fn ensure_data(&self) -> AtomicRefMut<'_, ElementData> {
423        unsafe {
424            self.as_node().get_jsmanaged().initialize_style_data();
425        };
426        self.mutate_data().unwrap()
427    }
428
429    /// Whether there is an ElementData container.
430    fn has_data(&self) -> bool {
431        self.get_style_data().is_some()
432    }
433
434    /// Immutably borrows the ElementData.
435    fn borrow_data(&self) -> Option<AtomicRef<'_, ElementData>> {
436        self.get_style_data().map(|data| data.element_data.borrow())
437    }
438
439    /// Mutably borrows the ElementData.
440    fn mutate_data(&self) -> Option<AtomicRefMut<'_, ElementData>> {
441        self.get_style_data()
442            .map(|data| data.element_data.borrow_mut())
443    }
444
445    fn skip_item_display_fixup(&self) -> bool {
446        false
447    }
448
449    fn has_animations(&self, context: &SharedStyleContext) -> bool {
450        // This is not used for pseudo elements currently so we can pass None.
451        self.has_css_animations(context, /* pseudo_element = */ None) ||
452            self.has_css_transitions(context, /* pseudo_element = */ None)
453    }
454
455    fn has_css_animations(
456        &self,
457        context: &SharedStyleContext,
458        pseudo_element: Option<PseudoElement>,
459    ) -> bool {
460        let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element);
461        context.animations.has_active_animations(&key)
462    }
463
464    fn has_css_transitions(
465        &self,
466        context: &SharedStyleContext,
467        pseudo_element: Option<PseudoElement>,
468    ) -> bool {
469        let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element);
470        context.animations.has_active_transitions(&key)
471    }
472
473    #[inline]
474    fn lang_attr(&self) -> Option<SelectorAttrValue> {
475        self.get_attr(&ns!(xml), &local_name!("lang"))
476            .or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
477            .map(|v| SelectorAttrValue::from(v as &str))
478    }
479
480    fn match_element_lang(
481        &self,
482        override_lang: Option<Option<SelectorAttrValue>>,
483        value: &Lang,
484    ) -> bool {
485        // Servo supports :lang() from CSS Selectors 4, which can take a comma-
486        // separated list of language tags in the pseudo-class, and which
487        // performs RFC 4647 extended filtering matching on them.
488        //
489        // FIXME(heycam): This is wrong, since extended_filtering accepts
490        // a string containing commas (separating each language tag in
491        // a list) but the pseudo-class instead should be parsing and
492        // storing separate <ident> or <string>s for each language tag.
493        //
494        // FIXME(heycam): Look at `element`'s document's Content-Language
495        // HTTP header for language tags to match `value` against.  To
496        // do this, we should make `get_lang_for_layout` return an Option,
497        // so we can decide when to fall back to the Content-Language check.
498        let element_lang = match override_lang {
499            Some(Some(lang)) => lang,
500            Some(None) => AtomString::default(),
501            None => AtomString::from(&*self.element.get_lang_for_layout()),
502        };
503        extended_filtering(&element_lang, value)
504    }
505
506    fn is_html_document_body_element(&self) -> bool {
507        self.is_body_element_of_html_element_root()
508    }
509
510    fn synthesize_presentational_hints_for_legacy_attributes<V>(
511        &self,
512        _visited_handling: VisitedHandlingMode,
513        hints: &mut V,
514    ) where
515        V: Push<ApplicableDeclarationBlock>,
516    {
517        self.element
518            .synthesize_presentational_hints_for_legacy_attributes(hints);
519    }
520
521    /// The shadow root this element is a host of.
522    fn shadow_root(&self) -> Option<ServoShadowRoot<'dom>> {
523        self.element
524            .get_shadow_root_for_layout()
525            .map(ServoShadowRoot::from_layout_js)
526    }
527
528    /// The shadow root which roots the subtree this element is contained in.
529    fn containing_shadow(&self) -> Option<ServoShadowRoot<'dom>> {
530        self.element
531            .upcast()
532            .containing_shadow_root_for_layout()
533            .map(ServoShadowRoot::from_layout_js)
534    }
535
536    fn local_name(&self) -> &LocalName {
537        self.element.local_name()
538    }
539
540    fn namespace(&self) -> &Namespace {
541        self.element.namespace()
542    }
543
544    fn query_container_size(
545        &self,
546        _display: &Display,
547    ) -> euclid::default::Size2D<Option<app_units::Au>> {
548        todo!();
549    }
550
551    fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
552        self.element.get_selector_flags().contains(flags)
553    }
554
555    fn relative_selector_search_direction(&self) -> ElementSelectorFlags {
556        self.element
557            .get_selector_flags()
558            .intersection(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING)
559    }
560
561    fn each_custom_state<F>(&self, callback: F)
562    where
563        F: FnMut(&AtomIdent),
564    {
565        self.element.each_custom_state_for_layout(callback);
566    }
567
568    /// Returns the implicit scope root for given sheet index and host.
569    fn implicit_scope_for_sheet_in_shadow_root(
570        opaque_host: ::selectors::OpaqueElement,
571        sheet_index: usize,
572    ) -> Option<ImplicitScopeRoot> {
573        // As long as this "unopaqued" element does not escape this function, we're not leaking
574        // potentially-mutable elements from opaque elements.
575        let host = unsafe {
576            let ptr = opaque_host.as_const_ptr::<JSObject>();
577            let untrusted_address = UntrustedNodeAddress::from_id(ptr as usize);
578            let node = Node::from_untrusted_node_address(untrusted_address);
579            let trusted_address = node.to_trusted_node_address();
580            let servo_layout_node = ServoLayoutNode::new(&trusted_address);
581            servo_layout_node.as_element().unwrap()
582        };
583        host.shadow_root()?.implicit_scope_for_sheet(sheet_index)
584    }
585
586    fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
587        let Some(slot_element) = self.element.unsafe_get().downcast::<HTMLSlotElement>() else {
588            return &[];
589        };
590        let assigned_nodes = slot_element.assigned_nodes();
591
592        // SAFETY:
593        // Self::ConcreteNode (aka ServoLayoutNode) and Slottable are guaranteed to have the same
594        // layout and alignment as ptr::NonNull<T>. Lifetimes are not an issue because the
595        // slottables are being kept alive by the slot element.
596        unsafe {
597            slice::from_raw_parts(
598                assigned_nodes.as_ptr() as *const Self::ConcreteNode,
599                assigned_nodes.len(),
600            )
601        }
602    }
603
604    fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
605        let box_tree_needs_rebuild = || {
606            let old_box = old.get_box();
607            let new_box = new.get_box();
608
609            if old_box.display != new_box.display ||
610                old_box.float != new_box.float ||
611                old_box.position != new_box.position
612            {
613                return true;
614            }
615
616            if new_box.position.is_absolutely_positioned() &&
617                old_box.original_display != new_box.original_display
618            {
619                return true;
620            }
621
622            if old.get_font() != new.get_font() {
623                return true;
624            }
625
626            if old.get_position().order != new.get_position().order {
627                return true;
628            }
629
630            // Only consider changes to the `quotes` attribute if they actually apply to this
631            // style (if it is a pseudo-element that supports it).
632            if matches!(
633                new.pseudo(),
634                Some(PseudoElement::Before | PseudoElement::After | PseudoElement::Marker),
635            ) && old.get_list().quotes != new.get_list().quotes
636            {
637                return true;
638            }
639
640            // NOTE: This should be kept in sync with the checks in `impl
641            // StyleExt::establishes_block_formatting_context` for `ComputedValues` in
642            // `components/layout/style_ext.rs`.
643            if new_box.display.outside() == DisplayOutside::Block &&
644                new_box.display.inside() == DisplayInside::Flow
645            {
646                let alignment_establishes_new_block_formatting_context =
647                    |style: &ComputedValues| {
648                        style.get_position().align_content.primary() != AlignFlags::NORMAL
649                    };
650
651                let old_column = old.get_column();
652                let new_column = new.get_column();
653                if old_box.overflow_x.is_scrollable() != new_box.overflow_x.is_scrollable() ||
654                    old_column.is_multicol() != new_column.is_multicol() ||
655                    old_column.column_span != new_column.column_span ||
656                    alignment_establishes_new_block_formatting_context(old) !=
657                        alignment_establishes_new_block_formatting_context(new)
658                {
659                    return true;
660                }
661            }
662
663            if old_box.display.is_list_item() {
664                let old_list = old.get_list();
665                let new_list = new.get_list();
666                if old_list.list_style_position != new_list.list_style_position ||
667                    old_list.list_style_image != new_list.list_style_image ||
668                    (new_list.list_style_image == Image::None &&
669                        old_list.list_style_type != new_list.list_style_type)
670                {
671                    return true;
672                }
673            }
674
675            if new.is_pseudo_style() && old.get_counters().content != new.get_counters().content {
676                return true;
677            }
678
679            false
680        };
681
682        let text_shaping_needs_recollect = || {
683            if old.clone_direction() != new.clone_direction() ||
684                old.clone_unicode_bidi() != new.clone_unicode_bidi()
685            {
686                return true;
687            }
688
689            let old_text = old.get_inherited_text().clone();
690            let new_text = new.get_inherited_text().clone();
691            if old_text.white_space_collapse != new_text.white_space_collapse ||
692                old_text.text_transform != new_text.text_transform ||
693                old_text.word_break != new_text.word_break ||
694                old_text.overflow_wrap != new_text.overflow_wrap ||
695                old_text.letter_spacing != new_text.letter_spacing ||
696                old_text.word_spacing != new_text.word_spacing ||
697                old_text.text_rendering != new_text.text_rendering
698            {
699                return true;
700            }
701
702            false
703        };
704
705        if box_tree_needs_rebuild() {
706            RestyleDamage::from_bits_retain(LayoutDamage::REBUILD_BOX.bits())
707        } else if text_shaping_needs_recollect() {
708            RestyleDamage::from_bits_retain(LayoutDamage::RECOLLECT_BOX_TREE_CHILDREN.bits())
709        } else {
710            // This element needs to be laid out again, but does not have any damage to
711            // its box. In the future, we will distinguish between types of damage to the
712            // fragment as well.
713            RestyleDamage::RELAYOUT
714        }
715    }
716}
717
718impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
719    type Impl = SelectorImpl;
720
721    fn opaque(&self) -> ::selectors::OpaqueElement {
722        ::selectors::OpaqueElement::new(unsafe { &*(self.as_node().opaque().0 as *const ()) })
723    }
724
725    fn parent_element(&self) -> Option<Self> {
726        ServoLayoutElement::parent_element(self)
727    }
728
729    fn parent_node_is_shadow_root(&self) -> bool {
730        match self.as_node().parent_node() {
731            None => false,
732            Some(node) => {
733                node.script_type_id() ==
734                    NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot)
735            },
736        }
737    }
738
739    fn containing_shadow_host(&self) -> Option<Self> {
740        self.containing_shadow().map(|s| s.host())
741    }
742
743    #[inline]
744    fn is_pseudo_element(&self) -> bool {
745        self.implemented_pseudo_element().is_some()
746    }
747
748    #[inline]
749    fn pseudo_element_originating_element(&self) -> Option<Self> {
750        debug_assert!(self.is_pseudo_element());
751        debug_assert!(!self.matches_user_and_content_rules());
752        self.containing_shadow_host()
753    }
754
755    fn prev_sibling_element(&self) -> Option<Self> {
756        let mut node = self.as_node();
757        while let Some(sibling) = node.prev_sibling() {
758            if let Some(element) = sibling.as_element() {
759                return Some(element);
760            }
761            node = sibling;
762        }
763        None
764    }
765
766    fn next_sibling_element(&self) -> Option<ServoLayoutElement<'dom>> {
767        let mut node = self.as_node();
768        while let Some(sibling) = node.next_sibling() {
769            if let Some(element) = sibling.as_element() {
770                return Some(element);
771            }
772            node = sibling;
773        }
774        None
775    }
776
777    fn first_element_child(&self) -> Option<Self> {
778        self.as_node()
779            .dom_children()
780            .find_map(|child| child.as_element())
781    }
782
783    fn attr_matches(
784        &self,
785        ns: &NamespaceConstraint<&style::Namespace>,
786        local_name: &style::LocalName,
787        operation: &AttrSelectorOperation<&AtomString>,
788    ) -> bool {
789        match *ns {
790            NamespaceConstraint::Specific(ns) => self
791                .get_attr_enum(ns, local_name)
792                .is_some_and(|value| value.eval_selector(operation)),
793            NamespaceConstraint::Any => self
794                .element
795                .get_attr_vals_for_layout(local_name)
796                .any(|value| value.eval_selector(operation)),
797        }
798    }
799
800    fn is_root(&self) -> bool {
801        ServoLayoutElement::is_root(self)
802    }
803
804    fn is_empty(&self) -> bool {
805        self.as_node()
806            .dom_children()
807            .all(|node| match node.script_type_id() {
808                NodeTypeId::Element(..) => false,
809                NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => {
810                    node.node.downcast().unwrap().data_for_layout().is_empty()
811                },
812                _ => true,
813            })
814    }
815
816    #[inline]
817    fn has_local_name(&self, name: &LocalName) -> bool {
818        self.element.local_name() == name
819    }
820
821    #[inline]
822    fn has_namespace(&self, ns: &Namespace) -> bool {
823        self.element.namespace() == ns
824    }
825
826    #[inline]
827    fn is_same_type(&self, other: &Self) -> bool {
828        self.element.local_name() == other.element.local_name() &&
829            self.element.namespace() == other.element.namespace()
830    }
831
832    fn match_non_ts_pseudo_class(
833        &self,
834        pseudo_class: &NonTSPseudoClass,
835        _: &mut MatchingContext<Self::Impl>,
836    ) -> bool {
837        match *pseudo_class {
838            // https://github.com/servo/servo/issues/8718
839            NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(),
840            NonTSPseudoClass::Visited => false,
841
842            NonTSPseudoClass::CustomState(ref state) => self.has_custom_state(&state.0),
843            NonTSPseudoClass::Lang(ref lang) => self.match_element_lang(None, lang),
844
845            NonTSPseudoClass::ServoNonZeroBorder => !matches!(
846                self.element
847                    .get_attr_for_layout(&ns!(), &local_name!("border")),
848                None | Some(&AttrValue::UInt(_, 0))
849            ),
850            NonTSPseudoClass::ReadOnly => !self
851                .element
852                .get_state_for_layout()
853                .contains(NonTSPseudoClass::ReadWrite.state_flag()),
854
855            NonTSPseudoClass::Active |
856            NonTSPseudoClass::Autofill |
857            NonTSPseudoClass::Checked |
858            NonTSPseudoClass::Default |
859            NonTSPseudoClass::Defined |
860            NonTSPseudoClass::Disabled |
861            NonTSPseudoClass::Enabled |
862            NonTSPseudoClass::Focus |
863            NonTSPseudoClass::FocusVisible |
864            NonTSPseudoClass::FocusWithin |
865            NonTSPseudoClass::Fullscreen |
866            NonTSPseudoClass::Hover |
867            NonTSPseudoClass::InRange |
868            NonTSPseudoClass::Indeterminate |
869            NonTSPseudoClass::Invalid |
870            NonTSPseudoClass::Modal |
871            NonTSPseudoClass::MozMeterOptimum |
872            NonTSPseudoClass::MozMeterSubOptimum |
873            NonTSPseudoClass::MozMeterSubSubOptimum |
874            NonTSPseudoClass::Optional |
875            NonTSPseudoClass::OutOfRange |
876            NonTSPseudoClass::PlaceholderShown |
877            NonTSPseudoClass::PopoverOpen |
878            NonTSPseudoClass::ReadWrite |
879            NonTSPseudoClass::Required |
880            NonTSPseudoClass::Target |
881            NonTSPseudoClass::UserInvalid |
882            NonTSPseudoClass::UserValid |
883            NonTSPseudoClass::Valid => self
884                .element
885                .get_state_for_layout()
886                .contains(pseudo_class.state_flag()),
887        }
888    }
889
890    fn match_pseudo_element(
891        &self,
892        pseudo: &PseudoElement,
893        _context: &mut MatchingContext<Self::Impl>,
894    ) -> bool {
895        self.implemented_pseudo_element() == Some(*pseudo)
896    }
897
898    #[inline]
899    fn is_link(&self) -> bool {
900        match self.as_node().script_type_id() {
901            // https://html.spec.whatwg.org/multipage/#selector-link
902            NodeTypeId::Element(ElementTypeId::HTMLElement(
903                HTMLElementTypeId::HTMLAnchorElement,
904            )) |
905            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAreaElement)) |
906            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) => {
907                self.element
908                    .get_attr_val_for_layout(&ns!(), &local_name!("href"))
909                    .is_some()
910            },
911            _ => false,
912        }
913    }
914
915    #[inline]
916    fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
917        unsafe {
918            (*self.element.id_attribute())
919                .as_ref()
920                .is_some_and(|atom| case_sensitivity.eq_atom(atom, id))
921        }
922    }
923
924    #[inline]
925    fn is_part(&self, name: &AtomIdent) -> bool {
926        self.element.has_class_or_part_for_layout(
927            name,
928            &local_name!("part"),
929            CaseSensitivity::CaseSensitive,
930        )
931    }
932
933    fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> {
934        self.element
935            .get_attr_for_layout(&ns!(), &local_name!("exportparts"))?
936            .as_shadow_parts()
937            .imported_part(name)
938            .map(|import| AtomIdent::new(import.clone()))
939    }
940
941    #[inline]
942    fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
943        self.element
944            .has_class_or_part_for_layout(name, &local_name!("class"), case_sensitivity)
945    }
946
947    fn is_html_slot_element(&self) -> bool {
948        self.element.is::<HTMLSlotElement>()
949    }
950
951    fn assigned_slot(&self) -> Option<Self> {
952        self.as_node().assigned_slot()
953    }
954
955    fn is_html_element_in_html_document(&self) -> bool {
956        self.element.is_html_element() && self.as_node().owner_doc().is_html_document()
957    }
958
959    fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
960        // Handle flags that apply to the element.
961        let self_flags = flags.for_self();
962        if !self_flags.is_empty() {
963            self.element.insert_selector_flags(flags);
964        }
965
966        // Handle flags that apply to the parent.
967        let parent_flags = flags.for_parent();
968        if !parent_flags.is_empty() {
969            if let Some(p) = self.as_node().parent_element() {
970                p.element.insert_selector_flags(flags);
971            }
972        }
973    }
974
975    fn add_element_unique_hashes(&self, filter: &mut BloomFilter) -> bool {
976        each_relevant_element_hash(*self, |hash| filter.insert_hash(hash & BLOOM_HASH_MASK));
977        true
978    }
979
980    fn has_custom_state(&self, name: &AtomIdent) -> bool {
981        let mut has_state = false;
982        self.element
983            .each_custom_state_for_layout(|state| has_state |= state == name);
984
985        has_state
986    }
987}
988
989/// A wrapper around elements that ensures layout can only
990/// ever access safe properties and cannot race on elements.
991#[derive(Clone, Copy, Debug)]
992pub struct ServoThreadSafeLayoutElement<'dom> {
993    /// The wrapped [`ServoLayoutElement`].
994    pub(super) element: ServoLayoutElement<'dom>,
995
996    /// The possibly nested [`PseudoElementChain`] for this element.
997    pub(super) pseudo_element_chain: PseudoElementChain,
998}
999
1000impl<'dom> ServoThreadSafeLayoutElement<'dom> {
1001    /// The shadow root this element is a host of.
1002    pub fn shadow_root(&self) -> Option<ServoShadowRoot<'dom>> {
1003        self.element
1004            .element
1005            .get_shadow_root_for_layout()
1006            .map(ServoShadowRoot::from_layout_js)
1007    }
1008
1009    pub fn slotted_nodes(&self) -> &[ServoLayoutNode<'dom>] {
1010        self.element.slotted_nodes()
1011    }
1012}
1013
1014impl<'dom> ThreadSafeLayoutElement<'dom> for ServoThreadSafeLayoutElement<'dom> {
1015    type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'dom>;
1016    type ConcreteElement = ServoLayoutElement<'dom>;
1017
1018    fn as_node(&self) -> ServoThreadSafeLayoutNode<'dom> {
1019        ServoThreadSafeLayoutNode {
1020            node: self.element.as_node(),
1021            pseudo_element_chain: self.pseudo_element_chain,
1022        }
1023    }
1024
1025    fn pseudo_element_chain(&self) -> PseudoElementChain {
1026        self.pseudo_element_chain
1027    }
1028
1029    fn with_pseudo(&self, pseudo_element: PseudoElement) -> Option<Self> {
1030        if pseudo_element.is_eager() &&
1031            self.style_data()
1032                .styles
1033                .pseudos
1034                .get(&pseudo_element)
1035                .is_none()
1036        {
1037            return None;
1038        }
1039
1040        if pseudo_element == PseudoElement::DetailsSummary &&
1041            (!self.has_local_name(&local_name!("details")) || !self.has_namespace(&ns!(html)))
1042        {
1043            return None;
1044        }
1045
1046        if pseudo_element == PseudoElement::DetailsContent &&
1047            (!self.has_local_name(&local_name!("details")) ||
1048                !self.has_namespace(&ns!(html)) ||
1049                self.get_attr(&ns!(), &local_name!("open")).is_none())
1050        {
1051            return None;
1052        }
1053
1054        // These pseudo-element type cannot be nested.
1055        if !self.pseudo_element_chain.is_empty() {
1056            assert!(!pseudo_element.is_eager());
1057            assert!(pseudo_element != PseudoElement::DetailsSummary);
1058            assert!(pseudo_element != PseudoElement::DetailsContent);
1059        }
1060
1061        Some(ServoThreadSafeLayoutElement {
1062            element: self.element,
1063            pseudo_element_chain: self.pseudo_element_chain.with_pseudo(pseudo_element),
1064        })
1065    }
1066
1067    fn type_id(&self) -> Option<LayoutNodeType> {
1068        self.as_node().type_id()
1069    }
1070
1071    fn unsafe_get(self) -> ServoLayoutElement<'dom> {
1072        self.element
1073    }
1074
1075    fn get_local_name(&self) -> &LocalName {
1076        self.element.local_name()
1077    }
1078
1079    fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
1080        self.element.get_attr_enum(namespace, name)
1081    }
1082
1083    fn get_attr<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str> {
1084        self.element.get_attr(namespace, name)
1085    }
1086
1087    fn style_data(&self) -> AtomicRef<'_, ElementData> {
1088        self.element.borrow_data().expect("Unstyled layout node?")
1089    }
1090
1091    fn is_shadow_host(&self) -> bool {
1092        self.element.shadow_root().is_some()
1093    }
1094
1095    fn is_body_element_of_html_element_root(&self) -> bool {
1096        self.element.is_html_document_body_element()
1097    }
1098
1099    fn is_root(&self) -> bool {
1100        self.element.is_root()
1101    }
1102}
1103
1104/// This implementation of `::selectors::Element` is used for implementing lazy
1105/// pseudo-elements.
1106///
1107/// Lazy pseudo-elements in Servo only allows selectors using safe properties,
1108/// i.e., local_name, attributes, so they can only be used for **private**
1109/// pseudo-elements (like `::-servo-details-content`).
1110///
1111/// Probably a few more of this functions can be implemented (like `has_class`, etc.),
1112/// but they have no use right now.
1113///
1114/// Note that the element implementation is needed only for selector matching,
1115/// not for inheritance (styles are inherited appropriately).
1116impl ::selectors::Element for ServoThreadSafeLayoutElement<'_> {
1117    type Impl = SelectorImpl;
1118
1119    fn opaque(&self) -> ::selectors::OpaqueElement {
1120        ::selectors::OpaqueElement::new(unsafe { &*(self.as_node().opaque().0 as *const ()) })
1121    }
1122
1123    fn parent_element(&self) -> Option<Self> {
1124        warn!("ServoThreadSafeLayoutElement::parent_element called");
1125        None
1126    }
1127
1128    #[inline]
1129    fn parent_node_is_shadow_root(&self) -> bool {
1130        self.element.parent_node_is_shadow_root()
1131    }
1132
1133    #[inline]
1134    fn containing_shadow_host(&self) -> Option<Self> {
1135        self.element
1136            .containing_shadow_host()
1137            .and_then(|element| element.as_node().to_threadsafe().as_element())
1138    }
1139
1140    #[inline]
1141    fn is_pseudo_element(&self) -> bool {
1142        self.element.is_pseudo_element()
1143    }
1144
1145    #[inline]
1146    fn pseudo_element_originating_element(&self) -> Option<Self> {
1147        self.element
1148            .pseudo_element_originating_element()
1149            .and_then(|element| element.as_node().to_threadsafe().as_element())
1150    }
1151
1152    // Skips non-element nodes
1153    fn prev_sibling_element(&self) -> Option<Self> {
1154        warn!("ServoThreadSafeLayoutElement::prev_sibling_element called");
1155        None
1156    }
1157
1158    // Skips non-element nodes
1159    fn next_sibling_element(&self) -> Option<Self> {
1160        warn!("ServoThreadSafeLayoutElement::next_sibling_element called");
1161        None
1162    }
1163
1164    // Skips non-element nodes
1165    fn first_element_child(&self) -> Option<Self> {
1166        warn!("ServoThreadSafeLayoutElement::first_element_child called");
1167        None
1168    }
1169
1170    fn is_html_slot_element(&self) -> bool {
1171        self.element.is_html_slot_element()
1172    }
1173
1174    fn is_html_element_in_html_document(&self) -> bool {
1175        self.element.is_html_element_in_html_document()
1176    }
1177
1178    #[inline]
1179    fn has_local_name(&self, name: &LocalName) -> bool {
1180        self.element.local_name() == name
1181    }
1182
1183    #[inline]
1184    fn has_namespace(&self, ns: &Namespace) -> bool {
1185        self.element.namespace() == ns
1186    }
1187
1188    #[inline]
1189    fn is_same_type(&self, other: &Self) -> bool {
1190        self.element.local_name() == other.element.local_name() &&
1191            self.element.namespace() == other.element.namespace()
1192    }
1193
1194    fn attr_matches(
1195        &self,
1196        ns: &NamespaceConstraint<&style::Namespace>,
1197        local_name: &style::LocalName,
1198        operation: &AttrSelectorOperation<&AtomString>,
1199    ) -> bool {
1200        match *ns {
1201            NamespaceConstraint::Specific(ns) => self
1202                .get_attr_enum(ns, local_name)
1203                .is_some_and(|value| value.eval_selector(operation)),
1204            NamespaceConstraint::Any => self
1205                .element
1206                .element
1207                .get_attr_vals_for_layout(local_name)
1208                .any(|v| v.eval_selector(operation)),
1209        }
1210    }
1211
1212    fn match_non_ts_pseudo_class(
1213        &self,
1214        _: &NonTSPseudoClass,
1215        _: &mut MatchingContext<Self::Impl>,
1216    ) -> bool {
1217        // NB: This could maybe be implemented
1218        warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called");
1219        false
1220    }
1221
1222    fn match_pseudo_element(
1223        &self,
1224        pseudo: &PseudoElement,
1225        context: &mut MatchingContext<Self::Impl>,
1226    ) -> bool {
1227        self.element.match_pseudo_element(pseudo, context)
1228    }
1229
1230    fn is_link(&self) -> bool {
1231        warn!("ServoThreadSafeLayoutElement::is_link called");
1232        false
1233    }
1234
1235    fn has_id(&self, _id: &AtomIdent, _case_sensitivity: CaseSensitivity) -> bool {
1236        debug!("ServoThreadSafeLayoutElement::has_id called");
1237        false
1238    }
1239
1240    #[inline]
1241    fn is_part(&self, _name: &AtomIdent) -> bool {
1242        debug!("ServoThreadSafeLayoutElement::is_part called");
1243        false
1244    }
1245
1246    fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> {
1247        debug!("ServoThreadSafeLayoutElement::imported_part called");
1248        None
1249    }
1250
1251    fn has_class(&self, _name: &AtomIdent, _case_sensitivity: CaseSensitivity) -> bool {
1252        debug!("ServoThreadSafeLayoutElement::has_class called");
1253        false
1254    }
1255
1256    fn is_empty(&self) -> bool {
1257        warn!("ServoThreadSafeLayoutElement::is_empty called");
1258        false
1259    }
1260
1261    fn is_root(&self) -> bool {
1262        warn!("ServoThreadSafeLayoutElement::is_root called");
1263        false
1264    }
1265
1266    fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
1267        // Handle flags that apply to the element.
1268        let self_flags = flags.for_self();
1269        if !self_flags.is_empty() {
1270            self.element.element.insert_selector_flags(flags);
1271        }
1272
1273        // Handle flags that apply to the parent.
1274        let parent_flags = flags.for_parent();
1275        if !parent_flags.is_empty() {
1276            if let Some(p) = self.element.parent_element() {
1277                p.element.insert_selector_flags(flags);
1278            }
1279        }
1280    }
1281
1282    fn add_element_unique_hashes(&self, filter: &mut BloomFilter) -> bool {
1283        each_relevant_element_hash(self.element, |hash| {
1284            filter.insert_hash(hash & BLOOM_HASH_MASK)
1285        });
1286        true
1287    }
1288
1289    fn has_custom_state(&self, _name: &AtomIdent) -> bool {
1290        false
1291    }
1292}