script: Move extended_filtering to the style crate. draft
authorCameron McCormack <cam@mcc.id.au>
Tue, 06 Jun 2017 12:44:17 +0800
changeset 590047 940c333ff711059b52b762f51bf4bdcbef224cbc
parent 590046 b71e025945df4e751bc12c8ff7227111cf53ef23
child 590048 7abf6695e9e9d45aceb5d108bdb6c6a70a5edd38
push id62581
push userbmo:cam@mcc.id.au
push dateWed, 07 Jun 2017 05:05:00 +0000
milestone55.0a1
script: Move extended_filtering to the style crate. We'll need to call it from the style crate in later patches, when matching :lang() against element snapshots. MozReview-Commit-ID: 1jSn8dO4L1F
servo/components/script/dom/bindings/str.rs
servo/components/script/dom/element.rs
servo/components/script/layout_wrapper.rs
servo/components/style/servo/selector_parser.rs
--- a/servo/components/script/dom/bindings/str.rs
+++ b/servo/components/script/dom/bindings/str.rs
@@ -108,73 +108,16 @@ pub fn is_token(s: &[u8]) -> bool {
             125 |
             32 => false, // separators
             x if x > 127 => false, // non-CHARs
             _ => 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
-    })
-}
-
 
 /// A DOMString.
 ///
 /// This type corresponds to the [`DOMString`](idl) type in WebIDL.
 ///
 /// [idl]: https://heycam.github.io/webidl/#idl-DOMString
 ///
 /// Cenceptually, a DOMString has the same value space as a JavaScript String,
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -21,17 +21,17 @@ use dom::bindings::codegen::Bindings::Wi
 use dom::bindings::codegen::UnionTypes::NodeOrString;
 use dom::bindings::conversions::DerivedFrom;
 use dom::bindings::error::{Error, ErrorResult, Fallible};
 use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
 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;
 use dom::create::create_element;
 use dom::document::{Document, LayoutDocumentHelpers};
 use dom::documentfragment::DocumentFragment;
 use dom::domrect::DOMRect;
 use dom::domtokenlist::DOMTokenList;
@@ -101,16 +101,17 @@ use std::rc::Rc;
 use style::attr::{AttrValue, LengthOrPercentageOrAuto};
 use style::context::{QuirksMode, ReflowGoal};
 use style::element_state::*;
 use style::properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, parse_style_attribute};
 use style::properties::longhands::{self, background_image, border_spacing, font_family, font_size, overflow_x};
 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;
 use style::stylist::ApplicableDeclarationBlock;
 use style::thread_state;
 use style::values::{CSSFloat, Either};
 use style::values::specified::{self, CSSColor};
 use stylesheet_loader::StylesheetOwner;
@@ -2453,18 +2454,20 @@ impl<'a> ::selectors::Element for Root<E
                 }
             },
 
             NonTSPseudoClass::ServoCaseSensitiveTypeAttr(ref expected_value) => {
                 self.get_attribute(&ns!(), &local_name!("type"))
                     .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 <ident> or <string>s for each language tag.
             NonTSPseudoClass::Lang(ref lang) => extended_filtering(&*self.get_lang(), &*lang),
 
             NonTSPseudoClass::ReadOnly =>
                 !Element::state(self).contains(pseudo_class.state_flag()),
 
             NonTSPseudoClass::Active |
             NonTSPseudoClass::Focus |
             NonTSPseudoClass::Fullscreen |
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -29,17 +29,16 @@
 //!     `html_element_in_html_document_for_layout()`.
 
 #![allow(unsafe_code)]
 
 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};
 use dom::node::{CAN_BE_FRAGMENTED, DIRTY_ON_VIEWPORT_SIZE_CHANGE, HAS_DIRTY_DESCENDANTS, IS_IN_DOC};
 use dom::node::{HANDLED_SNAPSHOT, HAS_SNAPSHOT};
 use dom::node::{LayoutNodeHelpers, Node};
 use dom::text::Text;
 use gfx_traits::ByteIndex;
@@ -65,17 +64,17 @@ use style::attr::AttrValue;
 use style::computed_values::display;
 use style::context::{QuirksMode, SharedStyleContext};
 use style::data::ElementData;
 use style::dom::{DescendantsBit, DirtyDescendants, LayoutIterator, NodeInfo, OpaqueNode};
 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;
 use style::stylearc::Arc;
 use style::stylist::ApplicableDeclarationBlock;
 
 #[derive(Copy, Clone)]
 pub struct ServoLayoutNode<'a> {
@@ -686,18 +685,20 @@ impl<'le> ::selectors::Element for Servo
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         match *pseudo_class {
             // https://github.com/servo/servo/issues/8718
             NonTSPseudoClass::Link |
             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 <ident> or <string>s for each language tag.
             NonTSPseudoClass::Lang(ref lang) => extended_filtering(&*self.element.get_lang_for_layout(), &*lang),
 
             NonTSPseudoClass::ServoNonZeroBorder => unsafe {
                 match (*self.element.unsafe_get()).get_attr_for_layout(&ns!(), &local_name!("border")) {
                     None | Some(&AttrValue::UInt(_, 0)) => false,
                     _ => true,
                 }
             },
--- a/servo/components/style/servo/selector_parser.rs
+++ b/servo/components/style/servo/selector_parser.rs
@@ -13,16 +13,17 @@ use dom::{OpaqueNode, TElement, TNode};
 use element_state::ElementState;
 use fnv::FnvHashMap;
 use restyle_hints::ElementSnapshot;
 use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser};
 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;
 use std::mem;
 use std::ops::{Deref, DerefMut};
 
 /// A pseudo-element, both public and private.
 ///
@@ -606,8 +607,65 @@ impl ServoElementSnapshot {
 }
 
 impl<E: Element<Impl=SelectorImpl> + Debug> ElementExt for E {
     #[inline]
     fn matches_user_and_author_rules(&self) -> bool {
         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
+    })
+}