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 8 use crate::dom::TElement; 9 use crate::element_state::ElementState; 10 use crate::selector_parser::{AttrValue, NonTSPseudoClass, PseudoElement, SelectorImpl}; 11 use crate::selector_parser::{Snapshot, SnapshotMap}; 12 use crate::values::AtomIdent; 13 use crate::{CaseSensitivityExt, LocalName, Namespace, WeakAtom}; 14 use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; 15 use selectors::matching::{ElementSelectorFlags, MatchingContext}; 16 use selectors::{Element, OpaqueElement}; 17 use std::cell::Cell; 18 use std::fmt; 19 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. 27 /// 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. 31 /// 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. state(&self) -> Option<ElementState>42 fn state(&self) -> Option<ElementState>; 43 44 /// If this snapshot contains attribute information. has_attrs(&self) -> bool45 fn has_attrs(&self) -> bool; 46 47 /// Gets the attribute information of the snapshot as a string. 48 /// 49 /// Only for debugging purposes. debug_list_attributes(&self) -> String50 fn debug_list_attributes(&self) -> String { 51 String::new() 52 } 53 54 /// The ID attribute per this snapshot. Should only be called if 55 /// `has_attrs()` returns true. id_attr(&self) -> Option<&WeakAtom>56 fn id_attr(&self) -> Option<&WeakAtom>; 57 58 /// Whether this snapshot contains the class `name`. Should only be called 59 /// if `has_attrs()` returns true. has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool60 fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool; 61 62 /// Whether this snapshot represents the part named `name`. Should only be 63 /// called if `has_attrs()` returns true. is_part(&self, name: &AtomIdent) -> bool64 fn is_part(&self, name: &AtomIdent) -> bool; 65 66 /// See Element::imported_part. imported_part(&self, name: &AtomIdent) -> Option<AtomIdent>67 fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent>; 68 69 /// A callback that should be called for each class of the snapshot. Should 70 /// only be called if `has_attrs()` returns true. each_class<F>(&self, _: F) where F: FnMut(&AtomIdent)71 fn each_class<F>(&self, _: F) 72 where 73 F: FnMut(&AtomIdent); 74 75 /// The `xml:lang=""` or `lang=""` attribute value per this snapshot. lang_attr(&self) -> Option<AttrValue>76 fn lang_attr(&self) -> Option<AttrValue>; 77 } 78 79 /// A simple wrapper over an element and a snapshot, that allows us to 80 /// selector-match against a past state of the element. 81 #[derive(Clone)] 82 pub struct ElementWrapper<'a, E> 83 where 84 E: TElement, 85 { 86 element: E, 87 cached_snapshot: Cell<Option<&'a Snapshot>>, 88 snapshot_map: &'a SnapshotMap, 89 } 90 91 impl<'a, E> ElementWrapper<'a, E> 92 where 93 E: TElement, 94 { 95 /// Trivially constructs an `ElementWrapper`. new(el: E, snapshot_map: &'a SnapshotMap) -> Self96 pub fn new(el: E, snapshot_map: &'a SnapshotMap) -> Self { 97 ElementWrapper { 98 element: el, 99 cached_snapshot: Cell::new(None), 100 snapshot_map: snapshot_map, 101 } 102 } 103 104 /// Gets the snapshot associated with this element, if any. snapshot(&self) -> Option<&'a Snapshot>105 pub fn snapshot(&self) -> Option<&'a Snapshot> { 106 if !self.element.has_snapshot() { 107 return None; 108 } 109 110 if let Some(s) = self.cached_snapshot.get() { 111 return Some(s); 112 } 113 114 let snapshot = self.snapshot_map.get(&self.element); 115 debug_assert!(snapshot.is_some(), "has_snapshot lied!"); 116 117 self.cached_snapshot.set(snapshot); 118 119 snapshot 120 } 121 122 /// Returns the states that have changed since the element was snapshotted. state_changes(&self) -> ElementState123 pub fn state_changes(&self) -> ElementState { 124 let snapshot = match self.snapshot() { 125 Some(s) => s, 126 None => return ElementState::empty(), 127 }; 128 129 match snapshot.state() { 130 Some(state) => state ^ self.element.state(), 131 None => ElementState::empty(), 132 } 133 } 134 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. get_lang(&self) -> Option<AttrValue>138 fn get_lang(&self) -> Option<AttrValue> { 139 let mut current = self.clone(); 140 loop { 141 let lang = match self.snapshot() { 142 Some(snapshot) if snapshot.has_attrs() => snapshot.lang_attr(), 143 _ => current.element.lang_attr(), 144 }; 145 if lang.is_some() { 146 return lang; 147 } 148 current = current.parent_element()?; 149 } 150 } 151 } 152 153 impl<'a, E> fmt::Debug for ElementWrapper<'a, E> 154 where 155 E: TElement, 156 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result157 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 158 // Ignore other fields for now, can change later if needed. 159 self.element.fmt(f) 160 } 161 } 162 163 impl<'a, E> Element for ElementWrapper<'a, E> 164 where 165 E: TElement, 166 { 167 type Impl = SelectorImpl; 168 match_non_ts_pseudo_class<F>( &self, pseudo_class: &NonTSPseudoClass, context: &mut MatchingContext<Self::Impl>, _setter: &mut F, ) -> bool where F: FnMut(&Self, ElementSelectorFlags),169 fn match_non_ts_pseudo_class<F>( 170 &self, 171 pseudo_class: &NonTSPseudoClass, 172 context: &mut MatchingContext<Self::Impl>, 173 _setter: &mut F, 174 ) -> bool 175 where 176 F: FnMut(&Self, ElementSelectorFlags), 177 { 178 // Some pseudo-classes need special handling to evaluate them against 179 // the snapshot. 180 match *pseudo_class { 181 // For :link and :visited, we don't actually want to test the 182 // element state directly. 183 // 184 // Instead, we use the `visited_handling` to determine if they 185 // match. 186 NonTSPseudoClass::Link => { 187 return self.is_link() && context.visited_handling().matches_unvisited(); 188 }, 189 NonTSPseudoClass::Visited => { 190 return self.is_link() && context.visited_handling().matches_visited(); 191 }, 192 193 #[cfg(feature = "gecko")] 194 NonTSPseudoClass::MozTableBorderNonzero => { 195 if let Some(snapshot) = self.snapshot() { 196 if snapshot.has_other_pseudo_class_state() { 197 return snapshot.mIsTableBorderNonzero(); 198 } 199 } 200 }, 201 202 #[cfg(feature = "gecko")] 203 NonTSPseudoClass::MozBrowserFrame => { 204 if let Some(snapshot) = self.snapshot() { 205 if snapshot.has_other_pseudo_class_state() { 206 return snapshot.mIsMozBrowserFrame(); 207 } 208 } 209 }, 210 211 #[cfg(feature = "gecko")] 212 NonTSPseudoClass::MozSelectListBox => { 213 if let Some(snapshot) = self.snapshot() { 214 if snapshot.has_other_pseudo_class_state() { 215 return snapshot.mIsSelectListBox(); 216 } 217 } 218 }, 219 220 // :lang() needs to match using the closest ancestor xml:lang="" or 221 // lang="" attribtue from snapshots. 222 NonTSPseudoClass::Lang(ref lang_arg) => { 223 return self 224 .element 225 .match_element_lang(Some(self.get_lang()), lang_arg); 226 }, 227 228 _ => {}, 229 } 230 231 let flag = pseudo_class.state_flag(); 232 if flag.is_empty() { 233 return self 234 .element 235 .match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}); 236 } 237 match self.snapshot().and_then(|s| s.state()) { 238 Some(snapshot_state) => snapshot_state.intersects(flag), 239 None => self 240 .element 241 .match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}), 242 } 243 } 244 match_pseudo_element( &self, pseudo_element: &PseudoElement, context: &mut MatchingContext<Self::Impl>, ) -> bool245 fn match_pseudo_element( 246 &self, 247 pseudo_element: &PseudoElement, 248 context: &mut MatchingContext<Self::Impl>, 249 ) -> bool { 250 self.element.match_pseudo_element(pseudo_element, context) 251 } 252 is_link(&self) -> bool253 fn is_link(&self) -> bool { 254 match self.snapshot().and_then(|s| s.state()) { 255 Some(state) => state.intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE), 256 None => self.element.is_link(), 257 } 258 } 259 opaque(&self) -> OpaqueElement260 fn opaque(&self) -> OpaqueElement { 261 self.element.opaque() 262 } 263 parent_element(&self) -> Option<Self>264 fn parent_element(&self) -> Option<Self> { 265 let parent = self.element.parent_element()?; 266 Some(Self::new(parent, self.snapshot_map)) 267 } 268 parent_node_is_shadow_root(&self) -> bool269 fn parent_node_is_shadow_root(&self) -> bool { 270 self.element.parent_node_is_shadow_root() 271 } 272 containing_shadow_host(&self) -> Option<Self>273 fn containing_shadow_host(&self) -> Option<Self> { 274 let host = self.element.containing_shadow_host()?; 275 Some(Self::new(host, self.snapshot_map)) 276 } 277 prev_sibling_element(&self) -> Option<Self>278 fn prev_sibling_element(&self) -> Option<Self> { 279 let sibling = self.element.prev_sibling_element()?; 280 Some(Self::new(sibling, self.snapshot_map)) 281 } 282 next_sibling_element(&self) -> Option<Self>283 fn next_sibling_element(&self) -> Option<Self> { 284 let sibling = self.element.next_sibling_element()?; 285 Some(Self::new(sibling, self.snapshot_map)) 286 } 287 288 #[inline] is_html_element_in_html_document(&self) -> bool289 fn is_html_element_in_html_document(&self) -> bool { 290 self.element.is_html_element_in_html_document() 291 } 292 293 #[inline] is_html_slot_element(&self) -> bool294 fn is_html_slot_element(&self) -> bool { 295 self.element.is_html_slot_element() 296 } 297 298 #[inline] has_local_name( &self, local_name: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedLocalName, ) -> bool299 fn has_local_name( 300 &self, 301 local_name: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedLocalName, 302 ) -> bool { 303 self.element.has_local_name(local_name) 304 } 305 306 #[inline] has_namespace( &self, ns: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedNamespaceUrl, ) -> bool307 fn has_namespace( 308 &self, 309 ns: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedNamespaceUrl, 310 ) -> bool { 311 self.element.has_namespace(ns) 312 } 313 314 #[inline] is_same_type(&self, other: &Self) -> bool315 fn is_same_type(&self, other: &Self) -> bool { 316 self.element.is_same_type(&other.element) 317 } 318 attr_matches( &self, ns: &NamespaceConstraint<&Namespace>, local_name: &LocalName, operation: &AttrSelectorOperation<&AttrValue>, ) -> bool319 fn attr_matches( 320 &self, 321 ns: &NamespaceConstraint<&Namespace>, 322 local_name: &LocalName, 323 operation: &AttrSelectorOperation<&AttrValue>, 324 ) -> bool { 325 match self.snapshot() { 326 Some(snapshot) if snapshot.has_attrs() => { 327 snapshot.attr_matches(ns, local_name, operation) 328 }, 329 _ => self.element.attr_matches(ns, local_name, operation), 330 } 331 } 332 has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool333 fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { 334 match self.snapshot() { 335 Some(snapshot) if snapshot.has_attrs() => snapshot 336 .id_attr() 337 .map_or(false, |atom| case_sensitivity.eq_atom(&atom, id)), 338 _ => self.element.has_id(id, case_sensitivity), 339 } 340 } 341 is_part(&self, name: &AtomIdent) -> bool342 fn is_part(&self, name: &AtomIdent) -> bool { 343 match self.snapshot() { 344 Some(snapshot) if snapshot.has_attrs() => snapshot.is_part(name), 345 _ => self.element.is_part(name), 346 } 347 } 348 imported_part(&self, name: &AtomIdent) -> Option<AtomIdent>349 fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> { 350 match self.snapshot() { 351 Some(snapshot) if snapshot.has_attrs() => snapshot.imported_part(name), 352 _ => self.element.imported_part(name), 353 } 354 } 355 has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool356 fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { 357 match self.snapshot() { 358 Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity), 359 _ => self.element.has_class(name, case_sensitivity), 360 } 361 } 362 is_empty(&self) -> bool363 fn is_empty(&self) -> bool { 364 self.element.is_empty() 365 } 366 is_root(&self) -> bool367 fn is_root(&self) -> bool { 368 self.element.is_root() 369 } 370 is_pseudo_element(&self) -> bool371 fn is_pseudo_element(&self) -> bool { 372 self.element.is_pseudo_element() 373 } 374 pseudo_element_originating_element(&self) -> Option<Self>375 fn pseudo_element_originating_element(&self) -> Option<Self> { 376 self.element 377 .pseudo_element_originating_element() 378 .map(|e| ElementWrapper::new(e, self.snapshot_map)) 379 } 380 assigned_slot(&self) -> Option<Self>381 fn assigned_slot(&self) -> Option<Self> { 382 self.element 383 .assigned_slot() 384 .map(|e| ElementWrapper::new(e, self.snapshot_map)) 385 } 386 } 387