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