style: Match :lang() using snapshots correctly.

This commit is contained in:
Cameron McCormack 2017-06-07 12:30:10 +08:00
parent c7e2500311
commit f492c8fe6e
6 changed files with 134 additions and 11 deletions

View file

@ -69,7 +69,8 @@ use style::dom::{PresentationalHintsSynthesizer, TElement, TNode, UnsafeNode};
use style::element_state::*; use style::element_state::*;
use style::font_metrics::ServoMetricsProvider; use style::font_metrics::ServoMetricsProvider;
use style::properties::{ComputedValues, PropertyDeclarationBlock}; use style::properties::{ComputedValues, PropertyDeclarationBlock};
use style::selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, extended_filtering}; use style::selector_parser::{AttrValue as SelectorAttrValue, NonTSPseudoClass, PseudoClassStringArg};
use style::selector_parser::{PseudoElement, SelectorImpl, extended_filtering};
use style::shared_lock::{SharedRwLock as StyleSharedRwLock, Locked as StyleLocked}; use style::shared_lock::{SharedRwLock as StyleSharedRwLock, Locked as StyleLocked};
use style::sink::Push; use style::sink::Push;
use style::str::is_whitespace; use style::str::is_whitespace;
@ -498,6 +499,39 @@ impl<'le> TElement for ServoLayoutElement<'le> {
fn has_css_transitions(&self) -> bool { fn has_css_transitions(&self) -> bool {
unreachable!("this should be only called on gecko"); unreachable!("this should be only called on gecko");
} }
#[inline]
fn lang_attr(&self) -> Option<SelectorAttrValue> {
self.get_attr(&ns!(xml), &local_name!("lang"))
.or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
.map(|v| String::from(v as &str))
}
fn match_element_lang(&self,
override_lang: Option<Option<SelectorAttrValue>>,
value: &PseudoClassStringArg)
-> bool
{
// Servo supports :lang() from CSS Selectors 4, which can take a comma-
// separated list of language tags in the pseudo-class, and which
// performs RFC 4647 extended filtering matching on them.
//
// FIXME(heycam): This is wrong, since extended_filtering accepts
// a string containing commas (separating each language tag in
// a list) but the pseudo-class instead should be parsing and
// storing separate <ident> or <string>s for each language tag.
//
// FIXME(heycam): Look at `element`'s document's Content-Language
// HTTP header for language tags to match `value` against. To
// do this, we should make `get_lang_for_layout` return an Option,
// so we can decide when to fall back to the Content-Language check.
let element_lang = match override_lang {
Some(Some(lang)) => lang,
Some(None) => String::new(),
None => self.element.get_lang_for_layout(),
};
extended_filtering(&element_lang, &*value)
}
} }
impl<'le> PartialEq for ServoLayoutElement<'le> { impl<'le> PartialEq for ServoLayoutElement<'le> {
@ -690,11 +724,7 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> {
NonTSPseudoClass::AnyLink => self.is_link(), NonTSPseudoClass::AnyLink => self.is_link(),
NonTSPseudoClass::Visited => false, NonTSPseudoClass::Visited => false,
// FIXME(heycam): This is wrong, since extended_filtering accepts NonTSPseudoClass::Lang(ref lang) => self.match_element_lang(None, &*lang),
// a string containing commas (separating each language tag in
// a list) but the pseudo-class instead should be parsing and
// storing separate <ident> or <string>s for each language tag.
NonTSPseudoClass::Lang(ref lang) => extended_filtering(&*self.element.get_lang_for_layout(), &*lang),
NonTSPseudoClass::ServoNonZeroBorder => unsafe { NonTSPseudoClass::ServoNonZeroBorder => unsafe {
match (*self.element.unsafe_get()).get_attr_for_layout(&ns!(), &local_name!("border")) { match (*self.element.unsafe_get()).get_attr_for_layout(&ns!(), &local_name!("border")) {

View file

@ -17,7 +17,8 @@ use properties::{ComputedValues, PropertyDeclarationBlock};
#[cfg(feature = "gecko")] use properties::animated_properties::AnimationValue; #[cfg(feature = "gecko")] use properties::animated_properties::AnimationValue;
#[cfg(feature = "gecko")] use properties::animated_properties::TransitionProperty; #[cfg(feature = "gecko")] use properties::animated_properties::TransitionProperty;
use rule_tree::CascadeLevel; use rule_tree::CascadeLevel;
use selector_parser::{ElementExt, PreExistingComputedValues, PseudoElement}; use selector_parser::{AttrValue, ElementExt, PreExistingComputedValues};
use selector_parser::{PseudoClassStringArg, PseudoElement};
use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode}; use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
use shared_lock::Locked; use shared_lock::Locked;
use sink::Push; use sink::Push;
@ -597,6 +598,20 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
existing_transitions: &HashMap<TransitionProperty, existing_transitions: &HashMap<TransitionProperty,
Arc<AnimationValue>>) Arc<AnimationValue>>)
-> bool; -> bool;
/// Returns the value of the `xml:lang=""` attribute (or, if appropriate,
/// the `lang=""` attribute) on this element.
fn lang_attr(&self) -> Option<AttrValue>;
/// Returns whether this element's language matches the language tag
/// `value`. If `override_lang` is not `None`, it specifies the value
/// of the `xml:lang=""` or `lang=""` attribute to use in place of
/// looking at the element and its ancestors. (This argument is used
/// to implement matching of `:lang()` against snapshots.)
fn match_element_lang(&self,
override_lang: Option<Option<AttrValue>>,
value: &PseudoClassStringArg)
-> bool;
} }
/// Trait abstracting over different kinds of dirty-descendants bits. /// Trait abstracting over different kinds of dirty-descendants bits.

View file

@ -181,4 +181,14 @@ impl ElementSnapshot for GeckoElementSnapshot {
callback, callback,
bindings::Gecko_SnapshotClassOrClassList) bindings::Gecko_SnapshotClassOrClassList)
} }
#[inline]
fn lang_attr(&self) -> Option<Atom> {
let ptr = unsafe { bindings::Gecko_SnapshotLangValue(self) };
if ptr.is_null() {
None
} else {
Some(unsafe { Atom::from_addrefed(ptr) })
}
}
} }

View file

@ -45,6 +45,7 @@ use gecko_bindings::bindings::Gecko_GetStyleContext;
use gecko_bindings::bindings::Gecko_GetUnvisitedLinkAttrDeclarationBlock; use gecko_bindings::bindings::Gecko_GetUnvisitedLinkAttrDeclarationBlock;
use gecko_bindings::bindings::Gecko_GetVisitedLinkAttrDeclarationBlock; use gecko_bindings::bindings::Gecko_GetVisitedLinkAttrDeclarationBlock;
use gecko_bindings::bindings::Gecko_IsSignificantChild; use gecko_bindings::bindings::Gecko_IsSignificantChild;
use gecko_bindings::bindings::Gecko_MatchLang;
use gecko_bindings::bindings::Gecko_MatchStringArgPseudo; use gecko_bindings::bindings::Gecko_MatchStringArgPseudo;
use gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr; use gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr;
use gecko_bindings::bindings::Gecko_UpdateAnimations; use gecko_bindings::bindings::Gecko_UpdateAnimations;
@ -66,7 +67,7 @@ use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
use properties::animated_properties::{AnimationValue, AnimationValueMap, TransitionProperty}; use properties::animated_properties::{AnimationValue, AnimationValueMap, TransitionProperty};
use properties::style_structs::Font; use properties::style_structs::Font;
use rule_tree::CascadeLevel as ServoCascadeLevel; use rule_tree::CascadeLevel as ServoCascadeLevel;
use selector_parser::ElementExt; use selector_parser::{AttrValue, ElementExt, PseudoClassStringArg};
use selectors::Element; use selectors::Element;
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint}; use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
@ -1023,6 +1024,35 @@ impl<'le> TElement for GeckoElement<'le> {
before_change_style, before_change_style,
after_change_style).does_animate() after_change_style).does_animate()
} }
#[inline]
fn lang_attr(&self) -> Option<AttrValue> {
let ptr = unsafe { bindings::Gecko_LangValue(self.0) };
if ptr.is_null() {
None
} else {
Some(unsafe { Atom::from_addrefed(ptr) })
}
}
fn match_element_lang(&self,
override_lang: Option<Option<AttrValue>>,
value: &PseudoClassStringArg)
-> bool
{
// Gecko supports :lang() from CSS Selectors 3, which only accepts a
// single language tag, and which performs simple dash-prefix matching
// on it.
debug_assert!(value.len() > 0 && value[value.len() - 1] == 0,
"expected value to be null terminated");
let override_lang_ptr = match &override_lang {
&Some(Some(ref atom)) => atom.as_ptr(),
_ => ptr::null_mut(),
};
unsafe {
Gecko_MatchLang(self.0, override_lang_ptr, override_lang.is_some(), value.as_ptr())
}
}
} }
impl<'le> PartialEq for GeckoElement<'le> { impl<'le> PartialEq for GeckoElement<'le> {
@ -1408,11 +1438,13 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
matches_complex_selector(s, 0, self, context, flags_setter) matches_complex_selector(s, 0, self, context, flags_setter)
}) })
} }
NonTSPseudoClass::Lang(ref lang_arg) => {
self.match_element_lang(None, lang_arg)
}
NonTSPseudoClass::MozSystemMetric(ref s) | NonTSPseudoClass::MozSystemMetric(ref s) |
NonTSPseudoClass::MozLocaleDir(ref s) | NonTSPseudoClass::MozLocaleDir(ref s) |
NonTSPseudoClass::MozEmptyExceptChildrenWithLocalname(ref s) | NonTSPseudoClass::MozEmptyExceptChildrenWithLocalname(ref s) |
NonTSPseudoClass::Dir(ref s) | NonTSPseudoClass::Dir(ref s) => {
NonTSPseudoClass::Lang(ref s) => {
unsafe { unsafe {
let mut set_slow_selector = false; let mut set_slow_selector = false;
let matches = Gecko_MatchStringArgPseudo(self.0, let matches = Gecko_MatchStringArgPseudo(self.0,

View file

@ -555,8 +555,12 @@ pub trait ElementSnapshot : Sized {
/// only be called if `has_attrs()` returns true. /// only be called if `has_attrs()` returns true.
fn each_class<F>(&self, F) fn each_class<F>(&self, F)
where F: FnMut(&Atom); where F: FnMut(&Atom);
/// The `xml:lang=""` or `lang=""` attribute value per this snapshot.
fn lang_attr(&self) -> Option<AttrValue>;
} }
#[derive(Clone)]
struct ElementWrapper<'a, E> struct ElementWrapper<'a, E>
where E: TElement, where E: TElement,
{ {
@ -606,6 +610,26 @@ impl<'a, E> ElementWrapper<'a, E>
None => ElementState::empty(), None => ElementState::empty(),
} }
} }
/// Returns the value of the `xml:lang=""` (or, if appropriate, `lang=""`)
/// attribute from this element's snapshot or the closest ancestor
/// element snapshot with the attribute specified.
fn get_lang(&self) -> Option<AttrValue> {
let mut current = self.clone();
loop {
let lang = match self.snapshot() {
Some(snapshot) if snapshot.has_attrs() => snapshot.lang_attr(),
_ => current.element.lang_attr(),
};
if lang.is_some() {
return lang;
}
match current.parent_element() {
Some(parent) => current = parent,
None => return None,
}
}
}
} }
impl<'a, E> fmt::Debug for ElementWrapper<'a, E> impl<'a, E> fmt::Debug for ElementWrapper<'a, E>
@ -707,6 +731,12 @@ impl<'a, E> Element for ElementWrapper<'a, E>
} }
} }
// :lang() needs to match using the closest ancestor xml:lang="" or
// lang="" attribtue from snapshots.
NonTSPseudoClass::Lang(ref lang_arg) => {
return self.element.match_element_lang(Some(self.get_lang()), lang_arg);
}
_ => {} _ => {}
} }

View file

@ -13,7 +13,7 @@ use dom::{OpaqueNode, TElement, TNode};
use element_state::ElementState; use element_state::ElementState;
use fnv::FnvHashMap; use fnv::FnvHashMap;
use restyle_hints::ElementSnapshot; use restyle_hints::ElementSnapshot;
use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser}; use selector_parser::{AttrValue as SelectorAttrValue, ElementExt, PseudoElementCascadeType, SelectorParser};
use selectors::Element; use selectors::Element;
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
use selectors::parser::SelectorMethods; use selectors::parser::SelectorMethods;
@ -588,6 +588,12 @@ impl ElementSnapshot for ServoElementSnapshot {
} }
} }
} }
fn lang_attr(&self) -> Option<SelectorAttrValue> {
self.get_attr(&ns!(xml), &local_name!("lang"))
.or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
.map(|v| String::from(v as &str))
}
} }
impl ServoElementSnapshot { impl ServoElementSnapshot {