diff --git a/components/script/dom/bindings/str.rs b/components/script/dom/bindings/str.rs index 6cb17fae4e3..4e579044491 100644 --- a/components/script/dom/bindings/str.rs +++ b/components/script/dom/bindings/str.rs @@ -113,63 +113,6 @@ pub fn is_token(s: &[u8]) -> bool { }) } -/// Returns whether the language is matched, as defined by -/// [RFC 4647](https://tools.ietf.org/html/rfc4647#section-3.3.2). -pub fn extended_filtering(tag: &str, range: &str) -> bool { - let lang_ranges: Vec<&str> = range.split(',').collect(); - - lang_ranges.iter().any(|&lang_range| { - // step 1 - let range_subtags: Vec<&str> = lang_range.split('\x2d').collect(); - let tag_subtags: Vec<&str> = tag.split('\x2d').collect(); - - let mut range_iter = range_subtags.iter(); - let mut tag_iter = tag_subtags.iter(); - - // step 2 - // Note: [Level-4 spec](https://drafts.csswg.org/selectors/#lang-pseudo) check for wild card - if let (Some(range_subtag), Some(tag_subtag)) = (range_iter.next(), tag_iter.next()) { - if !(range_subtag.eq_ignore_ascii_case(tag_subtag) || range_subtag.eq_ignore_ascii_case("*")) { - return false; - } - } - - let mut current_tag_subtag = tag_iter.next(); - - // step 3 - for range_subtag in range_iter { - // step 3a - if range_subtag.eq_ignore_ascii_case("*") { - continue; - } - match current_tag_subtag.clone() { - Some(tag_subtag) => { - // step 3c - if range_subtag.eq_ignore_ascii_case(tag_subtag) { - current_tag_subtag = tag_iter.next(); - continue; - } else { - // step 3d - if tag_subtag.len() == 1 { - return false; - } else { - // else step 3e - continue with loop - current_tag_subtag = tag_iter.next(); - if current_tag_subtag.is_none() { - return false; - } - } - } - }, - // step 3b - None => { return false; } - } - } - // step 4 - true - }) -} - /// A DOMString. /// diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index c0d4b6b700a..cb882f53463 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -26,7 +26,7 @@ use dom::bindings::js::{JS, LayoutJS, MutNullableJS}; use dom::bindings::js::{Root, RootedReference}; use dom::bindings::refcounted::{Trusted, TrustedPromise}; use dom::bindings::reflector::DomObject; -use dom::bindings::str::{DOMString, extended_filtering}; +use dom::bindings::str::DOMString; use dom::bindings::xmlname::{namespace_from_domstring, validate_and_extract, xml_name_type}; use dom::bindings::xmlname::XMLName::InvalidXMLName; use dom::characterdata::CharacterData; @@ -106,6 +106,7 @@ use style::properties::longhands::{self, background_image, border_spacing, font_ use style::restyle_hints::RestyleHint; use style::rule_tree::CascadeLevel; use style::selector_parser::{NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser}; +use style::selector_parser::extended_filtering; use style::shared_lock::{SharedRwLock, Locked}; use style::sink::Push; use style::stylearc::Arc; @@ -2464,8 +2465,10 @@ impl<'a> ::selectors::Element for Root { .map_or(false, |attr| attr.value().eq(expected_value)) } - // FIXME(#15746): This is wrong, we need to instead use extended filtering as per RFC4647 - // https://tools.ietf.org/html/rfc4647#section-3.3.2 + // 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 or s for each language tag. NonTSPseudoClass::Lang(ref lang) => extended_filtering(&*self.get_lang(), &*lang), NonTSPseudoClass::ReadOnly => diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index e915a2d07b1..9ce38c6a187 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -34,7 +34,6 @@ use atomic_refcell::{AtomicRef, AtomicRefCell}; use dom::bindings::inheritance::{CharacterDataTypeId, ElementTypeId}; use dom::bindings::inheritance::{HTMLElementTypeId, NodeTypeId}; use dom::bindings::js::LayoutJS; -use dom::bindings::str::extended_filtering; use dom::characterdata::LayoutCharacterDataHelpers; use dom::document::{Document, LayoutDocumentHelpers, PendingRestyle}; use dom::element::{Element, LayoutElementHelpers, RawLayoutElementHelpers}; @@ -70,7 +69,7 @@ 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}; +use style::selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, extended_filtering}; use style::shared_lock::{SharedRwLock as StyleSharedRwLock, Locked as StyleLocked}; use style::sink::Push; use style::str::is_whitespace; @@ -691,8 +690,10 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { NonTSPseudoClass::AnyLink => self.is_link(), NonTSPseudoClass::Visited => false, - // FIXME(#15746): This is wrong, we need to instead use extended filtering as per RFC4647 - // https://tools.ietf.org/html/rfc4647#section-3.3.2 + // 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 or s for each language tag. NonTSPseudoClass::Lang(ref lang) => extended_filtering(&*self.element.get_lang_for_layout(), &*lang), NonTSPseudoClass::ServoNonZeroBorder => unsafe { diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index dbc0e7c1eee..434e7a3082e 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -18,6 +18,7 @@ use selectors::Element; use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; use selectors::parser::SelectorMethods; use selectors::visitor::SelectorVisitor; +use std::ascii::AsciiExt; use std::borrow::Cow; use std::fmt; use std::fmt::Debug; @@ -611,3 +612,60 @@ impl + Debug> ElementExt for E { true } } + +/// Returns whether the language is matched, as defined by +/// [RFC 4647](https://tools.ietf.org/html/rfc4647#section-3.3.2). +pub fn extended_filtering(tag: &str, range: &str) -> bool { + let lang_ranges: Vec<&str> = range.split(',').collect(); + + lang_ranges.iter().any(|&lang_range| { + // step 1 + let range_subtags: Vec<&str> = lang_range.split('\x2d').collect(); + let tag_subtags: Vec<&str> = tag.split('\x2d').collect(); + + let mut range_iter = range_subtags.iter(); + let mut tag_iter = tag_subtags.iter(); + + // step 2 + // Note: [Level-4 spec](https://drafts.csswg.org/selectors/#lang-pseudo) check for wild card + if let (Some(range_subtag), Some(tag_subtag)) = (range_iter.next(), tag_iter.next()) { + if !(range_subtag.eq_ignore_ascii_case(tag_subtag) || range_subtag.eq_ignore_ascii_case("*")) { + return false; + } + } + + let mut current_tag_subtag = tag_iter.next(); + + // step 3 + for range_subtag in range_iter { + // step 3a + if range_subtag.eq_ignore_ascii_case("*") { + continue; + } + match current_tag_subtag.clone() { + Some(tag_subtag) => { + // step 3c + if range_subtag.eq_ignore_ascii_case(tag_subtag) { + current_tag_subtag = tag_iter.next(); + continue; + } else { + // step 3d + if tag_subtag.len() == 1 { + return false; + } else { + // else step 3e - continue with loop + current_tag_subtag = tag_iter.next(); + if current_tag_subtag.is_none() { + return false; + } + } + } + }, + // step 3b + None => { return false; } + } + } + // step 4 + true + }) +}