Simplify rust-selectors API for attribute selectors

This commit is contained in:
Simon Sapin 2017-05-17 19:41:21 +02:00
parent 2ca2c2d2be
commit 83c7824fda
15 changed files with 447 additions and 480 deletions

View file

@ -12,6 +12,8 @@ use cssparser::{self, Color, RGBA};
use euclid::num::Zero;
use num_traits::ToPrimitive;
use properties::PropertyDeclarationBlock;
use selector_parser::SelectorImpl;
use selectors::attr::AttrSelectorOperation;
use servo_url::ServoUrl;
use shared_lock::Locked;
use std::ascii::AsciiExt;
@ -349,6 +351,12 @@ impl AttrValue {
panic!("Uint not found");
}
}
pub fn eval_selector(&self, selector: &AttrSelectorOperation<SelectorImpl>) -> bool {
// FIXME(SimonSapin) this can be more efficient by matching on `(self, selector)` variants
// and doing Atom comparisons instead of string comparisons where possible.
selector.eval_str(self)
}
}
impl ::std::ops::Deref for AttrValue {

View file

@ -8,14 +8,15 @@
use dom::TElement;
use element_state::ElementState;
use gecko::snapshot_helpers;
use gecko::wrapper::{AttrSelectorHelpers, GeckoElement};
use gecko::wrapper::{NamespaceConstraintHelpers, GeckoElement};
use gecko_bindings::bindings;
use gecko_bindings::structs::ServoElementSnapshot;
use gecko_bindings::structs::ServoElementSnapshotFlags as Flags;
use gecko_bindings::structs::ServoElementSnapshotTable;
use restyle_hints::ElementSnapshot;
use selector_parser::SelectorImpl;
use selectors::parser::AttrSelector;
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity};
use selectors::parser::NamespaceConstraint;
use string_cache::Atom;
/// A snapshot of a Gecko element.
@ -47,11 +48,6 @@ impl SnapshotMap {
}
impl GeckoElementSnapshot {
#[inline]
fn is_html_element_in_html_document(&self) -> bool {
self.mIsHTMLElementInHTMLDocument
}
#[inline]
fn has_any(&self, flags: Flags) -> bool {
(self.mContains as u8 & flags as u8) != 0
@ -60,76 +56,67 @@ impl GeckoElementSnapshot {
fn as_ptr(&self) -> *const Self {
self
}
}
impl ::selectors::MatchAttr for GeckoElementSnapshot {
type Impl = SelectorImpl;
fn match_attr_has(&self, attr: &AttrSelector<SelectorImpl>) -> bool {
/// selectors::Element::attr_matches
pub fn attr_matches(&self,
ns: &NamespaceConstraint<SelectorImpl>,
local_name: &Atom,
operation: &AttrSelectorOperation<SelectorImpl>)
-> bool {
unsafe {
bindings::Gecko_SnapshotHasAttr(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()))
}
}
fn match_attr_equals(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrEquals(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr(),
/* ignoreCase = */ false)
}
}
fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrEquals(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr(),
/* ignoreCase = */ true)
}
}
fn match_attr_includes(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrIncludes(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
}
}
fn match_attr_dash(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrDashEquals(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
}
}
fn match_attr_prefix(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrHasPrefix(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
}
}
fn match_attr_substring(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrHasSubstring(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
}
}
fn match_attr_suffix(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_SnapshotAttrHasSuffix(self,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
match *operation {
AttrSelectorOperation::Exists => {
bindings:: Gecko_SnapshotHasAttr(self,
ns.atom_or_null(),
local_name.as_ptr())
}
AttrSelectorOperation::WithValue { operator, case_sensitivity, expected_value } => {
let ignore_case = match case_sensitivity {
CaseSensitivity::CaseSensitive => false,
CaseSensitivity::AsciiCaseInsensitive => true,
};
// FIXME: case sensitivity for operators other than Equal
match operator {
AttrSelectorOperator::Equal => bindings::Gecko_SnapshotAttrEquals(
self,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
ignore_case
),
AttrSelectorOperator::Includes => bindings::Gecko_SnapshotAttrIncludes(
self,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
),
AttrSelectorOperator::DashMatch => bindings::Gecko_SnapshotAttrDashEquals(
self,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
),
AttrSelectorOperator::Prefix => bindings::Gecko_SnapshotAttrHasPrefix(
self,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
),
AttrSelectorOperator::Suffix => bindings::Gecko_SnapshotAttrHasSuffix(
self,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
),
AttrSelectorOperator::Substring => bindings::Gecko_SnapshotAttrHasSubstring(
self,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
),
}
}
}
}
}
}

View file

@ -64,8 +64,9 @@ use properties::style_structs::Font;
use rule_tree::CascadeLevel as ServoCascadeLevel;
use selector_parser::ElementExt;
use selectors::Element;
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
use selectors::parser::{AttrSelector, NamespaceConstraint};
use selectors::parser::NamespaceConstraint;
use shared_lock::Locked;
use sink::Push;
use std::cell::RefCell;
@ -1082,6 +1083,8 @@ impl<'le> PresentationalHintsSynthesizer for GeckoElement<'le> {
}
impl<'le> ::selectors::Element for GeckoElement<'le> {
type Impl = SelectorImpl;
fn parent_element(&self) -> Option<Self> {
let parent_node = self.as_node().parent_node();
parent_node.and_then(|n| n.as_element())
@ -1136,6 +1139,68 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
None
}
fn attr_matches(&self,
ns: &NamespaceConstraint<SelectorImpl>,
local_name: &Atom,
operation: &AttrSelectorOperation<SelectorImpl>)
-> bool {
unsafe {
match *operation {
AttrSelectorOperation::Exists => {
bindings::Gecko_HasAttr(self.0,
ns.atom_or_null(),
local_name.as_ptr())
}
AttrSelectorOperation::WithValue { operator, case_sensitivity, expected_value } => {
let ignore_case = match case_sensitivity {
CaseSensitivity::CaseSensitive => false,
CaseSensitivity::AsciiCaseInsensitive => true,
};
// FIXME: case sensitivity for operators other than Equal
match operator {
AttrSelectorOperator::Equal => bindings::Gecko_AttrEquals(
self.0,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
ignore_case
),
AttrSelectorOperator::Includes => bindings::Gecko_AttrIncludes(
self.0,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
),
AttrSelectorOperator::DashMatch => bindings::Gecko_AttrDashEquals(
self.0,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
),
AttrSelectorOperator::Prefix => bindings::Gecko_AttrHasPrefix(
self.0,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
),
AttrSelectorOperator::Suffix => bindings::Gecko_AttrHasSuffix(
self.0,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
),
AttrSelectorOperator::Substring => bindings::Gecko_AttrHasSubstring(
self.0,
ns.atom_or_null(),
local_name.as_ptr(),
expected_value.as_ptr(),
),
}
}
}
}
}
fn is_root(&self) -> bool {
unsafe {
Gecko_IsRootElement(self.0)
@ -1343,99 +1408,18 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
}
/// A few helpers to help with attribute selectors and snapshotting.
pub trait AttrSelectorHelpers {
pub trait NamespaceConstraintHelpers {
/// Returns the namespace of the selector, or null otherwise.
fn ns_or_null(&self) -> *mut nsIAtom;
/// Returns the proper selector name depending on whether the requesting
/// element is an HTML element in an HTML document or not.
fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom;
fn atom_or_null(&self) -> *mut nsIAtom;
}
impl AttrSelectorHelpers for AttrSelector<SelectorImpl> {
fn ns_or_null(&self) -> *mut nsIAtom {
match self.namespace {
impl NamespaceConstraintHelpers for NamespaceConstraint<SelectorImpl> {
fn atom_or_null(&self) -> *mut nsIAtom {
match *self {
NamespaceConstraint::Any => ptr::null_mut(),
NamespaceConstraint::Specific(ref ns) => ns.url.0.as_ptr(),
}
}
fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom {
if is_html_element_in_html_document {
self.lower_name.as_ptr()
} else {
self.name.as_ptr()
}
}
}
impl<'le> ::selectors::MatchAttr for GeckoElement<'le> {
type Impl = SelectorImpl;
fn match_attr_has(&self, attr: &AttrSelector<Self::Impl>) -> bool {
unsafe {
bindings::Gecko_HasAttr(self.0,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()))
}
}
fn match_attr_equals(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_AttrEquals(self.0,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr(),
/* ignoreCase = */ false)
}
}
fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_AttrEquals(self.0,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr(),
/* ignoreCase = */ true)
}
}
fn match_attr_includes(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_AttrIncludes(self.0,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
}
}
fn match_attr_dash(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_AttrDashEquals(self.0,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
}
}
fn match_attr_prefix(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_AttrHasPrefix(self.0,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
}
}
fn match_attr_substring(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_AttrHasSubstring(self.0,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
}
}
fn match_attr_suffix(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
unsafe {
bindings::Gecko_AttrHasSuffix(self.0,
attr.ns_or_null(),
attr.select_name(self.is_html_element_in_html_document()),
value.as_ptr())
}
}
}
impl<'le> ElementExt for GeckoElement<'le> {

View file

@ -7,18 +7,20 @@
#![deny(missing_docs)]
use Atom;
use LocalName;
use dom::TElement;
use element_state::*;
#[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 selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap};
use selectors::Element;
use selectors::attr::AttrSelectorOperation;
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
use selectors::matching::matches_selector;
use selectors::parser::{AttrSelector, Combinator, Component, Selector};
use selectors::parser::{SelectorInner, SelectorMethods};
use selectors::parser::{Combinator, Component, Selector};
use selectors::parser::{SelectorInner, SelectorMethods, NamespaceConstraint};
use selectors::visitor::SelectorVisitor;
use smallvec::SmallVec;
use std::borrow::Borrow;
@ -170,7 +172,7 @@ impl HeapSizeOf for RestyleHint {
/// still need to take the ElementWrapper approach for attribute-dependent
/// style. So we do it the same both ways for now to reduce complexity, but it's
/// worth measuring the performance impact (if any) of the mStateMask approach.
pub trait ElementSnapshot : Sized + MatchAttr<Impl=SelectorImpl> {
pub trait ElementSnapshot : Sized {
/// The state of the snapshot, if any.
fn state(&self) -> Option<ElementState>;
@ -242,90 +244,6 @@ impl<'a, E> ElementWrapper<'a, E>
}
}
impl<'a, E> MatchAttr for ElementWrapper<'a, E>
where E: TElement,
{
type Impl = SelectorImpl;
fn match_attr_has(&self, attr: &AttrSelector<SelectorImpl>) -> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_has(attr),
_ => self.element.match_attr_has(attr)
}
}
fn match_attr_equals(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_equals(attr, value),
_ => self.element.match_attr_equals(attr, value)
}
}
fn match_attr_equals_ignore_ascii_case(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_equals_ignore_ascii_case(attr, value),
_ => self.element.match_attr_equals_ignore_ascii_case(attr, value)
}
}
fn match_attr_includes(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_includes(attr, value),
_ => self.element.match_attr_includes(attr, value)
}
}
fn match_attr_dash(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_dash(attr, value),
_ => self.element.match_attr_dash(attr, value)
}
}
fn match_attr_prefix(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_prefix(attr, value),
_ => self.element.match_attr_prefix(attr, value)
}
}
fn match_attr_substring(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_substring(attr, value),
_ => self.element.match_attr_substring(attr, value)
}
}
fn match_attr_suffix(&self,
attr: &AttrSelector<SelectorImpl>,
value: &AttrValue) -> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()
=> snapshot.match_attr_suffix(attr, value),
_ => self.element.match_attr_suffix(attr, value)
}
}
}
#[cfg(feature = "gecko")]
fn dir_selector_to_state(s: &[u16]) -> ElementState {
// Jump through some hoops to deal with our Box<[u16]> thing.
@ -345,6 +263,8 @@ fn dir_selector_to_state(s: &[u16]) -> ElementState {
impl<'a, E> Element for ElementWrapper<'a, E>
where E: TElement,
{
type Impl = SelectorImpl;
fn match_non_ts_pseudo_class<F>(&self,
pseudo_class: &NonTSPseudoClass,
context: &mut MatchingContext,
@ -450,6 +370,19 @@ impl<'a, E> Element for ElementWrapper<'a, E>
self.element.get_namespace()
}
fn attr_matches(&self,
ns: &NamespaceConstraint<Self::Impl>,
local_name: &LocalName,
operation: &AttrSelectorOperation<Self::Impl>)
-> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs() => {
snapshot.attr_matches(ns, local_name, operation)
}
_ => self.element.attr_matches(ns, local_name, operation)
}
}
fn get_id(&self) -> Option<Atom> {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs()

View file

@ -14,9 +14,10 @@ use element_state::ElementState;
use fnv::FnvHashMap;
use restyle_hints::ElementSnapshot;
use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser};
use selectors::{Element, MatchAttrGeneric};
use selectors::{Element};
use selectors::attr::AttrSelectorOperation;
use selectors::matching::{MatchingContext, MatchingMode};
use selectors::parser::{AttrSelector, SelectorMethods};
use selectors::parser::{SelectorMethods, NamespaceConstraint};
use selectors::visitor::SelectorVisitor;
use std::borrow::Cow;
use std::fmt;
@ -519,10 +520,10 @@ impl ServoElementSnapshot {
.map(|&(_, ref v)| v)
}
fn get_attr_ignore_ns(&self, name: &LocalName) -> Option<&AttrValue> {
fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool
where F: FnMut(&AttrValue) -> bool {
self.attrs.as_ref().unwrap().iter()
.find(|&&(ref ident, _)| ident.local_name == *name)
.map(|&(_, ref v)| v)
.any(|&(ref ident, ref v)| ident.local_name == *name && f(v))
}
}
@ -555,19 +556,23 @@ impl ElementSnapshot for ServoElementSnapshot {
}
}
impl MatchAttrGeneric for ServoElementSnapshot {
type Impl = SelectorImpl;
fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool
where F: Fn(&str) -> bool
{
impl ServoElementSnapshot {
/// selectors::Element::attr_matches
pub fn attr_matches(&self,
ns: &NamespaceConstraint<SelectorImpl>,
local_name: &LocalName,
operation: &AttrSelectorOperation<SelectorImpl>)
-> bool {
use selectors::parser::NamespaceConstraint;
let html = self.is_html_element_in_html_document;
let local_name = if html { &attr.lower_name } else { &attr.name };
match attr.namespace {
NamespaceConstraint::Specific(ref ns) => self.get_attr(&ns.url, local_name),
NamespaceConstraint::Any => self.get_attr_ignore_ns(local_name),
}.map_or(false, |v| test(v))
match *ns {
NamespaceConstraint::Specific(ref ns) => {
self.get_attr(&ns.url, local_name)
.map_or(false, |value| value.eval_selector(operation))
}
NamespaceConstraint::Any => {
self.any_attr_ignore_ns(local_name, |value| value.eval_selector(operation))
}
}
}
}