fix(dom): honor quirks mode for class/id selector case-sensitivity
set_quirks_mode was a no-op and the selector MatchingContext hardcoded QuirksMode::NoQuirks, so class/id selectors were always case-sensitive even on quirks-mode (no-doctype) pages where browsers match .foo / #bar ASCII-case-insensitively. Store the parser-reported quirks mode on DomTree, pass it into the selector context, and skip the exact-case #id index fast path in quirks mode so it falls through to case-insensitive matching. Fixes #499
This commit is contained in:
1 parent
8078cc3070
commit
72a9c3ced3
3 files changed
+64
-3
No files matched your search
@@ -580,6 +580,10 @@ impl DomTree {
|
|||||||
// Fast path: a bare "#id" selector resolves through the id index in O(1)
|
// Fast path: a bare "#id" selector resolves through the id index in O(1)
|
||||||
// instead of scanning every descendant. The index holds the first element
|
// instead of scanning every descendant. The index holds the first element
|
||||||
// in tree order per id, which is exactly what the full scan would return.
|
// in tree order per id, which is exactly what the full scan would return.
|
||||||
|
// In quirks mode `#id` matches ASCII-case-insensitively, but the id
|
||||||
|
// index is keyed on the exact-case id, so skip the fast path and let the
|
||||||
|
// selector engine (below) do the case-insensitive match.
|
||||||
|
if !self.is_quirks() {
|
||||||
if let Some(id) = simple_id_selector(selector) {
|
if let Some(id) = simple_id_selector(selector) {
|
||||||
match self.get_element_by_id(id) {
|
match self.get_element_by_id(id) {
|
||||||
// querySelector matches strict descendants of root only, so the
|
// querySelector matches strict descendants of root only, so the
|
||||||
@@ -592,13 +596,14 @@ impl DomTree {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let selector_list = parse_selector(selector)?;
|
let selector_list = parse_selector(selector)?;
|
||||||
let mut caches = selectors::context::SelectorCaches::default();
|
let mut caches = selectors::context::SelectorCaches::default();
|
||||||
let mut context = MatchingContext::new(
|
let mut context = MatchingContext::new(
|
||||||
MatchingMode::Normal,
|
MatchingMode::Normal,
|
||||||
None,
|
None,
|
||||||
&mut caches,
|
&mut caches,
|
||||||
QuirksMode::NoQuirks,
|
self.selector_quirks_mode(),
|
||||||
NeedsSelectorFlags::No,
|
NeedsSelectorFlags::No,
|
||||||
MatchingForInvalidation::No,
|
MatchingForInvalidation::No,
|
||||||
);
|
);
|
||||||
@@ -619,6 +624,16 @@ impl DomTree {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map the document's quirks flag onto the selector crate's QuirksMode. In
|
||||||
|
// quirks mode the crate matches class/id selectors ASCII-case-insensitively.
|
||||||
|
fn selector_quirks_mode(&self) -> QuirksMode {
|
||||||
|
if self.is_quirks() {
|
||||||
|
QuirksMode::Quirks
|
||||||
|
} else {
|
||||||
|
QuirksMode::NoQuirks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn query_selector_all_from(&self, root: NodeId, selector: &str) -> Result<Vec<NodeId>, String> {
|
pub fn query_selector_all_from(&self, root: NodeId, selector: &str) -> Result<Vec<NodeId>, String> {
|
||||||
let selector_list = parse_selector(selector)?;
|
let selector_list = parse_selector(selector)?;
|
||||||
let mut caches = selectors::context::SelectorCaches::default();
|
let mut caches = selectors::context::SelectorCaches::default();
|
||||||
@@ -626,7 +641,7 @@ impl DomTree {
|
|||||||
MatchingMode::Normal,
|
MatchingMode::Normal,
|
||||||
None,
|
None,
|
||||||
&mut caches,
|
&mut caches,
|
||||||
QuirksMode::NoQuirks,
|
self.selector_quirks_mode(),
|
||||||
NeedsSelectorFlags::No,
|
NeedsSelectorFlags::No,
|
||||||
MatchingForInvalidation::No,
|
MatchingForInvalidation::No,
|
||||||
);
|
);
|
||||||
@@ -729,6 +744,34 @@ mod tests {
|
|||||||
assert!(result.is_none());
|
assert!(result.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn quirks_mode_matches_class_and_id_case_insensitively() {
|
||||||
|
// No doctype => quirks mode; class/id match ASCII case-insensitively.
|
||||||
|
let tree = parse_html(r#"<div class="Foo" id="Bar">x</div>"#);
|
||||||
|
assert!(
|
||||||
|
tree.query_selector(".foo").unwrap().is_some(),
|
||||||
|
".foo should match class=\"Foo\" in quirks mode"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
tree.query_selector("#bar").unwrap().is_some(),
|
||||||
|
"#bar should match id=\"Bar\" in quirks mode"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn standards_mode_matches_class_and_id_case_sensitively() {
|
||||||
|
// With a doctype => no-quirks; class/id remain case-sensitive.
|
||||||
|
let tree = parse_html(r#"<!DOCTYPE html><div class="Foo" id="Bar">x</div>"#);
|
||||||
|
assert!(
|
||||||
|
tree.query_selector(".foo").unwrap().is_none(),
|
||||||
|
".foo must NOT match class=\"Foo\" in standards mode"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
tree.query_selector("#bar").unwrap().is_none(),
|
||||||
|
"#bar must NOT match id=\"Bar\" in standards mode"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_query_selector_complex() {
|
fn test_query_selector_complex() {
|
||||||
let tree = parse_html(
|
let tree = parse_html(
|
||||||
|
|||||||
@@ -143,6 +143,9 @@ pub(crate) struct DomTreeInner {
|
|||||||
pub(crate) free_list: Vec<u32>,
|
pub(crate) free_list: Vec<u32>,
|
||||||
pub(crate) document: NodeId,
|
pub(crate) document: NodeId,
|
||||||
pub(crate) id_index: HashMap<String, NodeId>,
|
pub(crate) id_index: HashMap<String, NodeId>,
|
||||||
|
// Whether the document was parsed in (full) quirks mode. In quirks mode CSS
|
||||||
|
// class and id selectors match ASCII-case-insensitively.
|
||||||
|
pub(crate) quirks: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DomTree {
|
impl DomTree {
|
||||||
@@ -162,6 +165,7 @@ impl DomTree {
|
|||||||
free_list: Vec::new(),
|
free_list: Vec::new(),
|
||||||
document: NodeId(0),
|
document: NodeId(0),
|
||||||
id_index: HashMap::new(),
|
id_index: HashMap::new(),
|
||||||
|
quirks: false,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,6 +174,17 @@ impl DomTree {
|
|||||||
self.inner.borrow().document
|
self.inner.borrow().document
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Record whether the document was parsed in (full) quirks mode.
|
||||||
|
pub fn set_quirks(&self, quirks: bool) {
|
||||||
|
self.inner.borrow_mut().quirks = quirks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the document is in (full) quirks mode, in which CSS class and id
|
||||||
|
/// selectors match ASCII-case-insensitively.
|
||||||
|
pub fn is_quirks(&self) -> bool {
|
||||||
|
self.inner.borrow().quirks
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn borrow_inner(&self) -> std::cell::Ref<'_, DomTreeInner> {
|
pub(crate) fn borrow_inner(&self) -> std::cell::Ref<'_, DomTreeInner> {
|
||||||
self.inner.borrow()
|
self.inner.borrow()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,7 +218,10 @@ impl TreeSink for DomTree {
|
|||||||
x == y
|
x == y
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_quirks_mode(&self, _mode: QuirksMode) {
|
fn set_quirks_mode(&self, mode: QuirksMode) {
|
||||||
|
// Only full quirks mode makes CSS class/id selectors case-insensitive;
|
||||||
|
// limited-quirks behaves like no-quirks for selector matching.
|
||||||
|
self.set_quirks(mode == QuirksMode::Quirks);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_mathml_annotation_xml_integration_point(&self, target: &NodeId) -> bool {
|
fn is_mathml_annotation_xml_integration_point(&self, target: &NodeId) -> bool {
|
||||||
|
|||||||
Reference in new issue
Block a user