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