Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
ID and class selectors are ASCII case-insensitive in quirks mode.
  • Loading branch information
SimonSapin committed Jun 12, 2017
1 parent 524fcac commit 5bccf98
Show file tree
Hide file tree
Showing 22 changed files with 314 additions and 192 deletions.
2 changes: 2 additions & 0 deletions components/atoms/static_atoms.txt
Expand Up @@ -68,3 +68,5 @@ fullscreenchange
fullscreenerror
gattserverdisconnected
onchange

reftest-wait
43 changes: 25 additions & 18 deletions components/script/dom/element.rs
Expand Up @@ -85,7 +85,7 @@ use net_traits::request::CorsSettings;
use ref_filter_map::ref_filter_map;
use script_layout_interface::message::ReflowQueryType;
use script_thread::Runnable;
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode};
use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS};
use selectors::matching::{RelevantLinkStatus, matches_selector_list};
Expand All @@ -97,6 +97,7 @@ use std::convert::TryFrom;
use std::default::Default;
use std::fmt;
use std::rc::Rc;
use style::CaseSensitivityExt;
use style::applicable_declarations::ApplicableDeclarationBlock;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::context::{QuirksMode, ReflowGoal};
Expand Down Expand Up @@ -345,7 +346,9 @@ impl RawLayoutElementHelpers for Element {

pub trait LayoutElementHelpers {
#[allow(unsafe_code)]
unsafe fn has_class_for_layout(&self, name: &Atom) -> bool;
unsafe fn in_quirks_mode_document_for_layout(&self) -> bool;
#[allow(unsafe_code)]
unsafe fn has_class_for_layout(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool;
#[allow(unsafe_code)]
unsafe fn get_classes_for_layout(&self) -> Option<&'static [Atom]>;

Expand Down Expand Up @@ -373,9 +376,15 @@ pub trait LayoutElementHelpers {
impl LayoutElementHelpers for LayoutJS<Element> {
#[allow(unsafe_code)]
#[inline]
unsafe fn has_class_for_layout(&self, name: &Atom) -> bool {
unsafe fn in_quirks_mode_document_for_layout(&self) -> bool {
self.upcast::<Node>().owner_doc_for_layout().quirks_mode() == QuirksMode::Quirks
}

#[allow(unsafe_code)]
#[inline]
unsafe fn has_class_for_layout(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
get_attr_for_layout(&*self.unsafe_get(), &ns!(), &local_name!("class")).map_or(false, |attr| {
attr.value_tokens_forever().unwrap().iter().any(|atom| atom == name)
attr.value_tokens_forever().unwrap().iter().any(|atom| case_sensitivity.eq_atom(atom, name))
})
}

Expand Down Expand Up @@ -1158,16 +1167,10 @@ impl Element {
})
}

pub fn has_class(&self, name: &Atom) -> bool {
let quirks_mode = document_from_node(self).quirks_mode();
let is_equal = |lhs: &Atom, rhs: &Atom| {
match quirks_mode {
QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => lhs == rhs,
QuirksMode::Quirks => lhs.eq_ignore_ascii_case(&rhs),
}
};
self.get_attribute(&ns!(), &local_name!("class"))
.map_or(false, |attr| attr.value().as_tokens().iter().any(|atom| is_equal(name, atom)))
pub fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
self.get_attribute(&ns!(), &local_name!("class")).map_or(false, |attr| {
attr.value().as_tokens().iter().any(|atom| case_sensitivity.eq_atom(name, atom))
})
}

pub fn set_atomic_attribute(&self, local_name: &LocalName, value: DOMString) {
Expand Down Expand Up @@ -2503,12 +2506,16 @@ impl<'a> ::selectors::Element for Root<Element> {
}
}

fn get_id(&self) -> Option<Atom> {
self.id_attribute.borrow().clone()
fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool {
self.id_attribute.borrow().as_ref().map_or(false, |atom| case_sensitivity.eq_atom(id, atom))
}

fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
Element::has_class(&**self, name, case_sensitivity)
}

fn has_class(&self, name: &Atom) -> bool {
Element::has_class(&**self, name)
fn in_quirks_mode_document(&self) -> bool {
document_from_node(&**self).quirks_mode() == QuirksMode::Quirks
}

fn is_html_element_in_html_document(&self) -> bool {
Expand Down
11 changes: 9 additions & 2 deletions components/script/dom/htmlcollection.rs
Expand Up @@ -11,12 +11,14 @@ use dom::bindings::str::DOMString;
use dom::bindings::trace::JSTraceable;
use dom::bindings::xmlname::namespace_from_domstring;
use dom::element::Element;
use dom::node::Node;
use dom::node::{Node, document_from_node};
use dom::window::Window;
use dom_struct::dom_struct;
use html5ever::{LocalName, QualName};
use selectors::attr::CaseSensitivity;
use servo_atoms::Atom;
use std::cell::Cell;
use style::context::QuirksMode;
use style::str::split_html_space_chars;

pub trait CollectionFilter : JSTraceable {
Expand Down Expand Up @@ -199,7 +201,12 @@ impl HTMLCollection {
}
impl CollectionFilter for ClassNameFilter {
fn filter(&self, elem: &Element, _root: &Node) -> bool {
self.classes.iter().all(|class| elem.has_class(class))
let case_sensitivity = match document_from_node(elem).quirks_mode() {
QuirksMode::NoQuirks |
QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive,
QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive,
};
self.classes.iter().all(|class| elem.has_class(class, case_sensitivity))
}
}
let filter = ClassNameFilter {
Expand Down
4 changes: 2 additions & 2 deletions components/script/dom/window.rs
Expand Up @@ -84,7 +84,7 @@ use script_traits::{ConstellationControlMsg, DocumentState, LoadData, MozBrowser
use script_traits::{ScriptMsg as ConstellationMsg, ScrollState, TimerEvent, TimerEventId};
use script_traits::{TimerSchedulerMsg, UntrustedNodeAddress, WindowSizeData, WindowSizeType};
use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult};
use servo_atoms::Atom;
use selectors::attr::CaseSensitivity;
use servo_config::opts;
use servo_config::prefs::PREFS;
use servo_geometry::{f32_rect_to_au_rect, max_rect};
Expand Down Expand Up @@ -1365,7 +1365,7 @@ impl Window {
// See http://testthewebforward.org/docs/reftests.html
let html_element = document.GetDocumentElement();
let reftest_wait = html_element.map_or(false, |elem| {
elem.has_class(&Atom::from("reftest-wait"))
elem.has_class(&atom!("reftest-wait"), CaseSensitivity::CaseSensitive)
});

let ready_state = document.ReadyState();
Expand Down
39 changes: 30 additions & 9 deletions components/script/layout_wrapper.rs
Expand Up @@ -49,7 +49,7 @@ use script_layout_interface::{HTMLCanvasData, LayoutNodeType, SVGSVGData, Truste
use script_layout_interface::{OpaqueStyleAndLayoutData, StyleData};
use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode};
use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus};
use selectors::matching::VisitedHandlingMode;
use servo_atoms::Atom;
Expand All @@ -61,6 +61,7 @@ use std::marker::PhantomData;
use std::mem::transmute;
use std::sync::atomic::Ordering;
use style;
use style::CaseSensitivityExt;
use style::applicable_declarations::ApplicableDeclarationBlock;
use style::attr::AttrValue;
use style::computed_values::display;
Expand Down Expand Up @@ -414,6 +415,13 @@ impl<'le> TElement for ServoLayoutElement<'le> {
self.get_attr(namespace, attr).is_some()
}

#[inline]
fn get_id(&self) -> Option<Atom> {
unsafe {
(*self.element.id_attribute()).clone()
}
}

#[inline(always)]
fn each_class<F>(&self, mut callback: F) where F: FnMut(&Atom) {
unsafe {
Expand Down Expand Up @@ -779,16 +787,24 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> {
}

#[inline]
fn get_id(&self) -> Option<Atom> {
fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool {
unsafe {
(*self.element.id_attribute()).clone()
(*self.element.id_attribute())
.as_ref()
.map_or(false, |atom| case_sensitivity.eq_atom(atom, id))
}
}

#[inline]
fn has_class(&self, name: &Atom) -> bool {
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
unsafe {
self.element.has_class_for_layout(name)
self.element.has_class_for_layout(name, case_sensitivity)
}
}

fn in_quirks_mode_document(&self) -> bool {
unsafe {
self.element.in_quirks_mode_document_for_layout()
}
}

Expand Down Expand Up @@ -1194,6 +1210,11 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> {
true
}

fn in_quirks_mode_document(&self) -> bool {
debug!("ServoThreadSafeLayoutElement::in_quirks_mode_document called");
false
}

#[inline]
fn get_local_name(&self) -> &LocalName {
self.element.get_local_name()
Expand Down Expand Up @@ -1249,12 +1270,12 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> {
false
}

fn get_id(&self) -> Option<Atom> {
debug!("ServoThreadSafeLayoutElement::get_id called");
None
fn has_id(&self, _id: &Atom, _case_sensitivity: CaseSensitivity) -> bool {
debug!("ServoThreadSafeLayoutElement::has_id called");
false
}

fn has_class(&self, _name: &Atom) -> bool {
fn has_class(&self, _name: &Atom, _case_sensitivity: CaseSensitivity) -> bool {
debug!("ServoThreadSafeLayoutElement::has_class called");
false
}
Expand Down
4 changes: 2 additions & 2 deletions components/selectors/attr.rs
Expand Up @@ -127,7 +127,7 @@ pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C

#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub enum ParsedCaseSensitivity {
CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive.
CaseSensitive,
AsciiCaseInsensitive,
AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument,
}
Expand All @@ -150,7 +150,7 @@ impl ParsedCaseSensitivity {

#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub enum CaseSensitivity {
CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive.
CaseSensitive,
AsciiCaseInsensitive,
}

Expand Down
17 changes: 13 additions & 4 deletions components/selectors/matching.rs
Expand Up @@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint};
use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
use bloom::{BLOOM_HASH_MASK, BloomFilter};
use parser::{AncestorHashes, Combinator, Component, LocalName};
use parser::{Selector, SelectorImpl, SelectorIter, SelectorList};
Expand Down Expand Up @@ -666,12 +666,21 @@ fn matches_simple_selector<E, F>(
let ns = ::parser::namespace_empty_string::<E::Impl>();
element.get_namespace() == ns.borrow()
}
// TODO: case-sensitivity depends on the document type and quirks mode
Component::ID(ref id) => {
element.get_id().map_or(false, |attr| attr == *id)
let case_sensitivity = if element.in_quirks_mode_document() {
CaseSensitivity::AsciiCaseInsensitive
} else {
CaseSensitivity::CaseSensitive
};
element.has_id(id, case_sensitivity)
}
Component::Class(ref class) => {
element.has_class(class)
let case_sensitivity = if element.in_quirks_mode_document() {
CaseSensitivity::AsciiCaseInsensitive
} else {
CaseSensitivity::CaseSensitive
};
element.has_class(class, case_sensitivity)
}
Component::AttributeInNoNamespaceExists { ref local_name, ref local_name_lower } => {
let is_html = element.is_html_element_in_html_document();
Expand Down
5 changes: 4 additions & 1 deletion components/selectors/parser.rs
Expand Up @@ -1385,7 +1385,10 @@ fn parse_attribute_flags<'i, 't, E>(input: &mut CssParser<'i, 't>)
-> Result<ParsedCaseSensitivity,
ParseError<'i, SelectorParseError<'i, E>>> {
match input.next() {
Err(_) => Ok(ParsedCaseSensitivity::CaseSensitive),
Err(_) => {
// Selectors spec says language-defined, but HTML says sensitive.
Ok(ParsedCaseSensitivity::CaseSensitive)
}
Ok(Token::Ident(ref value)) if value.eq_ignore_ascii_case("i") => {
Ok(ParsedCaseSensitivity::AsciiCaseInsensitive)
}
Expand Down
19 changes: 15 additions & 4 deletions components/selectors/tree.rs
Expand Up @@ -5,7 +5,7 @@
//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency
//! between layout and style.

use attr::{AttrSelectorOperation, NamespaceConstraint};
use attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
use matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus};
use parser::SelectorImpl;
use std::fmt::Debug;
Expand Down Expand Up @@ -63,9 +63,20 @@ pub trait Element: Sized + Debug {
/// Whether this element is a `link`.
fn is_link(&self) -> bool;

fn get_id(&self) -> Option<<Self::Impl as SelectorImpl>::Identifier>;

fn has_class(&self, name: &<Self::Impl as SelectorImpl>::ClassName) -> bool;
/// Whether this element is in a document that is in quirks mode.
///
/// https://dom.spec.whatwg.org/#concept-document-quirks
fn in_quirks_mode_document(&self) -> bool;

fn has_id(&self,
id: &<Self::Impl as SelectorImpl>::Identifier,
case_sensitivity: CaseSensitivity)
-> bool;

fn has_class(&self,
name: &<Self::Impl as SelectorImpl>::ClassName,
case_sensitivity: CaseSensitivity)
-> bool;

/// Returns whether this element matches `:empty`.
///
Expand Down
3 changes: 3 additions & 0 deletions components/style/dom.rs
Expand Up @@ -376,6 +376,9 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
/// Whether this element has an attribute with a given namespace.
fn has_attr(&self, namespace: &Namespace, attr: &LocalName) -> bool;

/// The ID for this element.
fn get_id(&self) -> Option<Atom>;

/// Internal iterator for the classes of this element.
fn each_class<F>(&self, callback: F) where F: FnMut(&Atom);

Expand Down
3 changes: 2 additions & 1 deletion components/style/gecko/snapshot.rs
Expand Up @@ -164,13 +164,14 @@ impl ElementSnapshot for GeckoElementSnapshot {
}

#[inline]
fn has_class(&self, name: &Atom) -> bool {
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
if !self.has_any(Flags::MaybeClass) {
return false;
}

snapshot_helpers::has_class(self.as_ptr(),
name,
case_sensitivity,
bindings::Gecko_SnapshotClassOrClassList)
}

Expand Down
8 changes: 6 additions & 2 deletions components/style/gecko/snapshot_helpers.rs
Expand Up @@ -4,7 +4,10 @@

//! Element an snapshot common logic.

use CaseSensitivityExt;
use gecko_bindings::structs::nsIAtom;
use gecko_string_cache::WeakAtom;
use selectors::attr::CaseSensitivity;
use std::{ptr, slice};
use string_cache::Atom;

Expand All @@ -16,6 +19,7 @@ pub type ClassOrClassList<T> = unsafe extern fn (T, *mut *mut nsIAtom, *mut *mut
/// element has the class that `name` represents.
pub fn has_class<T>(item: T,
name: &Atom,
case_sensitivity: CaseSensitivity,
getter: ClassOrClassList<T>) -> bool
{
unsafe {
Expand All @@ -24,10 +28,10 @@ pub fn has_class<T>(item: T,
let length = getter(item, &mut class, &mut list);
match length {
0 => false,
1 => name.as_ptr() == class,
1 => case_sensitivity.eq_atom(name, WeakAtom::new(class)),
n => {
let classes = slice::from_raw_parts(list, n as usize);
classes.iter().any(|ptr| name.as_ptr() == *ptr)
classes.iter().any(|ptr| case_sensitivity.eq_atom(name, WeakAtom::new(*ptr)))
}
}
}
Expand Down

0 comments on commit 5bccf98

Please sign in to comment.