style/invalidation/element/
element_wrapper.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
5//! A wrapper over an element and a snapshot, that allows us to selector-match
6//! against a past state of the element.
7
8use crate::dom::TElement;
9use crate::selector_parser::{AttrValue, NonTSPseudoClass, PseudoElement, SelectorImpl};
10use crate::selector_parser::{Snapshot, SnapshotMap};
11use crate::values::AtomIdent;
12use crate::{CaseSensitivityExt, LocalName, Namespace, WeakAtom};
13use dom::ElementState;
14use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
15use selectors::bloom::BloomFilter;
16use selectors::matching::{ElementSelectorFlags, MatchingContext};
17use selectors::{Element, OpaqueElement};
18use std::cell::Cell;
19use std::fmt;
20
21/// In order to compute restyle hints, we perform a selector match against a
22/// list of partial selectors whose rightmost simple selector may be sensitive
23/// to the thing being changed. We do this matching twice, once for the element
24/// as it exists now and once for the element as it existed at the time of the
25/// last restyle. If the results of the selector match differ, that means that
26/// the given partial selector is sensitive to the change, and we compute a
27/// restyle hint based on its combinator.
28///
29/// In order to run selector matching against the old element state, we generate
30/// a wrapper for the element which claims to have the old state. This is the
31/// ElementWrapper logic below.
32///
33/// Gecko does this differently for element states, and passes a mask called
34/// mStateMask, which indicates the states that need to be ignored during
35/// selector matching. This saves an ElementWrapper allocation and an additional
36/// selector match call at the expense of additional complexity inside the
37/// selector matching logic. This only works for boolean states though, so we
38/// still need to take the ElementWrapper approach for attribute-dependent
39/// style. So we do it the same both ways for now to reduce complexity, but it's
40/// worth measuring the performance impact (if any) of the mStateMask approach.
41pub trait ElementSnapshot: Sized {
42    /// The state of the snapshot, if any.
43    fn state(&self) -> Option<ElementState>;
44
45    /// If this snapshot contains attribute information.
46    fn has_attrs(&self) -> bool;
47
48    /// Gets the attribute information of the snapshot as a string.
49    ///
50    /// Only for debugging purposes.
51    fn debug_list_attributes(&self) -> String {
52        String::new()
53    }
54
55    /// The ID attribute per this snapshot. Should only be called if
56    /// `has_attrs()` returns true.
57    fn id_attr(&self) -> Option<&WeakAtom>;
58
59    /// Whether this snapshot contains the class `name`. Should only be called
60    /// if `has_attrs()` returns true.
61    fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool;
62
63    /// Whether this snapshot represents the part named `name`. Should only be
64    /// called if `has_attrs()` returns true.
65    fn is_part(&self, name: &AtomIdent) -> bool;
66
67    /// See Element::imported_part.
68    fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent>;
69
70    /// A callback that should be called for each class of the snapshot. Should
71    /// only be called if `has_attrs()` returns true.
72    fn each_class<F>(&self, _: F)
73    where
74        F: FnMut(&AtomIdent);
75
76    /// If this snapshot contains CustomStateSet information.
77    fn has_custom_states(&self) -> bool;
78
79    /// A callback that should be called for each CustomState of the snapshot.
80    fn has_custom_state(&self, state: &AtomIdent) -> bool;
81
82    /// A callback that should be called for each CustomState of the snapshot.
83    fn each_custom_state<F>(&self, callback: F)
84    where
85        F: FnMut(&AtomIdent);
86
87    /// The `xml:lang=""` or `lang=""` attribute value per this snapshot.
88    fn lang_attr(&self) -> Option<AttrValue>;
89}
90
91/// A simple wrapper over an element and a snapshot, that allows us to
92/// selector-match against a past state of the element.
93#[derive(Clone)]
94pub struct ElementWrapper<'a, E>
95where
96    E: TElement,
97{
98    element: E,
99    cached_snapshot: Cell<Option<&'a Snapshot>>,
100    snapshot_map: &'a SnapshotMap,
101}
102
103impl<'a, E> ElementWrapper<'a, E>
104where
105    E: TElement,
106{
107    /// Trivially constructs an `ElementWrapper`.
108    pub fn new(el: E, snapshot_map: &'a SnapshotMap) -> Self {
109        ElementWrapper {
110            element: el,
111            cached_snapshot: Cell::new(None),
112            snapshot_map: snapshot_map,
113        }
114    }
115
116    /// Gets the snapshot associated with this element, if any.
117    pub fn snapshot(&self) -> Option<&'a Snapshot> {
118        if !self.element.has_snapshot() {
119            return None;
120        }
121
122        if let Some(s) = self.cached_snapshot.get() {
123            return Some(s);
124        }
125
126        let snapshot = self.snapshot_map.get(&self.element);
127        debug_assert!(snapshot.is_some(), "has_snapshot lied!");
128
129        self.cached_snapshot.set(snapshot);
130
131        snapshot
132    }
133
134    /// Returns the states that have changed since the element was snapshotted.
135    pub fn state_changes(&self) -> ElementState {
136        let snapshot = match self.snapshot() {
137            Some(s) => s,
138            None => return ElementState::empty(),
139        };
140
141        match snapshot.state() {
142            Some(state) => state ^ self.element.state(),
143            None => ElementState::empty(),
144        }
145    }
146
147    /// Returns the value of the `xml:lang=""` (or, if appropriate, `lang=""`)
148    /// attribute from this element's snapshot or the closest ancestor
149    /// element snapshot with the attribute specified.
150    fn get_lang(&self) -> Option<AttrValue> {
151        let mut current = self.clone();
152        loop {
153            let lang = match self.snapshot() {
154                Some(snapshot) if snapshot.has_attrs() => snapshot.lang_attr(),
155                _ => current.element.lang_attr(),
156            };
157            if lang.is_some() {
158                return lang;
159            }
160            current = current.parent_element()?;
161        }
162    }
163}
164
165impl<'a, E> fmt::Debug for ElementWrapper<'a, E>
166where
167    E: TElement,
168{
169    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170        // Ignore other fields for now, can change later if needed.
171        self.element.fmt(f)
172    }
173}
174
175impl<'a, E> Element for ElementWrapper<'a, E>
176where
177    E: TElement,
178{
179    type Impl = SelectorImpl;
180
181    fn match_non_ts_pseudo_class(
182        &self,
183        pseudo_class: &NonTSPseudoClass,
184        context: &mut MatchingContext<Self::Impl>,
185    ) -> bool {
186        // Some pseudo-classes need special handling to evaluate them against
187        // the snapshot.
188        match *pseudo_class {
189            // For :link and :visited, we don't actually want to test the
190            // element state directly.
191            //
192            // Instead, we use the `visited_handling` to determine if they
193            // match.
194            NonTSPseudoClass::Link => {
195                return self.is_link() && context.visited_handling().matches_unvisited();
196            },
197            NonTSPseudoClass::Visited => {
198                return self.is_link() && context.visited_handling().matches_visited();
199            },
200
201            #[cfg(feature = "gecko")]
202            NonTSPseudoClass::MozTableBorderNonzero => {
203                if let Some(snapshot) = self.snapshot() {
204                    if snapshot.has_other_pseudo_class_state() {
205                        return snapshot.mIsTableBorderNonzero();
206                    }
207                }
208            },
209
210            #[cfg(feature = "gecko")]
211            NonTSPseudoClass::MozSelectListBox => {
212                if let Some(snapshot) = self.snapshot() {
213                    if snapshot.has_other_pseudo_class_state() {
214                        return snapshot.mIsSelectListBox();
215                    }
216                }
217            },
218
219            // :lang() needs to match using the closest ancestor xml:lang="" or
220            // lang="" attribtue from snapshots.
221            NonTSPseudoClass::Lang(ref lang_arg) => {
222                return self
223                    .element
224                    .match_element_lang(Some(self.get_lang()), lang_arg);
225            },
226
227            // :heading should match against snapshot before element
228            #[cfg(feature = "gecko")]
229            NonTSPseudoClass::Heading(ref levels) => {
230                return levels.matches_state(
231                    self.snapshot()
232                        .and_then(|s| s.state())
233                        .unwrap_or_else(|| self.element.state()),
234                );
235            },
236
237            // CustomStateSet should match against the snapshot before element
238            NonTSPseudoClass::CustomState(ref state) => return self.has_custom_state(&state.0),
239
240            _ => {},
241        }
242
243        let flag = pseudo_class.state_flag();
244        if flag.is_empty() {
245            return self
246                .element
247                .match_non_ts_pseudo_class(pseudo_class, context);
248        }
249        match self.snapshot().and_then(|s| s.state()) {
250            Some(snapshot_state) => snapshot_state.intersects(flag),
251            None => self
252                .element
253                .match_non_ts_pseudo_class(pseudo_class, context),
254        }
255    }
256
257    fn apply_selector_flags(&self, _flags: ElementSelectorFlags) {
258        debug_assert!(false, "Shouldn't need selector flags for invalidation");
259    }
260
261    fn match_pseudo_element(
262        &self,
263        pseudo_element: &PseudoElement,
264        context: &mut MatchingContext<Self::Impl>,
265    ) -> bool {
266        self.element.match_pseudo_element(pseudo_element, context)
267    }
268
269    fn is_link(&self) -> bool {
270        match self.snapshot().and_then(|s| s.state()) {
271            Some(state) => state.intersects(ElementState::VISITED_OR_UNVISITED),
272            None => self.element.is_link(),
273        }
274    }
275
276    fn opaque(&self) -> OpaqueElement {
277        self.element.opaque()
278    }
279
280    fn parent_element(&self) -> Option<Self> {
281        let parent = self.element.parent_element()?;
282        Some(Self::new(parent, self.snapshot_map))
283    }
284
285    fn parent_node_is_shadow_root(&self) -> bool {
286        self.element.parent_node_is_shadow_root()
287    }
288
289    fn containing_shadow_host(&self) -> Option<Self> {
290        let host = self.element.containing_shadow_host()?;
291        Some(Self::new(host, self.snapshot_map))
292    }
293
294    fn prev_sibling_element(&self) -> Option<Self> {
295        let sibling = self.element.prev_sibling_element()?;
296        Some(Self::new(sibling, self.snapshot_map))
297    }
298
299    fn next_sibling_element(&self) -> Option<Self> {
300        let sibling = self.element.next_sibling_element()?;
301        Some(Self::new(sibling, self.snapshot_map))
302    }
303
304    fn first_element_child(&self) -> Option<Self> {
305        let child = self.element.first_element_child()?;
306        Some(Self::new(child, self.snapshot_map))
307    }
308
309    #[inline]
310    fn is_html_element_in_html_document(&self) -> bool {
311        self.element.is_html_element_in_html_document()
312    }
313
314    #[inline]
315    fn is_html_slot_element(&self) -> bool {
316        self.element.is_html_slot_element()
317    }
318
319    #[inline]
320    fn has_local_name(
321        &self,
322        local_name: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedLocalName,
323    ) -> bool {
324        self.element.has_local_name(local_name)
325    }
326
327    #[inline]
328    fn has_namespace(
329        &self,
330        ns: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedNamespaceUrl,
331    ) -> bool {
332        self.element.has_namespace(ns)
333    }
334
335    #[inline]
336    fn is_same_type(&self, other: &Self) -> bool {
337        self.element.is_same_type(&other.element)
338    }
339
340    fn attr_matches(
341        &self,
342        ns: &NamespaceConstraint<&Namespace>,
343        local_name: &LocalName,
344        operation: &AttrSelectorOperation<&AttrValue>,
345    ) -> bool {
346        match self.snapshot() {
347            Some(snapshot) if snapshot.has_attrs() => {
348                snapshot.attr_matches(ns, local_name, operation)
349            },
350            _ => self.element.attr_matches(ns, local_name, operation),
351        }
352    }
353
354    fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
355        match self.snapshot() {
356            Some(snapshot) if snapshot.has_attrs() => snapshot
357                .id_attr()
358                .map_or(false, |atom| case_sensitivity.eq_atom(&atom, id)),
359            _ => self.element.has_id(id, case_sensitivity),
360        }
361    }
362
363    fn is_part(&self, name: &AtomIdent) -> bool {
364        match self.snapshot() {
365            Some(snapshot) if snapshot.has_attrs() => snapshot.is_part(name),
366            _ => self.element.is_part(name),
367        }
368    }
369
370    fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> {
371        match self.snapshot() {
372            Some(snapshot) if snapshot.has_attrs() => snapshot.imported_part(name),
373            _ => self.element.imported_part(name),
374        }
375    }
376
377    fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
378        match self.snapshot() {
379            Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity),
380            _ => self.element.has_class(name, case_sensitivity),
381        }
382    }
383
384    fn has_custom_state(&self, state: &AtomIdent) -> bool {
385        match self.snapshot() {
386            Some(snapshot) if snapshot.has_custom_states() => snapshot.has_custom_state(state),
387            _ => self.element.has_custom_state(state),
388        }
389    }
390
391    fn is_empty(&self) -> bool {
392        self.element.is_empty()
393    }
394
395    fn is_root(&self) -> bool {
396        self.element.is_root()
397    }
398
399    fn is_pseudo_element(&self) -> bool {
400        self.element.is_pseudo_element()
401    }
402
403    fn pseudo_element_originating_element(&self) -> Option<Self> {
404        self.element
405            .pseudo_element_originating_element()
406            .map(|e| ElementWrapper::new(e, self.snapshot_map))
407    }
408
409    fn assigned_slot(&self) -> Option<Self> {
410        self.element
411            .assigned_slot()
412            .map(|e| ElementWrapper::new(e, self.snapshot_map))
413    }
414
415    fn add_element_unique_hashes(&self, _filter: &mut BloomFilter) -> bool {
416        // Should not be relevant in the context of checking past elements in invalidation.
417        false
418    }
419}