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/. */
5 //! A wrapper over an element and a snapshot, that allows us to selector-match
6 //! against a past state of the element.
8 use crate::dom::TElement;
9 use crate::selector_parser::{AttrValue, NonTSPseudoClass, PseudoElement, SelectorImpl};
10 use crate::selector_parser::{Snapshot, SnapshotMap};
11 use crate::values::AtomIdent;
12 use crate::{CaseSensitivityExt, LocalName, Namespace, WeakAtom};
13 use dom::ElementState;
14 use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
15 use selectors::matching::{ElementSelectorFlags, MatchingContext};
16 use selectors::{Element, OpaqueElement};
20 /// In order to compute restyle hints, we perform a selector match against a
21 /// list of partial selectors whose rightmost simple selector may be sensitive
22 /// to the thing being changed. We do this matching twice, once for the element
23 /// as it exists now and once for the element as it existed at the time of the
24 /// last restyle. If the results of the selector match differ, that means that
25 /// the given partial selector is sensitive to the change, and we compute a
26 /// restyle hint based on its combinator.
28 /// In order to run selector matching against the old element state, we generate
29 /// a wrapper for the element which claims to have the old state. This is the
30 /// ElementWrapper logic below.
32 /// Gecko does this differently for element states, and passes a mask called
33 /// mStateMask, which indicates the states that need to be ignored during
34 /// selector matching. This saves an ElementWrapper allocation and an additional
35 /// selector match call at the expense of additional complexity inside the
36 /// selector matching logic. This only works for boolean states though, so we
37 /// still need to take the ElementWrapper approach for attribute-dependent
38 /// style. So we do it the same both ways for now to reduce complexity, but it's
39 /// worth measuring the performance impact (if any) of the mStateMask approach.
40 pub trait ElementSnapshot: Sized {
41 /// The state of the snapshot, if any.
42 fn state(&self) -> Option<ElementState>;
44 /// If this snapshot contains attribute information.
45 fn has_attrs(&self) -> bool;
47 /// Gets the attribute information of the snapshot as a string.
49 /// Only for debugging purposes.
50 fn debug_list_attributes(&self) -> String {
54 /// The ID attribute per this snapshot. Should only be called if
55 /// `has_attrs()` returns true.
56 fn id_attr(&self) -> Option<&WeakAtom>;
58 /// Whether this snapshot contains the class `name`. Should only be called
59 /// if `has_attrs()` returns true.
60 fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool;
62 /// Whether this snapshot represents the part named `name`. Should only be
63 /// called if `has_attrs()` returns true.
64 fn is_part(&self, name: &AtomIdent) -> bool;
66 /// See Element::imported_part.
67 fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent>;
69 /// A callback that should be called for each class of the snapshot. Should
70 /// only be called if `has_attrs()` returns true.
71 fn each_class<F>(&self, _: F)
75 /// The `xml:lang=""` or `lang=""` attribute value per this snapshot.
76 fn lang_attr(&self) -> Option<AttrValue>;
79 /// A simple wrapper over an element and a snapshot, that allows us to
80 /// selector-match against a past state of the element.
82 pub struct ElementWrapper<'a, E>
87 cached_snapshot: Cell<Option<&'a Snapshot>>,
88 snapshot_map: &'a SnapshotMap,
91 impl<'a, E> ElementWrapper<'a, E>
95 /// Trivially constructs an `ElementWrapper`.
96 pub fn new(el: E, snapshot_map: &'a SnapshotMap) -> Self {
99 cached_snapshot: Cell::new(None),
100 snapshot_map: snapshot_map,
104 /// Gets the snapshot associated with this element, if any.
105 pub fn snapshot(&self) -> Option<&'a Snapshot> {
106 if !self.element.has_snapshot() {
110 if let Some(s) = self.cached_snapshot.get() {
114 let snapshot = self.snapshot_map.get(&self.element);
115 debug_assert!(snapshot.is_some(), "has_snapshot lied!");
117 self.cached_snapshot.set(snapshot);
122 /// Returns the states that have changed since the element was snapshotted.
123 pub fn state_changes(&self) -> ElementState {
124 let snapshot = match self.snapshot() {
126 None => return ElementState::empty(),
129 match snapshot.state() {
130 Some(state) => state ^ self.element.state(),
131 None => ElementState::empty(),
135 /// Returns the value of the `xml:lang=""` (or, if appropriate, `lang=""`)
136 /// attribute from this element's snapshot or the closest ancestor
137 /// element snapshot with the attribute specified.
138 fn get_lang(&self) -> Option<AttrValue> {
139 let mut current = self.clone();
141 let lang = match self.snapshot() {
142 Some(snapshot) if snapshot.has_attrs() => snapshot.lang_attr(),
143 _ => current.element.lang_attr(),
148 current = current.parent_element()?;
153 impl<'a, E> fmt::Debug for ElementWrapper<'a, E>
157 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158 // Ignore other fields for now, can change later if needed.
163 impl<'a, E> Element for ElementWrapper<'a, E>
167 type Impl = SelectorImpl;
169 fn match_non_ts_pseudo_class(
171 pseudo_class: &NonTSPseudoClass,
172 context: &mut MatchingContext<Self::Impl>,
174 // Some pseudo-classes need special handling to evaluate them against
176 match *pseudo_class {
177 // For :link and :visited, we don't actually want to test the
178 // element state directly.
180 // Instead, we use the `visited_handling` to determine if they
182 NonTSPseudoClass::Link => {
183 return self.is_link() && context.visited_handling().matches_unvisited();
185 NonTSPseudoClass::Visited => {
186 return self.is_link() && context.visited_handling().matches_visited();
189 #[cfg(feature = "gecko")]
190 NonTSPseudoClass::MozTableBorderNonzero => {
191 if let Some(snapshot) = self.snapshot() {
192 if snapshot.has_other_pseudo_class_state() {
193 return snapshot.mIsTableBorderNonzero();
198 #[cfg(feature = "gecko")]
199 NonTSPseudoClass::MozBrowserFrame => {
200 if let Some(snapshot) = self.snapshot() {
201 if snapshot.has_other_pseudo_class_state() {
202 return snapshot.mIsMozBrowserFrame();
207 #[cfg(feature = "gecko")]
208 NonTSPseudoClass::MozSelectListBox => {
209 if let Some(snapshot) = self.snapshot() {
210 if snapshot.has_other_pseudo_class_state() {
211 return snapshot.mIsSelectListBox();
216 // :lang() needs to match using the closest ancestor xml:lang="" or
217 // lang="" attribtue from snapshots.
218 NonTSPseudoClass::Lang(ref lang_arg) => {
221 .match_element_lang(Some(self.get_lang()), lang_arg);
227 let flag = pseudo_class.state_flag();
231 .match_non_ts_pseudo_class(pseudo_class, context);
233 match self.snapshot().and_then(|s| s.state()) {
234 Some(snapshot_state) => snapshot_state.intersects(flag),
237 .match_non_ts_pseudo_class(pseudo_class, context),
241 fn apply_selector_flags(&self, _flags: ElementSelectorFlags) {
242 debug_assert!(false, "Shouldn't need selector flags for invalidation");
245 fn match_pseudo_element(
247 pseudo_element: &PseudoElement,
248 context: &mut MatchingContext<Self::Impl>,
250 self.element.match_pseudo_element(pseudo_element, context)
253 fn is_link(&self) -> bool {
254 match self.snapshot().and_then(|s| s.state()) {
255 Some(state) => state.intersects(ElementState::VISITED_OR_UNVISITED),
256 None => self.element.is_link(),
260 fn opaque(&self) -> OpaqueElement {
261 self.element.opaque()
264 fn parent_element(&self) -> Option<Self> {
265 let parent = self.element.parent_element()?;
266 Some(Self::new(parent, self.snapshot_map))
269 fn parent_node_is_shadow_root(&self) -> bool {
270 self.element.parent_node_is_shadow_root()
273 fn containing_shadow_host(&self) -> Option<Self> {
274 let host = self.element.containing_shadow_host()?;
275 Some(Self::new(host, self.snapshot_map))
278 fn prev_sibling_element(&self) -> Option<Self> {
279 let sibling = self.element.prev_sibling_element()?;
280 Some(Self::new(sibling, self.snapshot_map))
283 fn next_sibling_element(&self) -> Option<Self> {
284 let sibling = self.element.next_sibling_element()?;
285 Some(Self::new(sibling, self.snapshot_map))
288 fn first_element_child(&self) -> Option<Self> {
289 let child = self.element.first_element_child()?;
290 Some(Self::new(child, self.snapshot_map))
294 fn is_html_element_in_html_document(&self) -> bool {
295 self.element.is_html_element_in_html_document()
299 fn is_html_slot_element(&self) -> bool {
300 self.element.is_html_slot_element()
306 local_name: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedLocalName,
308 self.element.has_local_name(local_name)
314 ns: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedNamespaceUrl,
316 self.element.has_namespace(ns)
320 fn is_same_type(&self, other: &Self) -> bool {
321 self.element.is_same_type(&other.element)
326 ns: &NamespaceConstraint<&Namespace>,
327 local_name: &LocalName,
328 operation: &AttrSelectorOperation<&AttrValue>,
330 match self.snapshot() {
331 Some(snapshot) if snapshot.has_attrs() => {
332 snapshot.attr_matches(ns, local_name, operation)
334 _ => self.element.attr_matches(ns, local_name, operation),
338 fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
339 match self.snapshot() {
340 Some(snapshot) if snapshot.has_attrs() => snapshot
342 .map_or(false, |atom| case_sensitivity.eq_atom(&atom, id)),
343 _ => self.element.has_id(id, case_sensitivity),
347 fn is_part(&self, name: &AtomIdent) -> bool {
348 match self.snapshot() {
349 Some(snapshot) if snapshot.has_attrs() => snapshot.is_part(name),
350 _ => self.element.is_part(name),
354 fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> {
355 match self.snapshot() {
356 Some(snapshot) if snapshot.has_attrs() => snapshot.imported_part(name),
357 _ => self.element.imported_part(name),
361 fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
362 match self.snapshot() {
363 Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity),
364 _ => self.element.has_class(name, case_sensitivity),
368 fn is_empty(&self) -> bool {
369 self.element.is_empty()
372 fn is_root(&self) -> bool {
373 self.element.is_root()
376 fn is_pseudo_element(&self) -> bool {
377 self.element.is_pseudo_element()
380 fn pseudo_element_originating_element(&self) -> Option<Self> {
382 .pseudo_element_originating_element()
383 .map(|e| ElementWrapper::new(e, self.snapshot_map))
386 fn assigned_slot(&self) -> Option<Self> {
389 .map(|e| ElementWrapper::new(e, self.snapshot_map))