element_wrapper.rs (14390B)
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::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::bloom::BloomFilter; 16 use selectors::matching::{ElementSelectorFlags, MatchingContext}; 17 use selectors::{Element, OpaqueElement}; 18 use std::cell::Cell; 19 use 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. 41 pub 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)] 94 pub struct ElementWrapper<'a, E> 95 where 96 E: TElement, 97 { 98 element: E, 99 cached_snapshot: Cell<Option<&'a Snapshot>>, 100 snapshot_map: &'a SnapshotMap, 101 } 102 103 impl<'a, E> ElementWrapper<'a, E> 104 where 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 165 impl<'a, E> fmt::Debug for ElementWrapper<'a, E> 166 where 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 175 impl<'a, E> Element for ElementWrapper<'a, E> 176 where 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 }