style/invalidation/element/
element_wrapper.rs1use 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
21pub trait ElementSnapshot: Sized {
42 fn state(&self) -> Option<ElementState>;
44
45 fn has_attrs(&self) -> bool;
47
48 fn debug_list_attributes(&self) -> String {
52 String::new()
53 }
54
55 fn id_attr(&self) -> Option<&WeakAtom>;
58
59 fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool;
62
63 fn is_part(&self, name: &AtomIdent) -> bool;
66
67 fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent>;
69
70 fn each_class<F>(&self, _: F)
73 where
74 F: FnMut(&AtomIdent);
75
76 fn has_custom_states(&self) -> bool;
78
79 fn has_custom_state(&self, state: &AtomIdent) -> bool;
81
82 fn each_custom_state<F>(&self, callback: F)
84 where
85 F: FnMut(&AtomIdent);
86
87 fn lang_attr(&self) -> Option<AttrValue>;
89}
90
91#[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 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 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 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 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 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 match *pseudo_class {
189 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 NonTSPseudoClass::Lang(ref lang_arg) => {
222 return self
223 .element
224 .match_element_lang(Some(self.get_lang()), lang_arg);
225 },
226
227 #[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 NonTSPseudoClass::CustomState(ref state) => return self.has_custom_state(&state.0),
239
240 #[cfg(feature = "gecko")]
243 NonTSPseudoClass::Paused | NonTSPseudoClass::Playing => {
244 let state = self
245 .snapshot()
246 .and_then(|s| s.state())
247 .unwrap_or_else(|| self.element.state());
248 return self.element.is_html_media_element()
249 && (*pseudo_class == NonTSPseudoClass::Paused)
250 == state.intersects(ElementState::PAUSED);
251 },
252
253 _ => {},
254 }
255
256 let flag = pseudo_class.state_flag();
257 if flag.is_empty() {
258 return self
259 .element
260 .match_non_ts_pseudo_class(pseudo_class, context);
261 }
262 match self.snapshot().and_then(|s| s.state()) {
263 Some(snapshot_state) => snapshot_state.intersects(flag),
264 None => self
265 .element
266 .match_non_ts_pseudo_class(pseudo_class, context),
267 }
268 }
269
270 fn apply_selector_flags(&self, _flags: ElementSelectorFlags) {
271 debug_assert!(false, "Shouldn't need selector flags for invalidation");
272 }
273
274 fn match_pseudo_element(
275 &self,
276 pseudo_element: &PseudoElement,
277 context: &mut MatchingContext<Self::Impl>,
278 ) -> bool {
279 self.element.match_pseudo_element(pseudo_element, context)
280 }
281
282 fn is_link(&self) -> bool {
283 match self.snapshot().and_then(|s| s.state()) {
284 Some(state) => state.intersects(ElementState::VISITED_OR_UNVISITED),
285 None => self.element.is_link(),
286 }
287 }
288
289 fn opaque(&self) -> OpaqueElement {
290 self.element.opaque()
291 }
292
293 fn parent_element(&self) -> Option<Self> {
294 let parent = self.element.parent_element()?;
295 Some(Self::new(parent, self.snapshot_map))
296 }
297
298 fn parent_node_is_shadow_root(&self) -> bool {
299 self.element.parent_node_is_shadow_root()
300 }
301
302 fn containing_shadow_host(&self) -> Option<Self> {
303 let host = self.element.containing_shadow_host()?;
304 Some(Self::new(host, self.snapshot_map))
305 }
306
307 fn prev_sibling_element(&self) -> Option<Self> {
308 let sibling = self.element.prev_sibling_element()?;
309 Some(Self::new(sibling, self.snapshot_map))
310 }
311
312 fn next_sibling_element(&self) -> Option<Self> {
313 let sibling = self.element.next_sibling_element()?;
314 Some(Self::new(sibling, self.snapshot_map))
315 }
316
317 fn first_element_child(&self) -> Option<Self> {
318 let child = self.element.first_element_child()?;
319 Some(Self::new(child, self.snapshot_map))
320 }
321
322 #[inline]
323 fn is_html_element_in_html_document(&self) -> bool {
324 self.element.is_html_element_in_html_document()
325 }
326
327 #[inline]
328 fn is_html_slot_element(&self) -> bool {
329 self.element.is_html_slot_element()
330 }
331
332 #[inline]
333 fn has_local_name(
334 &self,
335 local_name: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedLocalName,
336 ) -> bool {
337 self.element.has_local_name(local_name)
338 }
339
340 #[inline]
341 fn has_namespace(
342 &self,
343 ns: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedNamespaceUrl,
344 ) -> bool {
345 self.element.has_namespace(ns)
346 }
347
348 #[inline]
349 fn is_same_type(&self, other: &Self) -> bool {
350 self.element.is_same_type(&other.element)
351 }
352
353 fn attr_matches(
354 &self,
355 ns: &NamespaceConstraint<&Namespace>,
356 local_name: &LocalName,
357 operation: &AttrSelectorOperation<&AttrValue>,
358 ) -> bool {
359 match self.snapshot() {
360 Some(snapshot) if snapshot.has_attrs() => {
361 snapshot.attr_matches(ns, local_name, operation)
362 },
363 _ => self.element.attr_matches(ns, local_name, operation),
364 }
365 }
366
367 fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
368 match self.snapshot() {
369 Some(snapshot) if snapshot.has_attrs() => snapshot
370 .id_attr()
371 .map_or(false, |atom| case_sensitivity.eq_atom(&atom, id)),
372 _ => self.element.has_id(id, case_sensitivity),
373 }
374 }
375
376 fn is_part(&self, name: &AtomIdent) -> bool {
377 match self.snapshot() {
378 Some(snapshot) if snapshot.has_attrs() => snapshot.is_part(name),
379 _ => self.element.is_part(name),
380 }
381 }
382
383 fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> {
384 match self.snapshot() {
385 Some(snapshot) if snapshot.has_attrs() => snapshot.imported_part(name),
386 _ => self.element.imported_part(name),
387 }
388 }
389
390 fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
391 match self.snapshot() {
392 Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity),
393 _ => self.element.has_class(name, case_sensitivity),
394 }
395 }
396
397 fn has_custom_state(&self, state: &AtomIdent) -> bool {
398 match self.snapshot() {
399 Some(snapshot) if snapshot.has_custom_states() => snapshot.has_custom_state(state),
400 _ => self.element.has_custom_state(state),
401 }
402 }
403
404 fn is_empty(&self) -> bool {
405 self.element.is_empty()
406 }
407
408 fn is_root(&self) -> bool {
409 self.element.is_root()
410 }
411
412 fn is_pseudo_element(&self) -> bool {
413 self.element.is_pseudo_element()
414 }
415
416 fn pseudo_element_originating_element(&self) -> Option<Self> {
417 self.element
418 .pseudo_element_originating_element()
419 .map(|e| ElementWrapper::new(e, self.snapshot_map))
420 }
421
422 fn assigned_slot(&self) -> Option<Self> {
423 self.element
424 .assigned_slot()
425 .map(|e| ElementWrapper::new(e, self.snapshot_map))
426 }
427
428 fn add_element_unique_hashes(&self, _filter: &mut BloomFilter) -> bool {
429 false
431 }
432}