servo/components/selectors/builder.rs
Emilio Cobos Álvarez db5db13559
style: Implement the non-functional :host selector.
Kinda tricky because :host only matches rules on the shadow root where the rules
come from. So we need to be careful during invalidation and style sharing.

I didn't use the non_ts_pseudo_class_list bits because as soon as we implement
the :host(..) bits we're going to need to special-case it anyway.

The general schema is the following:

 * Rightmost featureless :host selectors are handled inserting them in the
   host_rules hashmap. Note that we only insert featureless stuff there. We
   could insert all of them and just filter during matching, but that's slightly
   annoying.

 * The other selectors, like non-featureless :host or what not, are added to the
   normal cascade data. This is harmless, since the shadow host rules are never
   matched against the host, so we know they'll just never match, and avoids
   adding more special-cases.

 * Featureless :host selectors to the left of a combinator are handled during
   matching, in the special-case of next_element_for_combinator in selectors.
   This prevents this from being more invasive, and keeps the usual fast path
   slim, but it's a bit hard to match the spec and the implementation.

   We could keep a copy of the SelectorIter instead in the matching context to
   make the handling of featureless-ness explicit in match_non_ts_pseudo_class,
   but we'd still need the special-case anyway, so I'm not fond of it.

 * We take advantage of one thing that makes this sound. As you may have
   noticed, if you had `root` element which is a ShadowRoot, and you matched
   something like `div:host` against it, using a MatchingContext with
   current_host == root, we'd incorrectly report a match. But this is impossible
   due to the following constraints:

    * Shadow root rules aren't matched against the host during styling (except
      these featureless selectors).

    * DOM APIs' current_host needs to be the _containing_ host, not the element
      itself if you're a Shadow host.

Bug: 992245
Reviewed-by: xidorn
MozReview-Commit-ID: KayYNfTXb5h
2018-04-09 12:19:05 +02:00

347 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Helper module to build up a selector safely and efficiently.
//!
//! Our selector representation is designed to optimize matching, and has
//! several requirements:
//! * All simple selectors and combinators are stored inline in the same buffer
//! as Component instances.
//! * We store the top-level compound selectors from right to left, i.e. in
//! matching order.
//! * We store the simple selectors for each combinator from left to right, so
//! that we match the cheaper simple selectors first.
//!
//! Meeting all these constraints without extra memmove traffic during parsing
//! is non-trivial. This module encapsulates those details and presents an
//! easy-to-use API for the parser.
use parser::{Combinator, Component, SelectorImpl};
use servo_arc::{Arc, HeaderWithLength, ThinArc};
use sink::Push;
use smallvec::{self, SmallVec};
use std::cmp;
use std::iter;
use std::ops::Add;
use std::ptr;
use std::slice;
/// Top-level SelectorBuilder struct. This should be stack-allocated by the
/// consumer and never moved (because it contains a lot of inline data that
/// would be slow to memmov).
///
/// After instantation, callers may call the push_simple_selector() and
/// push_combinator() methods to append selector data as it is encountered
/// (from left to right). Once the process is complete, callers should invoke
/// build(), which transforms the contents of the SelectorBuilder into a heap-
/// allocated Selector and leaves the builder in a drained state.
pub struct SelectorBuilder<Impl: SelectorImpl> {
/// The entire sequence of simple selectors, from left to right, without combinators.
///
/// We make this large because the result of parsing a selector is fed into a new
/// Arc-ed allocation, so any spilled vec would be a wasted allocation. Also,
/// Components are large enough that we don't have much cache locality benefit
/// from reserving stack space for fewer of them.
simple_selectors: SmallVec<[Component<Impl>; 32]>,
/// The combinators, and the length of the compound selector to their left.
combinators: SmallVec<[(Combinator, usize); 16]>,
/// The length of the current compount selector.
current_len: usize,
}
impl<Impl: SelectorImpl> Default for SelectorBuilder<Impl> {
#[inline(always)]
fn default() -> Self {
SelectorBuilder {
simple_selectors: SmallVec::new(),
combinators: SmallVec::new(),
current_len: 0,
}
}
}
impl<Impl: SelectorImpl> Push<Component<Impl>> for SelectorBuilder<Impl> {
fn push(&mut self, value: Component<Impl>) {
self.push_simple_selector(value);
}
}
impl<Impl: SelectorImpl> SelectorBuilder<Impl> {
/// Pushes a simple selector onto the current compound selector.
#[inline(always)]
pub fn push_simple_selector(&mut self, ss: Component<Impl>) {
debug_assert!(!ss.is_combinator());
self.simple_selectors.push(ss);
self.current_len += 1;
}
/// Completes the current compound selector and starts a new one, delimited
/// by the given combinator.
#[inline(always)]
pub fn push_combinator(&mut self, c: Combinator) {
self.combinators.push((c, self.current_len));
self.current_len = 0;
}
/// Returns true if no simple selectors have ever been pushed to this builder.
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.simple_selectors.is_empty()
}
/// Returns true if combinators have ever been pushed to this builder.
#[inline(always)]
pub fn has_combinators(&self) -> bool {
!self.combinators.is_empty()
}
/// Consumes the builder, producing a Selector.
#[inline(always)]
pub fn build(
&mut self,
parsed_pseudo: bool,
parsed_slotted: bool,
) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
// Compute the specificity and flags.
let mut spec = SpecificityAndFlags(specificity(self.simple_selectors.iter()));
if parsed_pseudo {
spec.0 |= HAS_PSEUDO_BIT;
}
if parsed_slotted {
spec.0 |= HAS_SLOTTED_BIT;
}
self.build_with_specificity_and_flags(spec)
}
/// Builds with an explicit SpecificityAndFlags. This is separated from build() so
/// that unit tests can pass an explicit specificity.
#[inline(always)]
pub fn build_with_specificity_and_flags(&mut self, spec: SpecificityAndFlags)
-> ThinArc<SpecificityAndFlags, Component<Impl>> {
// First, compute the total number of Components we'll need to allocate
// space for.
let full_len = self.simple_selectors.len() + self.combinators.len();
// Create the header.
let header = HeaderWithLength::new(spec, full_len);
// Create the Arc using an iterator that drains our buffers.
// Use a raw pointer to be able to call set_len despite "borrowing" the slice.
// This is similar to SmallVec::drain, but we use a slice here because
// were gonna traverse it non-linearly.
let raw_simple_selectors: *const [Component<Impl>] = &*self.simple_selectors;
unsafe {
// Panic-safety: if SelectorBuilderIter is not iterated to the end,
// some simple selectors will safely leak.
self.simple_selectors.set_len(0)
}
let (rest, current) = split_from_end(unsafe { &*raw_simple_selectors }, self.current_len);
let iter = SelectorBuilderIter {
current_simple_selectors: current.iter(),
rest_of_simple_selectors: rest,
combinators: self.combinators.drain().rev(),
};
Arc::into_thin(Arc::from_header_and_iter(header, iter))
}
}
struct SelectorBuilderIter<'a, Impl: SelectorImpl> {
current_simple_selectors: slice::Iter<'a, Component<Impl>>,
rest_of_simple_selectors: &'a [Component<Impl>],
combinators: iter::Rev<smallvec::Drain<'a, (Combinator, usize)>>,
}
impl<'a, Impl: SelectorImpl> ExactSizeIterator for SelectorBuilderIter<'a, Impl> {
fn len(&self) -> usize {
self.current_simple_selectors.len() +
self.rest_of_simple_selectors.len() +
self.combinators.len()
}
}
impl<'a, Impl: SelectorImpl> Iterator for SelectorBuilderIter<'a, Impl> {
type Item = Component<Impl>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
if let Some(simple_selector_ref) = self.current_simple_selectors.next() {
// Move a simple selector out of this slice iterator.
// This is safe because weve called SmallVec::set_len(0) above,
// so SmallVec::drop wont drop this simple selector.
unsafe {
Some(ptr::read(simple_selector_ref))
}
} else {
self.combinators.next().map(|(combinator, len)| {
let (rest, current) = split_from_end(self.rest_of_simple_selectors, len);
self.rest_of_simple_selectors = rest;
self.current_simple_selectors = current.iter();
Component::Combinator(combinator)
})
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len(), Some(self.len()))
}
}
fn split_from_end<T>(s: &[T], at: usize) -> (&[T], &[T]) {
s.split_at(s.len() - at)
}
pub const HAS_PSEUDO_BIT: u32 = 1 << 30;
pub const HAS_SLOTTED_BIT: u32 = 1 << 31;
/// We use ten bits for each specificity kind (id, class, element), and the two
/// high bits for the pseudo and slotted flags.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SpecificityAndFlags(pub u32);
impl SpecificityAndFlags {
#[inline]
pub fn specificity(&self) -> u32 {
self.0 & !(HAS_PSEUDO_BIT | HAS_SLOTTED_BIT)
}
#[inline]
pub fn has_pseudo_element(&self) -> bool {
(self.0 & HAS_PSEUDO_BIT) != 0
}
#[inline]
pub fn is_slotted(&self) -> bool {
(self.0 & HAS_SLOTTED_BIT) != 0
}
}
const MAX_10BIT: u32 = (1u32 << 10) - 1;
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
struct Specificity {
id_selectors: u32,
class_like_selectors: u32,
element_selectors: u32,
}
impl Add for Specificity {
type Output = Specificity;
fn add(self, rhs: Specificity) -> Specificity {
Specificity {
id_selectors: self.id_selectors + rhs.id_selectors,
class_like_selectors:
self.class_like_selectors + rhs.class_like_selectors,
element_selectors:
self.element_selectors + rhs.element_selectors,
}
}
}
impl Default for Specificity {
fn default() -> Specificity {
Specificity {
id_selectors: 0,
class_like_selectors: 0,
element_selectors: 0,
}
}
}
impl From<u32> for Specificity {
fn from(value: u32) -> Specificity {
assert!(value <= MAX_10BIT << 20 | MAX_10BIT << 10 | MAX_10BIT);
Specificity {
id_selectors: value >> 20,
class_like_selectors: (value >> 10) & MAX_10BIT,
element_selectors: value & MAX_10BIT,
}
}
}
impl From<Specificity> for u32 {
fn from(specificity: Specificity) -> u32 {
cmp::min(specificity.id_selectors, MAX_10BIT) << 20
| cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10
| cmp::min(specificity.element_selectors, MAX_10BIT)
}
}
fn specificity<Impl>(iter: slice::Iter<Component<Impl>>) -> u32
where Impl: SelectorImpl
{
complex_selector_specificity(iter).into()
}
fn complex_selector_specificity<Impl>(mut iter: slice::Iter<Component<Impl>>)
-> Specificity
where Impl: SelectorImpl
{
fn simple_selector_specificity<Impl>(
simple_selector: &Component<Impl>,
specificity: &mut Specificity,
)
where
Impl: SelectorImpl
{
match *simple_selector {
Component::Combinator(..) => unreachable!(),
// FIXME(emilio): Spec doesn't define any particular specificity for
// ::slotted(), so apply the general rule for pseudos per:
//
// https://github.com/w3c/csswg-drafts/issues/1915
//
// Though other engines compute it dynamically, so maybe we should
// do that instead, eventually.
Component::Slotted(..) |
Component::PseudoElement(..) |
Component::LocalName(..) => {
specificity.element_selectors += 1
}
Component::ID(..) => {
specificity.id_selectors += 1
}
Component::Class(..) |
Component::AttributeInNoNamespace { .. } |
Component::AttributeInNoNamespaceExists { .. } |
Component::AttributeOther(..) |
Component::FirstChild | Component::LastChild |
Component::OnlyChild | Component::Root |
Component::Empty | Component::Scope |
Component::Host |
Component::NthChild(..) |
Component::NthLastChild(..) |
Component::NthOfType(..) |
Component::NthLastOfType(..) |
Component::FirstOfType | Component::LastOfType |
Component::OnlyOfType |
Component::NonTSPseudoClass(..) => {
specificity.class_like_selectors += 1
}
Component::ExplicitUniversalType |
Component::ExplicitAnyNamespace |
Component::ExplicitNoNamespace |
Component::DefaultNamespace(..) |
Component::Namespace(..) => {
// Does not affect specificity
}
Component::Negation(ref negated) => {
for ss in negated.iter() {
simple_selector_specificity(&ss, specificity);
}
}
}
}
let mut specificity = Default::default();
for simple_selector in &mut iter {
simple_selector_specificity(&simple_selector, &mut specificity);
}
specificity
}