mirror of
https://github.com/servo/servo.git
synced 2025-07-30 18:50:36 +01:00
style: Match :lang() using snapshots correctly.
This commit is contained in:
parent
c7e2500311
commit
f492c8fe6e
6 changed files with 134 additions and 11 deletions
|
@ -69,7 +69,8 @@ use style::dom::{PresentationalHintsSynthesizer, TElement, TNode, UnsafeNode};
|
|||
use style::element_state::*;
|
||||
use style::font_metrics::ServoMetricsProvider;
|
||||
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::sink::Push;
|
||||
use style::str::is_whitespace;
|
||||
|
@ -498,6 +499,39 @@ impl<'le> TElement for ServoLayoutElement<'le> {
|
|||
fn has_css_transitions(&self) -> bool {
|
||||
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> {
|
||||
|
@ -690,11 +724,7 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> {
|
|||
NonTSPseudoClass::AnyLink => self.is_link(),
|
||||
NonTSPseudoClass::Visited => false,
|
||||
|
||||
// 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.
|
||||
NonTSPseudoClass::Lang(ref lang) => extended_filtering(&*self.element.get_lang_for_layout(), &*lang),
|
||||
NonTSPseudoClass::Lang(ref lang) => self.match_element_lang(None, &*lang),
|
||||
|
||||
NonTSPseudoClass::ServoNonZeroBorder => unsafe {
|
||||
match (*self.element.unsafe_get()).get_attr_for_layout(&ns!(), &local_name!("border")) {
|
||||
|
|
|
@ -17,7 +17,8 @@ use properties::{ComputedValues, PropertyDeclarationBlock};
|
|||
#[cfg(feature = "gecko")] use properties::animated_properties::AnimationValue;
|
||||
#[cfg(feature = "gecko")] use properties::animated_properties::TransitionProperty;
|
||||
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 shared_lock::Locked;
|
||||
use sink::Push;
|
||||
|
@ -597,6 +598,20 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
|
|||
existing_transitions: &HashMap<TransitionProperty,
|
||||
Arc<AnimationValue>>)
|
||||
-> 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.
|
||||
|
|
|
@ -181,4 +181,14 @@ impl ElementSnapshot for GeckoElementSnapshot {
|
|||
callback,
|
||||
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) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ use gecko_bindings::bindings::Gecko_GetStyleContext;
|
|||
use gecko_bindings::bindings::Gecko_GetUnvisitedLinkAttrDeclarationBlock;
|
||||
use gecko_bindings::bindings::Gecko_GetVisitedLinkAttrDeclarationBlock;
|
||||
use gecko_bindings::bindings::Gecko_IsSignificantChild;
|
||||
use gecko_bindings::bindings::Gecko_MatchLang;
|
||||
use gecko_bindings::bindings::Gecko_MatchStringArgPseudo;
|
||||
use gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr;
|
||||
use gecko_bindings::bindings::Gecko_UpdateAnimations;
|
||||
|
@ -66,7 +67,7 @@ use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
|
|||
use properties::animated_properties::{AnimationValue, AnimationValueMap, TransitionProperty};
|
||||
use properties::style_structs::Font;
|
||||
use rule_tree::CascadeLevel as ServoCascadeLevel;
|
||||
use selector_parser::ElementExt;
|
||||
use selector_parser::{AttrValue, ElementExt, PseudoClassStringArg};
|
||||
use selectors::Element;
|
||||
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
|
||||
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
|
||||
|
@ -1023,6 +1024,35 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
before_change_style,
|
||||
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> {
|
||||
|
@ -1408,11 +1438,13 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
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::MozLocaleDir(ref s) |
|
||||
NonTSPseudoClass::MozEmptyExceptChildrenWithLocalname(ref s) |
|
||||
NonTSPseudoClass::Dir(ref s) |
|
||||
NonTSPseudoClass::Lang(ref s) => {
|
||||
NonTSPseudoClass::Dir(ref s) => {
|
||||
unsafe {
|
||||
let mut set_slow_selector = false;
|
||||
let matches = Gecko_MatchStringArgPseudo(self.0,
|
||||
|
|
|
@ -555,8 +555,12 @@ pub trait ElementSnapshot : Sized {
|
|||
/// only be called if `has_attrs()` returns true.
|
||||
fn each_class<F>(&self, F)
|
||||
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>
|
||||
where E: TElement,
|
||||
{
|
||||
|
@ -606,6 +610,26 @@ impl<'a, E> ElementWrapper<'a, E>
|
|||
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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ use dom::{OpaqueNode, TElement, TNode};
|
|||
use element_state::ElementState;
|
||||
use fnv::FnvHashMap;
|
||||
use restyle_hints::ElementSnapshot;
|
||||
use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser};
|
||||
use selector_parser::{AttrValue as SelectorAttrValue, ElementExt, PseudoElementCascadeType, SelectorParser};
|
||||
use selectors::Element;
|
||||
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
|
||||
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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue