Bug 1364850: Move PseudoElement to be just another combinator in selectors. r=bholley

MozReview-Commit-ID: 8OoOIodkKJ5
Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
This commit is contained in:
Emilio Cobos Álvarez 2017-05-15 23:52:09 +02:00
parent 8375319928
commit 522f8489d6
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
19 changed files with 675 additions and 541 deletions

View file

@ -10,12 +10,24 @@
use cssparser::ToCss;
use gecko_bindings::structs::{self, CSSPseudoElementType};
use selector_parser::PseudoElementCascadeType;
use selector_parser::{NonTSPseudoClass, PseudoElementCascadeType, SelectorImpl};
use std::fmt;
use string_cache::Atom;
include!(concat!(env!("OUT_DIR"), "/gecko/pseudo_element_definition.rs"));
impl ::selectors::parser::PseudoElement for PseudoElement {
type Impl = SelectorImpl;
fn supports_pseudo_class(&self, pseudo_class: &NonTSPseudoClass) -> bool {
if !self.supports_user_action_state() {
return false;
}
return pseudo_class.is_safe_user_action_state();
}
}
impl PseudoElement {
/// Returns the kind of cascade type that a given pseudo is going to use.
///

View file

@ -5,7 +5,6 @@
//! Gecko-specific bits for selector-parsing.
use cssparser::{Parser, ToCss};
use element_state::{IN_ACTIVE_STATE, IN_FOCUS_STATE, IN_HOVER_STATE};
use element_state::ElementState;
use gecko_bindings::structs::CSSPseudoClassType;
use selector_parser::{SelectorParser, PseudoElementCascadeType};
@ -132,7 +131,7 @@ impl NonTSPseudoClass {
/// https://drafts.csswg.org/selectors-4/#useraction-pseudos
///
/// We intentionally skip the link-related ones.
fn is_safe_user_action_state(&self) -> bool {
pub fn is_safe_user_action_state(&self) -> bool {
matches!(*self, NonTSPseudoClass::Hover |
NonTSPseudoClass::Active |
NonTSPseudoClass::Focus)
@ -195,58 +194,6 @@ impl NonTSPseudoClass {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SelectorImpl;
/// Some subset of pseudo-elements in Gecko are sensitive to some state
/// selectors.
///
/// We store the sensitive states in this struct in order to properly handle
/// these.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct PseudoElementSelector {
pseudo: PseudoElement,
state: ElementState,
}
impl PseudoElementSelector {
/// Returns the pseudo-element this selector represents.
pub fn pseudo_element(&self) -> &PseudoElement {
&self.pseudo
}
/// Returns the pseudo-element selector state.
pub fn state(&self) -> ElementState {
self.state
}
}
impl ToCss for PseudoElementSelector {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write,
{
if cfg!(debug_assertions) {
let mut state = self.state;
state.remove(IN_HOVER_STATE | IN_ACTIVE_STATE | IN_FOCUS_STATE);
assert_eq!(state, ElementState::empty(),
"Unhandled pseudo-element state selector?");
}
self.pseudo.to_css(dest)?;
if self.state.contains(IN_HOVER_STATE) {
dest.write_str(":hover")?
}
if self.state.contains(IN_ACTIVE_STATE) {
dest.write_str(":active")?
}
if self.state.contains(IN_FOCUS_STATE) {
dest.write_str(":focus")?
}
Ok(())
}
}
impl ::selectors::SelectorImpl for SelectorImpl {
type AttrValue = Atom;
type Identifier = Atom;
@ -257,7 +204,7 @@ impl ::selectors::SelectorImpl for SelectorImpl {
type BorrowedNamespaceUrl = WeakNamespace;
type BorrowedLocalName = WeakAtom;
type PseudoElementSelector = PseudoElementSelector;
type PseudoElement = PseudoElement;
type NonTSPseudoClass = NonTSPseudoClass;
}
@ -319,38 +266,9 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
}
}
fn parse_pseudo_element(&self, name: Cow<str>, input: &mut Parser) -> Result<PseudoElementSelector, ()> {
let pseudo =
match PseudoElement::from_slice(&name, self.in_user_agent_stylesheet()) {
Some(pseudo) => pseudo,
None => return Err(()),
};
let state = if pseudo.supports_user_action_state() {
input.try(|input| {
let mut state = ElementState::empty();
while !input.is_exhausted() {
input.expect_colon()?;
let ident = input.expect_ident()?;
let pseudo_class = self.parse_non_ts_pseudo_class(ident)?;
if !pseudo_class.is_safe_user_action_state() {
return Err(())
}
state.insert(pseudo_class.state_flag());
}
Ok(state)
}).ok()
} else {
None
};
Ok(PseudoElementSelector {
pseudo: pseudo,
state: state.unwrap_or(ElementState::empty()),
})
fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> {
PseudoElement::from_slice(&name, self.in_user_agent_stylesheet())
.ok_or(())
}
fn default_namespace(&self) -> Option<Namespace> {

View file

@ -64,7 +64,7 @@ use properties::style_structs::Font;
use rule_tree::CascadeLevel as ServoCascadeLevel;
use selector_parser::ElementExt;
use selectors::Element;
use selectors::matching::{ElementSelectorFlags, MatchingContext};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
use selectors::parser::{AttrSelector, NamespaceConstraint};
use shared_lock::Locked;
use sink::Push;
@ -1055,6 +1055,11 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
parent_node.and_then(|n| n.as_element())
}
fn pseudo_element_originating_element(&self) -> Option<Self> {
debug_assert!(self.implemented_pseudo_element().is_some());
self.closest_non_native_anonymous_ancestor()
}
fn first_child_element(&self) -> Option<Self> {
let mut child = self.as_node().first_child();
while let Some(child_node) = child {
@ -1244,6 +1249,20 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
}
}
fn match_pseudo_element(&self,
pseudo_element: &PseudoElement,
_context: &mut MatchingContext)
-> bool
{
// TODO(emilio): I believe we could assert we are a pseudo-element and
// match the proper pseudo-element, given how we rulehash the stuff
// based on the pseudo.
match self.implemented_pseudo_element() {
Some(ref pseudo) => pseudo == pseudo_element,
None => false,
}
}
fn get_id(&self) -> Option<Atom> {
let ptr = unsafe {
bindings::Gecko_AtomAttrValue(self.0,
@ -1378,8 +1397,9 @@ impl<'le> ::selectors::MatchAttr for GeckoElement<'le> {
impl<'le> ElementExt for GeckoElement<'le> {
#[inline]
fn is_link(&self) -> bool {
let mut context = MatchingContext::new(MatchingMode::Normal, None);
self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink,
&mut MatchingContext::default(),
&mut context,
&mut |_, _| {})
}

View file

@ -15,7 +15,6 @@ use cascade_info::CascadeInfo;
use context::{CurrentElementInfo, SelectorFlagsMap, SharedStyleContext, StyleContext};
use data::{ComputedStyle, ElementData, ElementStyles, RestyleData};
use dom::{AnimationRules, SendElement, TElement, TNode};
use element_state::ElementState;
use font_metrics::FontMetricsProvider;
use properties::{CascadeFlags, ComputedValues, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade};
use properties::longhands::display::computed_value as display;
@ -24,7 +23,7 @@ use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_SMIL};
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode};
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
use selectors::bloom::BloomFilter;
use selectors::matching::{ElementSelectorFlags, MatchingContext, StyleRelations};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations};
use selectors::matching::AFFECTED_BY_PSEUDO_ELEMENTS;
use shared_lock::StylesheetGuards;
use sink::ForgetfulSink;
@ -895,10 +894,10 @@ pub trait MatchMethods : TElement {
sharing: StyleSharingBehavior)
{
// Perform selector matching for the primary style.
let mut primary_matching_context = MatchingContext::default();
let mut relations = StyleRelations::empty();
let _rule_node_changed = self.match_primary(context,
data,
&mut primary_matching_context);
&mut relations);
// Cascade properties and compute primary values.
self.cascade_primary(context, data);
@ -912,7 +911,7 @@ pub trait MatchMethods : TElement {
// If we have any pseudo elements, indicate so in the primary StyleRelations.
if !data.styles().pseudos.is_empty() {
primary_matching_context.relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
}
// If the style is shareable, add it to the LRU cache.
@ -932,7 +931,7 @@ pub trait MatchMethods : TElement {
.style_sharing_candidate_cache
.insert_if_possible(self,
data.styles().primary.values(),
primary_matching_context.relations,
relations,
revalidation_match_results);
}
}
@ -952,7 +951,7 @@ pub trait MatchMethods : TElement {
fn match_primary(&self,
context: &mut StyleContext<Self>,
data: &mut ElementData,
matching_context: &mut MatchingContext)
relations: &mut StyleRelations)
-> bool
{
let implemented_pseudo = self.implemented_pseudo_element();
@ -1004,35 +1003,27 @@ pub trait MatchMethods : TElement {
let animation_rules = self.get_animation_rules();
let bloom = context.thread_local.bloom_filter.filter();
let map = &mut context.thread_local.selector_flags;
let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| {
self.apply_selector_flags(map, element, flags);
};
let selector_matching_target = match implemented_pseudo {
Some(..) => {
self.closest_non_native_anonymous_ancestor()
.expect("Pseudo-element without non-NAC parent?")
},
None => *self,
};
let pseudo_and_state = match implemented_pseudo {
Some(ref pseudo) => Some((pseudo, self.get_state())),
None => None,
};
let mut matching_context =
MatchingContext::new(MatchingMode::Normal, Some(bloom));
// Compute the primary rule node.
stylist.push_applicable_declarations(&selector_matching_target,
Some(bloom),
stylist.push_applicable_declarations(self,
implemented_pseudo.as_ref(),
style_attribute,
smil_override,
animation_rules,
pseudo_and_state,
&mut applicable_declarations,
matching_context,
&mut matching_context,
&mut set_selector_flags);
*relations = matching_context.relations;
let primary_rule_node =
compute_rule_node::<Self>(&stylist.rule_tree,
&mut applicable_declarations,
@ -1041,8 +1032,8 @@ pub trait MatchMethods : TElement {
return data.set_primary_rules(primary_rule_node);
}
/// Runs selector matching to (re)compute eager pseudo-element rule nodes for this
/// element.
/// Runs selector matching to (re)compute eager pseudo-element rule nodes
/// for this element.
///
/// Returns whether any of the pseudo rule nodes changed (including, but not
/// limited to, cases where we match different pseudos altogether).
@ -1070,6 +1061,10 @@ pub trait MatchMethods : TElement {
let rule_tree = &stylist.rule_tree;
let bloom_filter = context.thread_local.bloom_filter.filter();
let mut matching_context =
MatchingContext::new(MatchingMode::ForStatelessPseudoElement,
Some(bloom_filter));
// Compute rule nodes for eagerly-cascaded pseudo-elements.
let mut matches_different_pseudos = false;
let mut rule_nodes_changed = false;
@ -1079,13 +1074,12 @@ pub trait MatchMethods : TElement {
// NB: We handle animation rules for ::before and ::after when
// traversing them.
stylist.push_applicable_declarations(self,
Some(bloom_filter),
Some(&pseudo),
None,
None,
AnimationRules(None, None),
Some((&pseudo, ElementState::empty())),
&mut applicable_declarations,
&mut MatchingContext::default(),
&mut matching_context,
&mut set_selector_flags);
if !applicable_declarations.is_empty() {

View file

@ -9,14 +9,13 @@
use Atom;
use dom::TElement;
use element_state::*;
use fnv::FnvHashMap;
#[cfg(feature = "gecko")]
use gecko_bindings::structs::nsRestyleHint;
#[cfg(feature = "servo")]
use heapsize::HeapSizeOf;
use selector_parser::{AttrValue, NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap};
use selectors::{Element, MatchAttr};
use selectors::matching::{ElementSelectorFlags, MatchingContext};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
use selectors::matching::matches_selector;
use selectors::parser::{AttrSelector, Combinator, Component, Selector};
use selectors::parser::{SelectorInner, SelectorMethods};
@ -406,6 +405,14 @@ impl<'a, E> Element for ElementWrapper<'a, E>
}
}
fn match_pseudo_element(&self,
pseudo_element: &PseudoElement,
context: &mut MatchingContext)
-> bool
{
self.element.match_pseudo_element(pseudo_element, context)
}
fn parent_element(&self) -> Option<Self> {
self.element.parent_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
@ -475,6 +482,11 @@ impl<'a, E> Element for ElementWrapper<'a, E>
_ => self.element.each_class(callback)
}
}
fn pseudo_element_originating_element(&self) -> Option<Self> {
self.element.closest_non_native_anonymous_ancestor()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
}
fn selector_to_state(sel: &Component<SelectorImpl>) -> ElementState {
@ -507,6 +519,9 @@ fn combinator_to_restyle_hint(combinator: Option<Combinator>) -> RestyleHint {
match combinator {
None => RESTYLE_SELF,
Some(c) => match c {
// NB: RESTYLE_SELF is needed to handle properly eager pseudos,
// otherwise we may leave a stale style on the parent.
Combinator::PseudoElement => RESTYLE_SELF | RESTYLE_DESCENDANTS,
Combinator::Child => RESTYLE_DESCENDANTS,
Combinator::Descendant => RESTYLE_DESCENDANTS,
Combinator::NextSibling => RESTYLE_LATER_SIBLINGS,
@ -634,13 +649,6 @@ impl SelectorVisitor for SensitivitiesVisitor {
#[derive(Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct DependencySet {
/// A map used for pseudo-element's dependencies.
///
/// Note that pseudo-elements are somewhat special, because some of them in
/// Gecko track state, and also because they don't do selector-matching as
/// normal, but against their parent element.
pseudo_dependencies: FnvHashMap<PseudoElement, SelectorMap<PseudoElementDependency>>,
/// This is for all other normal element's selectors/selector parts.
dependencies: SelectorMap<Dependency>,
}
@ -668,34 +676,9 @@ impl DependencySet {
index += 1; // Account for the simple selector.
}
let pseudo_selector_is_state_dependent =
sequence_start == 0 &&
selector.pseudo_element.as_ref().map_or(false, |pseudo_selector| {
!pseudo_selector.state().is_empty()
});
if pseudo_selector_is_state_dependent {
let pseudo_selector = selector.pseudo_element.as_ref().unwrap();
self.pseudo_dependencies
.entry(pseudo_selector.pseudo_element().clone())
.or_insert_with(SelectorMap::new)
.insert(PseudoElementDependency {
selector: selector.clone(),
});
}
// If we found a sensitivity, add an entry in the dependency set.
if !visitor.sensitivities.is_empty() {
let mut hint = combinator_to_restyle_hint(combinator);
if sequence_start == 0 && selector.pseudo_element.is_some() {
// FIXME(emilio): Be more granular about this. See the
// comment in `PseudoElementDependency` about how could this
// be modified in order to be more efficient and restyle
// less.
hint |= RESTYLE_DESCENDANTS;
}
let hint = combinator_to_restyle_hint(combinator);
let dep_selector = if sequence_start == 0 {
// Reuse the bloom hashes if this is the base selector.
@ -724,82 +707,22 @@ impl DependencySet {
pub fn new() -> Self {
DependencySet {
dependencies: SelectorMap::new(),
pseudo_dependencies: FnvHashMap::default(),
}
}
/// Return the total number of dependencies that this set contains.
pub fn len(&self) -> usize {
self.dependencies.len() +
self.pseudo_dependencies.values().fold(0, |acc, val| acc + val.len())
self.dependencies.len()
}
/// Clear this dependency set.
pub fn clear(&mut self) {
self.dependencies = SelectorMap::new();
self.pseudo_dependencies.clear()
}
fn compute_pseudo_hint<E>(
&self,
pseudo: &E,
pseudo_element: PseudoElement,
snapshots: &SnapshotMap)
-> RestyleHint
where E: TElement,
{
debug!("compute_pseudo_hint: {:?}, {:?}", pseudo, pseudo_element);
debug_assert!(pseudo.has_snapshot());
let map = match self.pseudo_dependencies.get(&pseudo_element) {
Some(map) => map,
None => return RestyleHint::empty(),
};
// Only pseudo-element's state is relevant.
let pseudo_state_changes =
ElementWrapper::new(*pseudo, snapshots).state_changes();
debug!("pseudo_state_changes: {:?}", pseudo_state_changes);
if pseudo_state_changes.is_empty() {
return RestyleHint::empty();
}
let selector_matching_target =
pseudo.closest_non_native_anonymous_ancestor().unwrap();
// Note that we rely on that, if the originating element changes, it'll
// post a restyle hint that would make us redo selector matching, so we
// don't need to care about that.
//
// If that ever changes, we'd need to share more code with
// `compute_element_hint`.
let mut hint = RestyleHint::empty();
map.lookup(selector_matching_target, &mut |dep| {
// If the selector didn't match before, it either doesn't match now
// either (or it doesn't matter because our parent posted a restyle
// for us above).
if !matches_selector(&dep.selector.inner, &selector_matching_target,
None, &mut MatchingContext::default(),
&mut |_, _| {}) {
return true;
}
let pseudo_selector = dep.selector.pseudo_element.as_ref().unwrap();
debug_assert!(!pseudo_selector.state().is_empty());
if pseudo_selector.state().intersects(pseudo_state_changes) {
hint = RESTYLE_SELF;
return false;
}
true
});
hint
}
fn compute_element_hint<E>(
/// Compute a restyle hint given an element and a snapshot, per the rules
/// explained in the rest of the documentation.
pub fn compute_hint<E>(
&self,
el: &E,
snapshots: &SnapshotMap)
@ -838,8 +761,18 @@ impl DependencySet {
});
}
// FIXME(emilio): A bloom filter here would be neat.
let mut matching_context =
MatchingContext::new(MatchingMode::Normal, None);
let lookup_element = if el.implemented_pseudo_element().is_some() {
el.closest_non_native_anonymous_ancestor().unwrap()
} else {
*el
};
self.dependencies
.lookup_with_additional(*el, additional_id, &additional_classes, &mut |dep| {
.lookup_with_additional(lookup_element, additional_id, &additional_classes, &mut |dep| {
trace!("scanning dependency: {:?}", dep);
if !dep.sensitivities.sensitive_to(attrs_changed,
state_changes) {
@ -856,12 +789,12 @@ impl DependencySet {
// been set during original matching for any element that might
// change its matching behavior here.
let matched_then =
matches_selector(&dep.selector, &snapshot_el, None,
&mut MatchingContext::default(),
matches_selector(&dep.selector, &snapshot_el,
&mut matching_context,
&mut |_, _| {});
let matches_now =
matches_selector(&dep.selector, el, None,
&mut MatchingContext::default(),
matches_selector(&dep.selector, el,
&mut matching_context,
&mut |_, _| {});
if matched_then != matches_now {
hint.insert(dep.hint);
@ -875,21 +808,4 @@ impl DependencySet {
hint
}
/// Compute a restyle hint given an element and a snapshot, per the rules
/// explained in the rest of the documentation.
pub fn compute_hint<E>(&self,
el: &E,
snapshots: &SnapshotMap)
-> RestyleHint
where E: TElement + Clone,
{
debug!("DependencySet::compute_hint({:?})", el);
if let Some(pseudo) = el.implemented_pseudo_element() {
return self.compute_pseudo_hint(el, pseudo, snapshots);
}
self.compute_element_hint(el, snapshots)
}
}

View file

@ -15,7 +15,7 @@ use fnv::FnvHashMap;
use restyle_hints::ElementSnapshot;
use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser};
use selectors::{Element, MatchAttrGeneric};
use selectors::matching::MatchingContext;
use selectors::matching::{MatchingContext, MatchingMode};
use selectors::parser::{AttrSelector, SelectorMethods};
use selectors::visitor::SelectorVisitor;
use std::borrow::Cow;
@ -51,6 +51,14 @@ pub enum PseudoElement {
ServoInlineAbsolute,
}
impl ::selectors::parser::PseudoElement for PseudoElement {
type Impl = SelectorImpl;
fn supports_pseudo_class(&self, _: &NonTSPseudoClass) -> bool {
false
}
}
impl ToCss for PseudoElement {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
use self::PseudoElement::*;
@ -78,18 +86,6 @@ impl ToCss for PseudoElement {
pub const EAGER_PSEUDO_COUNT: usize = 3;
impl PseudoElement {
/// The pseudo-element, used for compatibility with Gecko's
/// `PseudoElementSelector`.
pub fn pseudo_element(&self) -> &Self {
self
}
/// The pseudo-element selector's state, used for compatibility with Gecko's
/// `PseudoElementSelector`.
pub fn state(&self) -> ElementState {
ElementState::empty()
}
/// Gets the canonical index of this eagerly-cascaded pseudo-element.
#[inline]
pub fn eager_index(&self) -> usize {
@ -264,7 +260,7 @@ impl NonTSPseudoClass {
pub struct SelectorImpl;
impl ::selectors::SelectorImpl for SelectorImpl {
type PseudoElementSelector = PseudoElement;
type PseudoElement = PseudoElement;
type NonTSPseudoClass = NonTSPseudoClass;
type AttrValue = String;
@ -323,9 +319,7 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
Ok(pseudo_class)
}
fn parse_pseudo_element(&self,
name: Cow<str>,
_input: &mut CssParser)
fn parse_pseudo_element(&self, name: Cow<str>)
-> Result<PseudoElement, ()> {
use self::PseudoElement::*;
let pseudo_element = match_ignore_ascii_case! { &name,
@ -579,8 +573,9 @@ impl MatchAttrGeneric for ServoElementSnapshot {
impl<E: Element<Impl=SelectorImpl> + Debug> ElementExt for E {
fn is_link(&self) -> bool {
let mut context = MatchingContext::new(MatchingMode::Normal, None);
self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink,
&mut MatchingContext::default(),
&mut context,
&mut |_, _| {})
}

View file

@ -26,10 +26,9 @@ use properties::PropertyDeclarationBlock;
use restyle_hints::{RestyleHint, DependencySet};
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
use selector_parser::{SelectorImpl, PseudoElement, SnapshotMap};
use selectors::Element;
use selectors::bloom::BloomFilter;
use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext};
use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode};
use selectors::parser::{AttrSelector, Combinator, Component, Selector, SelectorInner, SelectorIter};
use selectors::parser::{SelectorMethods, LocalName as LocalNameSelector};
use selectors::visitor::SelectorVisitor;
@ -479,9 +478,9 @@ impl Stylist {
rule: &Arc<Locked<StyleRule>>,
stylesheet: &Stylesheet)
{
let map = if let Some(ref pseudo_selector) = selector.pseudo_element {
let map = if let Some(pseudo) = selector.pseudo_element() {
self.pseudos_map
.entry(pseudo_selector.pseudo_element().clone())
.entry(pseudo.clone())
.or_insert_with(PerPseudoElementSelectorMap::new)
.borrow_for_origin(&stylesheet.origin)
} else {
@ -525,9 +524,6 @@ impl Stylist {
#[inline]
fn note_attribute_and_state_dependencies(&mut self, selector: &Selector<SelectorImpl>) {
if let Some(ref pseudo_selector) = selector.pseudo_element {
self.state_dependencies.insert(pseudo_selector.state());
}
selector.visit(&mut AttributeAndStateDependencyVisitor(self));
}
@ -635,14 +631,13 @@ impl Stylist {
guards: &StylesheetGuards,
element: &E,
pseudo: &PseudoElement,
pseudo_state: ElementState,
parent: &Arc<ComputedValues>,
font_metrics: &FontMetricsProvider)
-> Option<ComputedStyle>
where E: TElement,
{
let rule_node =
match self.lazy_pseudo_rules(guards, element, pseudo, pseudo_state) {
match self.lazy_pseudo_rules(guards, element, pseudo) {
Some(rule_node) => rule_node,
None => return None
};
@ -673,8 +668,7 @@ impl Stylist {
pub fn lazy_pseudo_rules<E>(&self,
guards: &StylesheetGuards,
element: &E,
pseudo: &PseudoElement,
pseudo_state: ElementState)
pseudo: &PseudoElement)
-> Option<StrongRuleNode>
where E: TElement
{
@ -708,14 +702,15 @@ impl Stylist {
};
let mut declarations = ApplicableDeclarationList::new();
let mut matching_context =
MatchingContext::new(MatchingMode::ForStatelessPseudoElement, None);
self.push_applicable_declarations(element,
None,
Some(pseudo),
None,
None,
AnimationRules(None, None),
Some((pseudo, pseudo_state)),
&mut declarations,
&mut MatchingContext::default(),
&mut matching_context,
&mut set_selector_flags);
if declarations.is_empty() {
return None
@ -839,16 +834,15 @@ impl Stylist {
pub fn push_applicable_declarations<E, V, F>(
&self,
element: &E,
parent_bf: Option<&BloomFilter>,
pseudo_element: Option<&PseudoElement>,
style_attribute: Option<&Arc<Locked<PropertyDeclarationBlock>>>,
smil_override: Option<&Arc<Locked<PropertyDeclarationBlock>>>,
animation_rules: AnimationRules,
pseudo_element: Option<(&PseudoElement, ElementState)>,
applicable_declarations: &mut V,
context: &mut MatchingContext,
flags_setter: &mut F)
where E: TElement,
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>,
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock> + ::std::fmt::Debug,
F: FnMut(&E, ElementSelectorFlags),
{
debug_assert!(!self.is_device_dirty);
@ -857,20 +851,31 @@ impl Stylist {
debug_assert!(cfg!(feature = "gecko") ||
style_attribute.is_none() || pseudo_element.is_none(),
"Style attributes do not apply to pseudo-elements");
debug_assert!(pseudo_element.as_ref().map_or(true, |p| !p.0.is_precomputed()));
debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed()));
let map = match pseudo_element {
Some((ref pseudo, _)) => self.pseudos_map.get(pseudo).unwrap(),
Some(pseudo) => self.pseudos_map.get(pseudo).unwrap(),
None => &self.element_map,
};
let is_implemented_pseudo =
element.implemented_pseudo_element().is_some();
// NB: This causes use to rule has pseudo selectors based on the
// properties of the originating element (which is fine, given the
// find_first_from_right usage).
let rule_hash_target = if is_implemented_pseudo {
element.closest_non_native_anonymous_ancestor().unwrap()
} else {
*element
};
debug!("Determining if style is shareable: pseudo: {}",
pseudo_element.is_some());
// Step 1: Normal user-agent rules.
map.user_agent.get_all_matching_rules(element,
pseudo_element,
parent_bf,
&rule_hash_target,
applicable_declarations,
context,
flags_setter,
@ -893,19 +898,25 @@ impl Stylist {
debug!("preshints: {:?}", context.relations);
}
if element.matches_user_and_author_rules() {
// NB: the following condition, although it may look somewhat
// inaccurate, would be equivalent to something like:
//
// element.matches_user_and_author_rules() ||
// (is_implemented_pseudo &&
// rule_hash_target.matches_user_and_author_rules())
//
// Which may be more what you would probably expect.
if rule_hash_target.matches_user_and_author_rules() {
// Step 3: User and author normal rules.
map.user.get_all_matching_rules(element,
pseudo_element,
parent_bf,
&rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::UserNormal);
debug!("user normal: {:?}", context.relations);
map.author.get_all_matching_rules(element,
pseudo_element,
parent_bf,
&rule_hash_target,
applicable_declarations,
context,
flags_setter,
@ -960,7 +971,6 @@ impl Stylist {
ApplicableDeclarationBlock::from_declarations(anim, CascadeLevel::Transitions));
}
debug!("transition: {:?}", context.relations);
debug!("push_applicable_declarations: shareable: {:?}", context.relations);
}
@ -993,6 +1003,11 @@ impl Stylist {
where E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
// NB: `MatchingMode` doesn't really matter, given we don't share style
// between pseudos.
let mut matching_context =
MatchingContext::new(MatchingMode::Normal, Some(bloom));
// Note that, by the time we're revalidating, we're guaranteed that the
// candidate and the entry have the same id, classes, and local name.
// This means we're guaranteed to get the same rulehash buckets for all
@ -1002,8 +1017,7 @@ impl Stylist {
self.selectors_for_cache_revalidation.lookup(*element, &mut |selector| {
results.push(matches_selector(selector,
element,
Some(bloom),
&mut MatchingContext::default(),
&mut matching_context,
flags_setter));
true
});
@ -1182,6 +1196,7 @@ pub fn needs_revalidation(selector: &Selector<SelectorImpl>) -> bool {
/// Map that contains the CSS rules for a specific PseudoElement
/// (or lack of PseudoElement).
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Debug)]
struct PerPseudoElementSelectorMap {
/// Rules from user agent stylesheets
user_agent: SelectorMap<Rule>,
@ -1283,13 +1298,12 @@ impl SelectorMap<Rule> {
/// Sort the Rules at the end to maintain cascading order.
pub fn get_all_matching_rules<E, V, F>(&self,
element: &E,
pseudo_element: Option<(&PseudoElement, ElementState)>,
parent_bf: Option<&BloomFilter>,
rule_hash_target: &E,
matching_rules_list: &mut V,
context: &mut MatchingContext,
flags_setter: &mut F,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
where E: TElement,
V: VecLike<ApplicableDeclarationBlock>,
F: FnMut(&E, ElementSelectorFlags),
{
@ -1299,10 +1313,8 @@ impl SelectorMap<Rule> {
// At the end, we're going to sort the rules that we added, so remember where we began.
let init_len = matching_rules_list.len();
if let Some(id) = element.get_id() {
if let Some(id) = rule_hash_target.get_id() {
SelectorMap::get_matching_rules_from_hash(element,
pseudo_element,
parent_bf,
&self.id_hash,
&id,
matching_rules_list,
@ -1311,10 +1323,8 @@ impl SelectorMap<Rule> {
cascade_level)
}
element.each_class(|class| {
rule_hash_target.each_class(|class| {
SelectorMap::get_matching_rules_from_hash(element,
pseudo_element,
parent_bf,
&self.class_hash,
class,
matching_rules_list,
@ -1324,18 +1334,14 @@ impl SelectorMap<Rule> {
});
SelectorMap::get_matching_rules_from_hash(element,
pseudo_element,
parent_bf,
&self.local_name_hash,
element.get_local_name(),
rule_hash_target.get_local_name(),
matching_rules_list,
context,
flags_setter,
cascade_level);
SelectorMap::get_matching_rules(element,
pseudo_element,
parent_bf,
&self.other,
matching_rules_list,
context,
@ -1372,15 +1378,13 @@ impl SelectorMap<Rule> {
fn get_matching_rules_from_hash<E, Str, BorrowedStr: ?Sized, Vector, F>(
element: &E,
pseudo_element: Option<(&PseudoElement, ElementState)>,
parent_bf: Option<&BloomFilter>,
hash: &FnvHashMap<Str, Vec<Rule>>,
key: &BorrowedStr,
matching_rules: &mut Vector,
context: &mut MatchingContext,
flags_setter: &mut F,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
where E: TElement,
Str: Borrow<BorrowedStr> + Eq + Hash,
BorrowedStr: Eq + Hash,
Vector: VecLike<ApplicableDeclarationBlock>,
@ -1388,8 +1392,6 @@ impl SelectorMap<Rule> {
{
if let Some(rules) = hash.get(key) {
SelectorMap::get_matching_rules(element,
pseudo_element,
parent_bf,
rules,
matching_rules,
context,
@ -1400,42 +1402,18 @@ impl SelectorMap<Rule> {
/// Adds rules in `rules` that match `element` to the `matching_rules` list.
fn get_matching_rules<E, V, F>(element: &E,
pseudo_element: Option<(&PseudoElement, ElementState)>,
parent_bf: Option<&BloomFilter>,
rules: &[Rule],
matching_rules: &mut V,
context: &mut MatchingContext,
flags_setter: &mut F,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
where E: TElement,
V: VecLike<ApplicableDeclarationBlock>,
F: FnMut(&E, ElementSelectorFlags),
{
for rule in rules.iter() {
debug_assert_eq!(rule.selector.pseudo_element.is_some(),
pseudo_element.is_some(),
"Testing pseudo-elements against the wrong map");
if let Some((pseudo, pseudo_state)) = pseudo_element {
let pseudo_selector =
rule.selector.pseudo_element.as_ref().unwrap();
debug_assert_eq!(pseudo_selector.pseudo_element(), pseudo,
"Testing pseudo-element against the wrong entry");
let state = pseudo_selector.state();
// NB: We only allow a subset of the flags here, so using
// contains for them is fine, (and it's necessary, to handle
// multiple state flags properly).
if !state.is_empty() && !pseudo_state.contains(state) {
continue;
}
}
for rule in rules {
if matches_selector(&rule.selector.inner,
element,
parent_bf,
context,
flags_setter) {
matching_rules.push(
@ -1593,45 +1571,70 @@ impl<T> SelectorMap<T> where T: Clone + Borrow<SelectorInner<SelectorImpl>> {
}
}
/// Searches the selector from right to left, beginning to the left of the
/// ::pseudo-element (if any), and ending at the first combinator.
///
/// The first non-None value returned from |f| is returned.
///
/// Effectively, pseudo-elements are ignored, given only state pseudo-classes
/// may appear before them.
fn find_from_right<F, R>(selector: &SelectorInner<SelectorImpl>, mut f: F) -> Option<R>
where F: FnMut(&Component<SelectorImpl>) -> Option<R>,
{
let mut iter = selector.complex.iter();
for ss in &mut iter {
if let Some(r) = f(ss) {
return Some(r)
}
}
if iter.next_sequence() == Some(Combinator::PseudoElement) {
for ss in &mut iter {
if let Some(r) = f(ss) {
return Some(r)
}
}
}
None
}
/// Retrieve the first ID name in the selector, or None otherwise.
pub fn get_id_name(selector: &SelectorInner<SelectorImpl>) -> Option<Atom> {
for ss in selector.complex.iter() {
find_from_right(selector, |ss| {
// TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode.
if let Component::ID(ref id) = *ss {
return Some(id.clone());
}
}
None
None
})
}
/// Retrieve the FIRST class name in the selector, or None otherwise.
pub fn get_class_name(selector: &SelectorInner<SelectorImpl>) -> Option<Atom> {
for ss in selector.complex.iter() {
find_from_right(selector, |ss| {
// TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode.
if let Component::Class(ref class) = *ss {
return Some(class.clone());
}
}
None
None
})
}
/// Retrieve the name if it is a type selector, or None otherwise.
pub fn get_local_name(selector: &SelectorInner<SelectorImpl>)
-> Option<LocalNameSelector<SelectorImpl>> {
for ss in selector.complex.iter() {
find_from_right(selector, |ss| {
if let Component::LocalName(ref n) = *ss {
return Some(LocalNameSelector {
name: n.name.clone(),
lower_name: n.lower_name.clone(),
})
}
}
None
None
})
}
/// A rule, that wraps a style rule, but represents a single selector of the
@ -1661,7 +1664,7 @@ impl Borrow<SelectorInner<SelectorImpl>> for Rule {
impl Rule {
/// Returns the specificity of the rule.
pub fn specificity(&self) -> u32 {
self.selector.specificity
self.selector.specificity()
}
fn to_applicable_declaration_block(&self, level: CascadeLevel) -> ApplicableDeclarationBlock {