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 #![deny(missing_docs)]
6 
7 //! Servo's selector parser.
8 
9 use crate::attr::{AttrIdentifier, AttrValue};
10 use crate::dom::{OpaqueNode, TElement, TNode};
11 use crate::element_state::{DocumentState, ElementState};
12 use crate::invalidation::element::document_state::InvalidationMatchingData;
13 use crate::invalidation::element::element_wrapper::ElementSnapshot;
14 use crate::properties::longhands::display::computed_value::T as Display;
15 use crate::properties::{ComputedValues, PropertyFlags};
16 use crate::selector_parser::AttrValue as SelectorAttrValue;
17 use crate::selector_parser::{PseudoElementCascadeType, SelectorParser};
18 use crate::values::{AtomIdent, AtomString};
19 use crate::{Atom, CaseSensitivityExt, LocalName, Namespace, Prefix};
20 use cssparser::{serialize_identifier, CowRcStr, Parser as CssParser, SourceLocation, ToCss};
21 use fxhash::FxHashMap;
22 use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
23 use selectors::parser::SelectorParseErrorKind;
24 use selectors::visitor::SelectorVisitor;
25 use std::fmt;
26 use std::mem;
27 use std::ops::{Deref, DerefMut};
28 use style_traits::{ParseError, StyleParseErrorKind};
29 
30 /// A pseudo-element, both public and private.
31 ///
32 /// NB: If you add to this list, be sure to update `each_simple_pseudo_element` too.
33 #[derive(
34     Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, ToShmem,
35 )]
36 #[allow(missing_docs)]
37 #[repr(usize)]
38 pub enum PseudoElement {
39     // Eager pseudos. Keep these first so that eager_index() works.
40     After = 0,
41     Before,
42     Selection,
43     // If/when :first-letter is added, update is_first_letter accordingly.
44 
45     // If/when :first-line is added, update is_first_line accordingly.
46 
47     // If/when ::first-letter, ::first-line, or ::placeholder are added, adjust
48     // our property_restriction implementation to do property filtering for
49     // them.  Also, make sure the UA sheet has the !important rules some of the
50     // APPLIES_TO_PLACEHOLDER properties expect!
51 
52     // Non-eager pseudos.
53     DetailsSummary,
54     DetailsContent,
55     ServoText,
56     ServoInputText,
57     ServoTableWrapper,
58     ServoAnonymousTableWrapper,
59     ServoAnonymousTable,
60     ServoAnonymousTableRow,
61     ServoAnonymousTableCell,
62     ServoAnonymousBlock,
63     ServoInlineBlockWrapper,
64     ServoInlineAbsolute,
65 }
66 
67 /// The count of all pseudo-elements.
68 pub const PSEUDO_COUNT: usize = PseudoElement::ServoInlineAbsolute as usize + 1;
69 
70 impl ::selectors::parser::PseudoElement for PseudoElement {
71     type Impl = SelectorImpl;
72 }
73 
74 impl ToCss for PseudoElement {
to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write,75     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
76     where
77         W: fmt::Write,
78     {
79         use self::PseudoElement::*;
80         dest.write_str(match *self {
81             After => "::after",
82             Before => "::before",
83             Selection => "::selection",
84             DetailsSummary => "::-servo-details-summary",
85             DetailsContent => "::-servo-details-content",
86             ServoText => "::-servo-text",
87             ServoInputText => "::-servo-input-text",
88             ServoTableWrapper => "::-servo-table-wrapper",
89             ServoAnonymousTableWrapper => "::-servo-anonymous-table-wrapper",
90             ServoAnonymousTable => "::-servo-anonymous-table",
91             ServoAnonymousTableRow => "::-servo-anonymous-table-row",
92             ServoAnonymousTableCell => "::-servo-anonymous-table-cell",
93             ServoAnonymousBlock => "::-servo-anonymous-block",
94             ServoInlineBlockWrapper => "::-servo-inline-block-wrapper",
95             ServoInlineAbsolute => "::-servo-inline-absolute",
96         })
97     }
98 }
99 
100 /// The number of eager pseudo-elements. Keep this in sync with cascade_type.
101 pub const EAGER_PSEUDO_COUNT: usize = 3;
102 
103 impl PseudoElement {
104     /// Gets the canonical index of this eagerly-cascaded pseudo-element.
105     #[inline]
eager_index(&self) -> usize106     pub fn eager_index(&self) -> usize {
107         debug_assert!(self.is_eager());
108         self.clone() as usize
109     }
110 
111     /// An index for this pseudo-element to be indexed in an enumerated array.
112     #[inline]
index(&self) -> usize113     pub fn index(&self) -> usize {
114         self.clone() as usize
115     }
116 
117     /// An array of `None`, one per pseudo-element.
pseudo_none_array<T>() -> [Option<T>; PSEUDO_COUNT]118     pub fn pseudo_none_array<T>() -> [Option<T>; PSEUDO_COUNT] {
119         Default::default()
120     }
121 
122     /// Creates a pseudo-element from an eager index.
123     #[inline]
from_eager_index(i: usize) -> Self124     pub fn from_eager_index(i: usize) -> Self {
125         assert!(i < EAGER_PSEUDO_COUNT);
126         let result: PseudoElement = unsafe { mem::transmute(i) };
127         debug_assert!(result.is_eager());
128         result
129     }
130 
131     /// Whether the current pseudo element is ::before or ::after.
132     #[inline]
is_before_or_after(&self) -> bool133     pub fn is_before_or_after(&self) -> bool {
134         self.is_before() || self.is_after()
135     }
136 
137     /// Whether this is an unknown ::-webkit- pseudo-element.
138     #[inline]
is_unknown_webkit_pseudo_element(&self) -> bool139     pub fn is_unknown_webkit_pseudo_element(&self) -> bool {
140         false
141     }
142 
143     /// Whether this pseudo-element is the ::marker pseudo.
144     #[inline]
is_marker(&self) -> bool145     pub fn is_marker(&self) -> bool {
146         false
147     }
148 
149     /// Whether this pseudo-element is the ::selection pseudo.
150     #[inline]
is_selection(&self) -> bool151     pub fn is_selection(&self) -> bool {
152         *self == PseudoElement::Selection
153     }
154 
155     /// Whether this pseudo-element is the ::before pseudo.
156     #[inline]
is_before(&self) -> bool157     pub fn is_before(&self) -> bool {
158         *self == PseudoElement::Before
159     }
160 
161     /// Whether this pseudo-element is the ::after pseudo.
162     #[inline]
is_after(&self) -> bool163     pub fn is_after(&self) -> bool {
164         *self == PseudoElement::After
165     }
166 
167     /// Whether the current pseudo element is :first-letter
168     #[inline]
is_first_letter(&self) -> bool169     pub fn is_first_letter(&self) -> bool {
170         false
171     }
172 
173     /// Whether the current pseudo element is :first-line
174     #[inline]
is_first_line(&self) -> bool175     pub fn is_first_line(&self) -> bool {
176         false
177     }
178 
179     /// Whether this pseudo-element is the ::-moz-color-swatch pseudo.
180     #[inline]
is_color_swatch(&self) -> bool181     pub fn is_color_swatch(&self) -> bool {
182         false
183     }
184 
185     /// Whether this pseudo-element is eagerly-cascaded.
186     #[inline]
is_eager(&self) -> bool187     pub fn is_eager(&self) -> bool {
188         self.cascade_type() == PseudoElementCascadeType::Eager
189     }
190 
191     /// Whether this pseudo-element is lazily-cascaded.
192     #[inline]
is_lazy(&self) -> bool193     pub fn is_lazy(&self) -> bool {
194         self.cascade_type() == PseudoElementCascadeType::Lazy
195     }
196 
197     /// Whether this pseudo-element is for an anonymous box.
is_anon_box(&self) -> bool198     pub fn is_anon_box(&self) -> bool {
199         self.is_precomputed()
200     }
201 
202     /// Whether this pseudo-element skips flex/grid container display-based
203     /// fixup.
204     #[inline]
skip_item_display_fixup(&self) -> bool205     pub fn skip_item_display_fixup(&self) -> bool {
206         !self.is_before_or_after()
207     }
208 
209     /// Whether this pseudo-element is precomputed.
210     #[inline]
is_precomputed(&self) -> bool211     pub fn is_precomputed(&self) -> bool {
212         self.cascade_type() == PseudoElementCascadeType::Precomputed
213     }
214 
215     /// Returns which kind of cascade type has this pseudo.
216     ///
217     /// For more info on cascade types, see docs/components/style.md
218     ///
219     /// Note: Keep this in sync with EAGER_PSEUDO_COUNT.
220     #[inline]
cascade_type(&self) -> PseudoElementCascadeType221     pub fn cascade_type(&self) -> PseudoElementCascadeType {
222         match *self {
223             PseudoElement::After | PseudoElement::Before | PseudoElement::Selection => {
224                 PseudoElementCascadeType::Eager
225             },
226             PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy,
227             PseudoElement::DetailsContent |
228             PseudoElement::ServoText |
229             PseudoElement::ServoInputText |
230             PseudoElement::ServoTableWrapper |
231             PseudoElement::ServoAnonymousTableWrapper |
232             PseudoElement::ServoAnonymousTable |
233             PseudoElement::ServoAnonymousTableRow |
234             PseudoElement::ServoAnonymousTableCell |
235             PseudoElement::ServoAnonymousBlock |
236             PseudoElement::ServoInlineBlockWrapper |
237             PseudoElement::ServoInlineAbsolute => PseudoElementCascadeType::Precomputed,
238         }
239     }
240 
241     /// Covert non-canonical pseudo-element to canonical one, and keep a
242     /// canonical one as it is.
canonical(&self) -> PseudoElement243     pub fn canonical(&self) -> PseudoElement {
244         self.clone()
245     }
246 
247     /// Stub, only Gecko needs this
pseudo_info(&self)248     pub fn pseudo_info(&self) {
249         ()
250     }
251 
252     /// Property flag that properties must have to apply to this pseudo-element.
253     #[inline]
property_restriction(&self) -> Option<PropertyFlags>254     pub fn property_restriction(&self) -> Option<PropertyFlags> {
255         None
256     }
257 
258     /// Whether this pseudo-element should actually exist if it has
259     /// the given styles.
should_exist(&self, style: &ComputedValues) -> bool260     pub fn should_exist(&self, style: &ComputedValues) -> bool {
261         let display = style.get_box().clone_display();
262         if display == Display::None {
263             return false;
264         }
265         if self.is_before_or_after() && style.ineffective_content_property() {
266             return false;
267         }
268 
269         true
270     }
271 }
272 
273 /// The type used for storing `:lang` arguments.
274 pub type Lang = Box<str>;
275 
276 /// A non tree-structural pseudo-class.
277 /// See https://drafts.csswg.org/selectors-4/#structural-pseudos
278 #[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
279 #[allow(missing_docs)]
280 pub enum NonTSPseudoClass {
281     Active,
282     AnyLink,
283     Checked,
284     Defined,
285     Disabled,
286     Enabled,
287     Focus,
288     Fullscreen,
289     Hover,
290     Indeterminate,
291     Lang(Lang),
292     Link,
293     PlaceholderShown,
294     ReadWrite,
295     ReadOnly,
296     ServoNonZeroBorder,
297     Target,
298     Visited,
299 }
300 
301 impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
302     type Impl = SelectorImpl;
303 
304     #[inline]
is_active_or_hover(&self) -> bool305     fn is_active_or_hover(&self) -> bool {
306         matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
307     }
308 
309     #[inline]
is_user_action_state(&self) -> bool310     fn is_user_action_state(&self) -> bool {
311         matches!(
312             *self,
313             NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::Focus
314         )
315     }
316 
visit<V>(&self, _: &mut V) -> bool where V: SelectorVisitor<Impl = Self::Impl>,317     fn visit<V>(&self, _: &mut V) -> bool
318     where
319         V: SelectorVisitor<Impl = Self::Impl>,
320     {
321         true
322     }
323 }
324 
325 impl ToCss for NonTSPseudoClass {
to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write,326     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
327     where
328         W: fmt::Write,
329     {
330         use self::NonTSPseudoClass::*;
331         if let Lang(ref lang) = *self {
332             dest.write_str(":lang(")?;
333             serialize_identifier(lang, dest)?;
334             return dest.write_str(")");
335         }
336 
337         dest.write_str(match *self {
338             Active => ":active",
339             AnyLink => ":any-link",
340             Checked => ":checked",
341             Defined => ":defined",
342             Disabled => ":disabled",
343             Enabled => ":enabled",
344             Focus => ":focus",
345             Fullscreen => ":fullscreen",
346             Hover => ":hover",
347             Indeterminate => ":indeterminate",
348             Link => ":link",
349             PlaceholderShown => ":placeholder-shown",
350             ReadWrite => ":read-write",
351             ReadOnly => ":read-only",
352             ServoNonZeroBorder => ":-servo-nonzero-border",
353             Target => ":target",
354             Visited => ":visited",
355             Lang(_) => unreachable!(),
356         })
357     }
358 }
359 
360 impl NonTSPseudoClass {
361     /// Gets a given state flag for this pseudo-class. This is used to do
362     /// selector matching, and it's set from the DOM.
state_flag(&self) -> ElementState363     pub fn state_flag(&self) -> ElementState {
364         use self::NonTSPseudoClass::*;
365         match *self {
366             Active => ElementState::IN_ACTIVE_STATE,
367             Focus => ElementState::IN_FOCUS_STATE,
368             Fullscreen => ElementState::IN_FULLSCREEN_STATE,
369             Hover => ElementState::IN_HOVER_STATE,
370             Defined => ElementState::IN_DEFINED_STATE,
371             Enabled => ElementState::IN_ENABLED_STATE,
372             Disabled => ElementState::IN_DISABLED_STATE,
373             Checked => ElementState::IN_CHECKED_STATE,
374             Indeterminate => ElementState::IN_INDETERMINATE_STATE,
375             ReadOnly | ReadWrite => ElementState::IN_READWRITE_STATE,
376             PlaceholderShown => ElementState::IN_PLACEHOLDER_SHOWN_STATE,
377             Target => ElementState::IN_TARGET_STATE,
378 
379             AnyLink | Lang(_) | Link | Visited | ServoNonZeroBorder => ElementState::empty(),
380         }
381     }
382 
383     /// Get the document state flag associated with a pseudo-class, if any.
document_state_flag(&self) -> DocumentState384     pub fn document_state_flag(&self) -> DocumentState {
385         DocumentState::empty()
386     }
387 
388     /// Returns true if the given pseudoclass should trigger style sharing cache revalidation.
needs_cache_revalidation(&self) -> bool389     pub fn needs_cache_revalidation(&self) -> bool {
390         self.state_flag().is_empty()
391     }
392 }
393 
394 /// The abstract struct we implement the selector parser implementation on top
395 /// of.
396 #[derive(Clone, Debug, PartialEq)]
397 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
398 pub struct SelectorImpl;
399 
400 impl ::selectors::SelectorImpl for SelectorImpl {
401     type PseudoElement = PseudoElement;
402     type NonTSPseudoClass = NonTSPseudoClass;
403 
404     type ExtraMatchingData = InvalidationMatchingData;
405     type AttrValue = String;
406     type Identifier = Atom;
407     type ClassName = Atom;
408     type PartName = Atom;
409     type LocalName = LocalName;
410     type NamespacePrefix = Prefix;
411     type NamespaceUrl = Namespace;
412     type BorrowedLocalName = LocalName;
413     type BorrowedNamespaceUrl = Namespace;
414 }
415 
416 impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
417     type Impl = SelectorImpl;
418     type Error = StyleParseErrorKind<'i>;
419 
parse_non_ts_pseudo_class( &self, location: SourceLocation, name: CowRcStr<'i>, ) -> Result<NonTSPseudoClass, ParseError<'i>>420     fn parse_non_ts_pseudo_class(
421         &self,
422         location: SourceLocation,
423         name: CowRcStr<'i>,
424     ) -> Result<NonTSPseudoClass, ParseError<'i>> {
425         use self::NonTSPseudoClass::*;
426         let pseudo_class = match_ignore_ascii_case! { &name,
427             "active" => Active,
428             "any-link" => AnyLink,
429             "checked" => Checked,
430             "defined" => Defined,
431             "disabled" => Disabled,
432             "enabled" => Enabled,
433             "focus" => Focus,
434             "fullscreen" => Fullscreen,
435             "hover" => Hover,
436             "indeterminate" => Indeterminate,
437             "-moz-inert" => MozInert,
438             "link" => Link,
439             "placeholder-shown" => PlaceholderShown,
440             "read-write" => ReadWrite,
441             "read-only" => ReadOnly,
442             "target" => Target,
443             "visited" => Visited,
444             "-servo-nonzero-border" => {
445                 if !self.in_user_agent_stylesheet() {
446                     return Err(location.new_custom_error(
447                         SelectorParseErrorKind::UnexpectedIdent("-servo-nonzero-border".into())
448                     ))
449                 }
450                 ServoNonZeroBorder
451             },
452             _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
453         };
454 
455         Ok(pseudo_class)
456     }
457 
parse_non_ts_functional_pseudo_class<'t>( &self, name: CowRcStr<'i>, parser: &mut CssParser<'i, 't>, ) -> Result<NonTSPseudoClass, ParseError<'i>>458     fn parse_non_ts_functional_pseudo_class<'t>(
459         &self,
460         name: CowRcStr<'i>,
461         parser: &mut CssParser<'i, 't>,
462     ) -> Result<NonTSPseudoClass, ParseError<'i>> {
463         use self::NonTSPseudoClass::*;
464         let pseudo_class = match_ignore_ascii_case! { &name,
465             "lang" => {
466                 Lang(parser.expect_ident_or_string()?.as_ref().into())
467             },
468             _ => return Err(parser.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
469         };
470 
471         Ok(pseudo_class)
472     }
473 
parse_pseudo_element( &self, location: SourceLocation, name: CowRcStr<'i>, ) -> Result<PseudoElement, ParseError<'i>>474     fn parse_pseudo_element(
475         &self,
476         location: SourceLocation,
477         name: CowRcStr<'i>,
478     ) -> Result<PseudoElement, ParseError<'i>> {
479         use self::PseudoElement::*;
480         let pseudo_element = match_ignore_ascii_case! { &name,
481             "before" => Before,
482             "after" => After,
483             "selection" => Selection,
484             "-servo-details-summary" => {
485                 if !self.in_user_agent_stylesheet() {
486                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
487                 }
488                 DetailsSummary
489             },
490             "-servo-details-content" => {
491                 if !self.in_user_agent_stylesheet() {
492                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
493                 }
494                 DetailsContent
495             },
496             "-servo-text" => {
497                 if !self.in_user_agent_stylesheet() {
498                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
499                 }
500                 ServoText
501             },
502             "-servo-input-text" => {
503                 if !self.in_user_agent_stylesheet() {
504                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
505                 }
506                 ServoInputText
507             },
508             "-servo-table-wrapper" => {
509                 if !self.in_user_agent_stylesheet() {
510                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
511                 }
512                 ServoTableWrapper
513             },
514             "-servo-anonymous-table-wrapper" => {
515                 if !self.in_user_agent_stylesheet() {
516                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
517                 }
518                 ServoAnonymousTableWrapper
519             },
520             "-servo-anonymous-table" => {
521                 if !self.in_user_agent_stylesheet() {
522                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
523                 }
524                 ServoAnonymousTable
525             },
526             "-servo-anonymous-table-row" => {
527                 if !self.in_user_agent_stylesheet() {
528                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
529                 }
530                 ServoAnonymousTableRow
531             },
532             "-servo-anonymous-table-cell" => {
533                 if !self.in_user_agent_stylesheet() {
534                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
535                 }
536                 ServoAnonymousTableCell
537             },
538             "-servo-anonymous-block" => {
539                 if !self.in_user_agent_stylesheet() {
540                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
541                 }
542                 ServoAnonymousBlock
543             },
544             "-servo-inline-block-wrapper" => {
545                 if !self.in_user_agent_stylesheet() {
546                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
547                 }
548                 ServoInlineBlockWrapper
549             },
550             "-servo-inline-absolute" => {
551                 if !self.in_user_agent_stylesheet() {
552                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
553                 }
554                 ServoInlineAbsolute
555             },
556             _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
557 
558         };
559 
560         Ok(pseudo_element)
561     }
562 
default_namespace(&self) -> Option<Namespace>563     fn default_namespace(&self) -> Option<Namespace> {
564         self.namespaces.default.as_ref().map(|ns| ns.clone())
565     }
566 
namespace_for_prefix(&self, prefix: &Prefix) -> Option<Namespace>567     fn namespace_for_prefix(&self, prefix: &Prefix) -> Option<Namespace> {
568         self.namespaces.prefixes.get(prefix).cloned()
569     }
570 }
571 
572 impl SelectorImpl {
573     /// A helper to traverse each eagerly cascaded pseudo-element, executing
574     /// `fun` on it.
575     #[inline]
each_eagerly_cascaded_pseudo_element<F>(mut fun: F) where F: FnMut(PseudoElement),576     pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
577     where
578         F: FnMut(PseudoElement),
579     {
580         for i in 0..EAGER_PSEUDO_COUNT {
581             fun(PseudoElement::from_eager_index(i));
582         }
583     }
584 }
585 
586 /// A map from elements to snapshots for the Servo style back-end.
587 #[derive(Debug)]
588 pub struct SnapshotMap(FxHashMap<OpaqueNode, ServoElementSnapshot>);
589 
590 impl SnapshotMap {
591     /// Create a new empty `SnapshotMap`.
new() -> Self592     pub fn new() -> Self {
593         SnapshotMap(FxHashMap::default())
594     }
595 
596     /// Get a snapshot given an element.
get<T: TElement>(&self, el: &T) -> Option<&ServoElementSnapshot>597     pub fn get<T: TElement>(&self, el: &T) -> Option<&ServoElementSnapshot> {
598         self.0.get(&el.as_node().opaque())
599     }
600 }
601 
602 impl Deref for SnapshotMap {
603     type Target = FxHashMap<OpaqueNode, ServoElementSnapshot>;
604 
deref(&self) -> &Self::Target605     fn deref(&self) -> &Self::Target {
606         &self.0
607     }
608 }
609 
610 impl DerefMut for SnapshotMap {
deref_mut(&mut self) -> &mut Self::Target611     fn deref_mut(&mut self) -> &mut Self::Target {
612         &mut self.0
613     }
614 }
615 
616 /// Servo's version of an element snapshot.
617 #[derive(Debug, Default, MallocSizeOf)]
618 pub struct ServoElementSnapshot {
619     /// The stored state of the element.
620     pub state: Option<ElementState>,
621     /// The set of stored attributes and its values.
622     pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
623     /// The set of changed attributes and its values.
624     pub changed_attrs: Vec<LocalName>,
625     /// Whether the class attribute changed or not.
626     pub class_changed: bool,
627     /// Whether the id attribute changed or not.
628     pub id_changed: bool,
629     /// Whether other attributes other than id or class changed or not.
630     pub other_attributes_changed: bool,
631 }
632 
633 impl ServoElementSnapshot {
634     /// Create an empty element snapshot.
new() -> Self635     pub fn new() -> Self {
636         Self::default()
637     }
638 
639     /// Returns whether the id attribute changed or not.
id_changed(&self) -> bool640     pub fn id_changed(&self) -> bool {
641         self.id_changed
642     }
643 
644     /// Returns whether the class attribute changed or not.
class_changed(&self) -> bool645     pub fn class_changed(&self) -> bool {
646         self.class_changed
647     }
648 
649     /// Returns whether other attributes other than id or class changed or not.
other_attr_changed(&self) -> bool650     pub fn other_attr_changed(&self) -> bool {
651         self.other_attributes_changed
652     }
653 
get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue>654     fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
655         self.attrs
656             .as_ref()
657             .unwrap()
658             .iter()
659             .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace)
660             .map(|&(_, ref v)| v)
661     }
662 
663     /// Executes the callback once for each attribute that changed.
664     #[inline]
each_attr_changed<F>(&self, mut callback: F) where F: FnMut(&LocalName),665     pub fn each_attr_changed<F>(&self, mut callback: F)
666     where
667         F: FnMut(&LocalName),
668     {
669         for name in &self.changed_attrs {
670             callback(name)
671         }
672     }
673 
any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool where F: FnMut(&AttrValue) -> bool,674     fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool
675     where
676         F: FnMut(&AttrValue) -> bool,
677     {
678         self.attrs
679             .as_ref()
680             .unwrap()
681             .iter()
682             .any(|&(ref ident, ref v)| ident.local_name == *name && f(v))
683     }
684 }
685 
686 impl ElementSnapshot for ServoElementSnapshot {
state(&self) -> Option<ElementState>687     fn state(&self) -> Option<ElementState> {
688         self.state.clone()
689     }
690 
has_attrs(&self) -> bool691     fn has_attrs(&self) -> bool {
692         self.attrs.is_some()
693     }
694 
id_attr(&self) -> Option<&Atom>695     fn id_attr(&self) -> Option<&Atom> {
696         self.get_attr(&ns!(), &local_name!("id"))
697             .map(|v| v.as_atom())
698     }
699 
is_part(&self, _name: &AtomIdent) -> bool700     fn is_part(&self, _name: &AtomIdent) -> bool {
701         false
702     }
703 
imported_part(&self, _: &AtomIdent) -> Option<AtomIdent>704     fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> {
705         None
706     }
707 
has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool708     fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
709         self.get_attr(&ns!(), &local_name!("class"))
710             .map_or(false, |v| {
711                 v.as_tokens()
712                     .iter()
713                     .any(|atom| case_sensitivity.eq_atom(atom, name))
714             })
715     }
716 
each_class<F>(&self, mut callback: F) where F: FnMut(&AtomIdent),717     fn each_class<F>(&self, mut callback: F)
718     where
719         F: FnMut(&AtomIdent),
720     {
721         if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) {
722             for class in v.as_tokens() {
723                 callback(AtomIdent::cast(class));
724             }
725         }
726     }
727 
lang_attr(&self) -> Option<SelectorAttrValue>728     fn lang_attr(&self) -> Option<SelectorAttrValue> {
729         self.get_attr(&ns!(xml), &local_name!("lang"))
730             .or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
731             .map(|v| SelectorAttrValue::from(v as &str))
732     }
733 }
734 
735 impl ServoElementSnapshot {
736     /// selectors::Element::attr_matches
attr_matches( &self, ns: &NamespaceConstraint<&Namespace>, local_name: &LocalName, operation: &AttrSelectorOperation<&AtomString>, ) -> bool737     pub fn attr_matches(
738         &self,
739         ns: &NamespaceConstraint<&Namespace>,
740         local_name: &LocalName,
741         operation: &AttrSelectorOperation<&AtomString>,
742     ) -> bool {
743         match *ns {
744             NamespaceConstraint::Specific(ref ns) => self
745                 .get_attr(ns, local_name)
746                 .map_or(false, |value| value.eval_selector(operation)),
747             NamespaceConstraint::Any => {
748                 self.any_attr_ignore_ns(local_name, |value| value.eval_selector(operation))
749             },
750         }
751     }
752 }
753 
754 /// Returns whether the language is matched, as defined by
755 /// [RFC 4647](https://tools.ietf.org/html/rfc4647#section-3.3.2).
extended_filtering(tag: &str, range: &str) -> bool756 pub fn extended_filtering(tag: &str, range: &str) -> bool {
757     range.split(',').any(|lang_range| {
758         // step 1
759         let mut range_subtags = lang_range.split('\x2d');
760         let mut tag_subtags = tag.split('\x2d');
761 
762         // step 2
763         // Note: [Level-4 spec](https://drafts.csswg.org/selectors/#lang-pseudo) check for wild card
764         if let (Some(range_subtag), Some(tag_subtag)) = (range_subtags.next(), tag_subtags.next()) {
765             if !(range_subtag.eq_ignore_ascii_case(tag_subtag) ||
766                 range_subtag.eq_ignore_ascii_case("*"))
767             {
768                 return false;
769             }
770         }
771 
772         let mut current_tag_subtag = tag_subtags.next();
773 
774         // step 3
775         for range_subtag in range_subtags {
776             // step 3a
777             if range_subtag == "*" {
778                 continue;
779             }
780             match current_tag_subtag.clone() {
781                 Some(tag_subtag) => {
782                     // step 3c
783                     if range_subtag.eq_ignore_ascii_case(tag_subtag) {
784                         current_tag_subtag = tag_subtags.next();
785                         continue;
786                     }
787                     // step 3d
788                     if tag_subtag.len() == 1 {
789                         return false;
790                     }
791                     // else step 3e - continue with loop
792                     current_tag_subtag = tag_subtags.next();
793                     if current_tag_subtag.is_none() {
794                         return false;
795                     }
796                 },
797                 // step 3b
798                 None => {
799                     return false;
800                 },
801             }
802         }
803         // step 4
804         true
805     })
806 }
807