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