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 // :dir is implemented in terms of state flags, but which state flag 182 // it maps to depends on the argument to :dir. That means we can't 183 // just add its state flags to the NonTSPseudoClass, because if we 184 // added all of them there, and tested via intersects() here, we'd 185 // get incorrect behavior for :not(:dir()) cases. 186 // 187 // FIXME(bz): How can I set this up so once Servo adds :dir() 188 // support we don't forget to update this code? 189 #[cfg(feature = "gecko")] 190 NonTSPseudoClass::Dir(ref dir) => { 191 let selector_flag = dir.element_state(); 192 if selector_flag.is_empty() { 193 // :dir() with some random argument; does not match. 194 return false; 195 } 196 let state = match self.snapshot().and_then(|s| s.state()) { 197 Some(snapshot_state) => snapshot_state, 198 None => self.element.state(), 199 }; 200 return state.contains(selector_flag); 201 }, 202 203 // For :link and :visited, we don't actually want to test the 204 // element state directly. 205 // 206 // Instead, we use the `visited_handling` to determine if they 207 // match. 208 NonTSPseudoClass::Link => { 209 return self.is_link() && context.visited_handling().matches_unvisited(); 210 }, 211 NonTSPseudoClass::Visited => { 212 return self.is_link() && context.visited_handling().matches_visited(); 213 }, 214 215 #[cfg(feature = "gecko")] 216 NonTSPseudoClass::MozTableBorderNonzero => { 217 if let Some(snapshot) = self.snapshot() { 218 if snapshot.has_other_pseudo_class_state() { 219 return snapshot.mIsTableBorderNonzero(); 220 } 221 } 222 }, 223 224 #[cfg(feature = "gecko")] 225 NonTSPseudoClass::MozBrowserFrame => { 226 if let Some(snapshot) = self.snapshot() { 227 if snapshot.has_other_pseudo_class_state() { 228 return snapshot.mIsMozBrowserFrame(); 229 } 230 } 231 }, 232 233 #[cfg(feature = "gecko")] 234 NonTSPseudoClass::MozSelectListBox => { 235 if let Some(snapshot) = self.snapshot() { 236 if snapshot.has_other_pseudo_class_state() { 237 return snapshot.mIsSelectListBox(); 238 } 239 } 240 }, 241 242 // :lang() needs to match using the closest ancestor xml:lang="" or 243 // lang="" attribtue from snapshots. 244 NonTSPseudoClass::Lang(ref lang_arg) => { 245 return self 246 .element 247 .match_element_lang(Some(self.get_lang()), lang_arg); 248 }, 249 250 _ => {}, 251 } 252 253 let flag = pseudo_class.state_flag(); 254 if flag.is_empty() { 255 return self 256 .element 257 .match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}); 258 } 259 match self.snapshot().and_then(|s| s.state()) { 260 Some(snapshot_state) => snapshot_state.intersects(flag), 261 None => self 262 .element 263 .match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}), 264 } 265 } 266 match_pseudo_element( &self, pseudo_element: &PseudoElement, context: &mut MatchingContext<Self::Impl>, ) -> bool267 fn match_pseudo_element( 268 &self, 269 pseudo_element: &PseudoElement, 270 context: &mut MatchingContext<Self::Impl>, 271 ) -> bool { 272 self.element.match_pseudo_element(pseudo_element, context) 273 } 274 is_link(&self) -> bool275 fn is_link(&self) -> bool { 276 match self.snapshot().and_then(|s| s.state()) { 277 Some(state) => state.intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE), 278 None => self.element.is_link(), 279 } 280 } 281 opaque(&self) -> OpaqueElement282 fn opaque(&self) -> OpaqueElement { 283 self.element.opaque() 284 } 285 parent_element(&self) -> Option<Self>286 fn parent_element(&self) -> Option<Self> { 287 let parent = self.element.parent_element()?; 288 Some(Self::new(parent, self.snapshot_map)) 289 } 290 parent_node_is_shadow_root(&self) -> bool291 fn parent_node_is_shadow_root(&self) -> bool { 292 self.element.parent_node_is_shadow_root() 293 } 294 containing_shadow_host(&self) -> Option<Self>295 fn containing_shadow_host(&self) -> Option<Self> { 296 let host = self.element.containing_shadow_host()?; 297 Some(Self::new(host, self.snapshot_map)) 298 } 299 prev_sibling_element(&self) -> Option<Self>300 fn prev_sibling_element(&self) -> Option<Self> { 301 let sibling = self.element.prev_sibling_element()?; 302 Some(Self::new(sibling, self.snapshot_map)) 303 } 304 next_sibling_element(&self) -> Option<Self>305 fn next_sibling_element(&self) -> Option<Self> { 306 let sibling = self.element.next_sibling_element()?; 307 Some(Self::new(sibling, self.snapshot_map)) 308 } 309 310 #[inline] is_html_element_in_html_document(&self) -> bool311 fn is_html_element_in_html_document(&self) -> bool { 312 self.element.is_html_element_in_html_document() 313 } 314 315 #[inline] is_html_slot_element(&self) -> bool316 fn is_html_slot_element(&self) -> bool { 317 self.element.is_html_slot_element() 318 } 319 320 #[inline] has_local_name( &self, local_name: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedLocalName, ) -> bool321 fn has_local_name( 322 &self, 323 local_name: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedLocalName, 324 ) -> bool { 325 self.element.has_local_name(local_name) 326 } 327 328 #[inline] has_namespace( &self, ns: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedNamespaceUrl, ) -> bool329 fn has_namespace( 330 &self, 331 ns: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedNamespaceUrl, 332 ) -> bool { 333 self.element.has_namespace(ns) 334 } 335 336 #[inline] is_same_type(&self, other: &Self) -> bool337 fn is_same_type(&self, other: &Self) -> bool { 338 self.element.is_same_type(&other.element) 339 } 340 attr_matches( &self, ns: &NamespaceConstraint<&Namespace>, local_name: &LocalName, operation: &AttrSelectorOperation<&AttrValue>, ) -> bool341 fn attr_matches( 342 &self, 343 ns: &NamespaceConstraint<&Namespace>, 344 local_name: &LocalName, 345 operation: &AttrSelectorOperation<&AttrValue>, 346 ) -> bool { 347 match self.snapshot() { 348 Some(snapshot) if snapshot.has_attrs() => { 349 snapshot.attr_matches(ns, local_name, operation) 350 }, 351 _ => self.element.attr_matches(ns, local_name, operation), 352 } 353 } 354 has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool355 fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { 356 match self.snapshot() { 357 Some(snapshot) if snapshot.has_attrs() => snapshot 358 .id_attr() 359 .map_or(false, |atom| case_sensitivity.eq_atom(&atom, id)), 360 _ => self.element.has_id(id, case_sensitivity), 361 } 362 } 363 is_part(&self, name: &AtomIdent) -> bool364 fn is_part(&self, name: &AtomIdent) -> bool { 365 match self.snapshot() { 366 Some(snapshot) if snapshot.has_attrs() => snapshot.is_part(name), 367 _ => self.element.is_part(name), 368 } 369 } 370 imported_part(&self, name: &AtomIdent) -> Option<AtomIdent>371 fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> { 372 match self.snapshot() { 373 Some(snapshot) if snapshot.has_attrs() => snapshot.imported_part(name), 374 _ => self.element.imported_part(name), 375 } 376 } 377 has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool378 fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { 379 match self.snapshot() { 380 Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity), 381 _ => self.element.has_class(name, case_sensitivity), 382 } 383 } 384 is_empty(&self) -> bool385 fn is_empty(&self) -> bool { 386 self.element.is_empty() 387 } 388 is_root(&self) -> bool389 fn is_root(&self) -> bool { 390 self.element.is_root() 391 } 392 is_pseudo_element(&self) -> bool393 fn is_pseudo_element(&self) -> bool { 394 self.element.is_pseudo_element() 395 } 396 pseudo_element_originating_element(&self) -> Option<Self>397 fn pseudo_element_originating_element(&self) -> Option<Self> { 398 self.element 399 .pseudo_element_originating_element() 400 .map(|e| ElementWrapper::new(e, self.snapshot_map)) 401 } 402 assigned_slot(&self) -> Option<Self>403 fn assigned_slot(&self) -> Option<Self> { 404 self.element 405 .assigned_slot() 406 .map(|e| ElementWrapper::new(e, self.snapshot_map)) 407 } 408 } 409