diff options
-rw-r--r-- | components/script/layout_wrapper.rs | 42 | ||||
-rw-r--r-- | components/style/dom.rs | 17 | ||||
-rw-r--r-- | components/style/gecko/snapshot.rs | 10 | ||||
-rw-r--r-- | components/style/gecko/wrapper.rs | 38 | ||||
-rw-r--r-- | components/style/restyle_hints.rs | 30 | ||||
-rw-r--r-- | components/style/servo/selector_parser.rs | 8 |
6 files changed, 134 insertions, 11 deletions
diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index 9ce38c6a187..f7f5396dc8d 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -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")) { diff --git a/components/style/dom.rs b/components/style/dom.rs index 57011643301..64e11b2faf0 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -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. diff --git a/components/style/gecko/snapshot.rs b/components/style/gecko/snapshot.rs index 4c8fe00e602..d238151fd9a 100644 --- a/components/style/gecko/snapshot.rs +++ b/components/style/gecko/snapshot.rs @@ -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) }) + } + } } diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index d9baeb79312..cc4411ffaf5 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -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, diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index 44eec13f13c..e89ef8dfef7 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -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); + } + _ => {} } diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index b754a35462d..43d3d5dffa0 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -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 { |