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