commit 2bf971952649f68eb066e07fc283f96d1c881a51
parent 91a80a1d9781ee3f3ca84ab833db52c3d813d864
Author: Denis Palmeiro <dpalmeiro@mozilla.com>
Date: Wed, 3 Dec 2025 14:46:43 +0000
Bug 1997380: Add test to exercise querySelector bloom filter. r=emilio
This test exercises a few problematic situations I encountered when testing
the bloom filter implementation over a large number of websites, mostly
centered around class value matching which we cannot utilize the bloom
filter for.
Differential Revision: https://phabricator.services.mozilla.com/D273068
Diffstat:
1 file changed, 431 insertions(+), 0 deletions(-)
diff --git a/testing/web-platform/tests/dom/nodes/querySelector-mixed-case.html b/testing/web-platform/tests/dom/nodes/querySelector-mixed-case.html
@@ -0,0 +1,431 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>querySelector with mixed-case attributes</title>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1997380">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<div id="test-container"></div>
+
+<script>
+"use strict";
+
+const container = document.getElementById("test-container");
+
+function buildTestTree() {
+ // Build entire DOM tree structure first (without attributes)
+ const html1 = document.createElement("div");
+ html1.id = "html1";
+
+ const svg1 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg1.id = "svg1";
+
+ const svg2 = document.createElementNS("http://www.w3.org/2000/svg", "g");
+ svg2.id = "svg2";
+
+ const svg3 = document.createElementNS("http://www.w3.org/2000/svg", "circle");
+ svg3.id = "svg3";
+
+ const html2 = document.createElement("div");
+ html2.id = "html2";
+
+ const math1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "math");
+ math1.id = "math1";
+
+ const math2 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mi");
+ math2.id = "math2";
+
+ const svg4 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg4.id = "svg4";
+
+ const svg5 = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ svg5.id = "svg5";
+
+ const html3 = document.createElement("div");
+ html3.id = "html3";
+
+ // Create foreignObject with HTML inside SVG (will go in svg1)
+ const foreign1 = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
+ foreign1.id = "foreign1";
+
+ const html4 = document.createElement("div");
+ html4.id = "html4";
+
+ const html5 = document.createElement("span");
+ html5.id = "html5";
+
+ // Create nested: HTML > SVG inside html2
+ const svg6 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg6.id = "svg6";
+
+ const foreign2 = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
+ foreign2.id = "foreign2";
+
+ const html6 = document.createElement("div");
+ html6.id = "html6";
+
+ const svg7 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg7.id = "svg7";
+
+ const svg8 = document.createElementNS("http://www.w3.org/2000/svg", "circle");
+ svg8.id = "svg8";
+
+ // Build tree structure
+ svg2.appendChild(svg3);
+ svg1.appendChild(svg2);
+
+ // Add foreignObject with HTML to svg1
+ html4.appendChild(html5);
+ foreign1.appendChild(html4);
+ svg1.appendChild(foreign1);
+
+ html1.appendChild(svg1);
+
+ math1.appendChild(math2);
+ html2.appendChild(math1);
+
+ // Add deeply nested SVG > foreignObject > HTML > SVG to html2
+ svg7.appendChild(svg8);
+ html6.appendChild(svg7);
+ foreign2.appendChild(html6);
+ svg6.appendChild(foreign2);
+ html2.appendChild(svg6);
+
+ html1.appendChild(html2);
+
+ svg4.appendChild(svg5);
+
+ // Create wrapper container for all trees
+ const wrapper = document.createElement("div");
+ wrapper.appendChild(html1);
+ wrapper.appendChild(svg4);
+ wrapper.appendChild(html3);
+
+ // NOW set all attributes
+ html1.setAttribute("viewBox", "html-val");
+ html1.setAttribute("dataIndex", "0");
+ html1.setAttribute("testAttr", "alpha");
+
+ svg1.setAttribute("viewBox", "svg-val");
+ svg1.setAttribute("dataIndex", "1");
+ svg1.setAttribute("testAttr", "beta");
+
+ svg2.setAttribute("mixedCase", "foo");
+ svg2.setAttribute("dataValue", "first");
+
+ svg3.setAttribute("innerAttr", "found");
+
+ html2.setAttribute("viewBox", "nested");
+ html2.setAttribute("mathVariant", "bold");
+
+ math1.setAttribute("mathVariant", "italic");
+ math1.setAttribute("displayStyle", "true");
+
+ math2.setAttribute("testIndex", "5");
+ math2.setAttribute("dataValue", "second");
+
+ svg5.setAttribute("viewBox", "rect-val");
+ svg5.setAttribute("testIndex", "10");
+
+ html3.setAttribute("MixedCase", "bar");
+ html3.setAttribute("TestIndex", "20");
+
+ // Set attributes on foreignObject tree in svg1
+ foreign1.setAttribute("foreignAttr", "foreign-val");
+
+ html4.setAttribute("ForeignHTML", "inside-foreign");
+ html4.setAttribute("DataType", "html-in-svg");
+
+ html5.setAttribute("NestedCase", "span-val");
+
+ // Set attributes on deeply nested tree in html2
+ svg6.setAttribute("DeepSVG", "middle-svg");
+ svg6.setAttribute("CaseSensitive", "nested");
+
+ foreign2.setAttribute("SecondForeign", "deep-foreign");
+
+ html6.setAttribute("InnerHTML", "deep-html");
+ html6.setAttribute("DataType", "nested");
+
+ svg7.setAttribute("ReEnteredSVG", "back-to-svg");
+
+ svg8.setAttribute("DeepestAttr", "circle-val");
+ svg8.setAttribute("CaseSensitive", "deepest");
+
+ return wrapper;
+}
+
+function runTests(root) {
+ // DOM tree structure with attributes:
+ // <div id="test-container">
+ // <div id="html1" viewBox="html-val" dataIndex="0" testAttr="alpha">
+ // <svg id="svg1" viewBox="svg-val" dataIndex="1" testAttr="beta">
+ // <g id="svg2" mixedCase="foo" dataValue="first">
+ // <circle id="svg3" innerAttr="found">
+ // </g>
+ // <foreignObject id="foreign1" foreignAttr="foreign-val">
+ // <div id="html4" ForeignHTML="inside-foreign" DataType="html-in-svg">
+ // <span id="html5" NestedCase="span-val">
+ // </div>
+ // </foreignObject>
+ // </svg>
+ // <div id="html2" viewBox="nested" mathVariant="bold">
+ // <math id="math1" mathVariant="italic" displayStyle="true">
+ // <mi id="math2" testIndex="5" dataValue="second">
+ // </math>
+ // <svg id="svg6" DeepSVG="middle-svg" CaseSensitive="nested">
+ // <foreignObject id="foreign2" SecondForeign="deep-foreign">
+ // <div id="html6" InnerHTML="deep-html" DataType="nested">
+ // <svg id="svg7" ReEnteredSVG="back-to-svg">
+ // <circle id="svg8" DeepestAttr="circle-val" CaseSensitive="deepest">
+ // </svg>
+ // </div>
+ // </foreignObject>
+ // </svg>
+ // </div>
+ // </div>
+ // <svg id="svg4">
+ // <rect id="svg5" viewBox="rect-val" testIndex="10">
+ // </svg>
+ // <div id="html3" MixedCase="bar" TestIndex="20">
+ // </div>
+
+ // Test 1: viewBox with mixed case - HTML is case-insensitive, SVG is case-sensitive
+ let results = root.querySelectorAll("[viewBox]");
+ assert_equals(results.length, 4, "[viewBox] should match 2 HTML elements + 2 SVG elements");
+ let ids = Array.from(results).map(el => el.id).sort();
+ assert_array_equals(ids, ["html1", "html2", "svg1", "svg5"], "[viewBox] should match correct elements");
+
+ // Test 2: viewbox lowercase - should only match HTML elements
+ results = root.querySelectorAll("[viewbox]");
+ assert_equals(results.length, 2, "[viewbox] should only match HTML elements");
+ ids = Array.from(results).map(el => el.id).sort();
+ assert_array_equals(ids, ["html1", "html2"], "[viewbox] should only match HTML divs");
+
+ // Test 3: VIEWBOX uppercase - should only match HTML elements
+ results = root.querySelectorAll("[VIEWBOX]");
+ assert_equals(results.length, 2, "[VIEWBOX] should only match HTML elements");
+ ids = Array.from(results).map(el => el.id).sort();
+ assert_array_equals(ids, ["html1", "html2"], "[VIEWBOX] should only match HTML divs");
+
+ // Test 4: mathVariant - HTML is case-insensitive, MathML is case-sensitive
+ results = root.querySelectorAll("[mathVariant]");
+ assert_equals(results.length, 2, "[mathVariant] should match HTML + MathML");
+ ids = Array.from(results).map(el => el.id).sort();
+ assert_array_equals(ids, ["html2", "math1"], "[mathVariant] should match correct elements");
+
+ // Test 5: mathvariant lowercase - should only match HTML
+ results = root.querySelectorAll("[mathvariant]");
+ assert_equals(results.length, 1, "[mathvariant] should only match HTML");
+ assert_equals(results[0].id, "html2", "[mathvariant] should match html2");
+
+ // Test 6: dataIndex exact case
+ results = root.querySelectorAll("[dataIndex]");
+ assert_equals(results.length, 2, "[dataIndex] should match HTML and SVG");
+ ids = Array.from(results).map(el => el.id).sort();
+ assert_array_equals(ids, ["html1", "svg1"], "[dataIndex] should match html1 and svg1");
+
+ // Test 7: dataindex lowercase - should only match HTML
+ results = root.querySelectorAll("[dataindex]");
+ assert_equals(results.length, 1, "[dataindex] should only match HTML");
+ assert_equals(results[0].id, "html1", "[dataindex] should match html1");
+
+ // Test 8: testIndex mixed case
+ // HTML elements: selector lowercased → [testindex] → matches html3
+ // SVG/MathML: selector used as-is → [testIndex] → matches math2, svg5 (case-sensitive)
+ results = root.querySelectorAll("[testIndex]");
+ assert_equals(results.length, 3, "[testIndex] should match 3 elements");
+ ids = Array.from(results).map(el => el.id).sort();
+ assert_array_equals(ids, ["html3", "math2", "svg5"], "[testIndex] should match html3, math2, and svg5");
+
+ // Test 9: TestIndex with capital T
+ // HTML elements: selector lowercased → [testindex] → matches html3
+ // SVG/MathML: selector used as-is → [TestIndex] → no match (testIndex != TestIndex)
+ results = root.querySelectorAll("[TestIndex]");
+ assert_equals(results.length, 1, "[TestIndex] should only match HTML element");
+ assert_equals(results[0].id, "html3", "[TestIndex] should match html3");
+
+ // Test 10: testindex all lowercase
+ // HTML elements: selector lowercased → [testindex] → matches html3
+ // SVG/MathML: selector used as-is → [testindex] → no match (testIndex != testindex)
+ results = root.querySelectorAll("[testindex]");
+ assert_equals(results.length, 1, "[testindex] should only match HTML element");
+ assert_equals(results[0].id, "html3", "[testindex] should match html3");
+
+ // Test 11: mixedCase exact
+ // HTML elements: selector lowercased → [mixedcase] → matches html3
+ // SVG: selector used as-is → [mixedCase] → matches svg2 (case-sensitive)
+ results = root.querySelectorAll("[mixedCase]");
+ assert_equals(results.length, 2, "[mixedCase] should match 2 elements");
+ ids = Array.from(results).map(el => el.id).sort();
+ assert_array_equals(ids, ["html3", "svg2"], "[mixedCase] should match html3 and svg2");
+
+ // Test 12: MixedCase capital M
+ // HTML elements: selector lowercased → [mixedcase] → matches html3
+ // SVG: selector used as-is → [MixedCase] → no match (mixedCase != MixedCase)
+ results = root.querySelectorAll("[MixedCase]");
+ assert_equals(results.length, 1, "[MixedCase] should only match HTML element");
+ assert_equals(results[0].id, "html3", "[MixedCase] should match html3");
+
+ // Test 13: mixedcase all lowercase
+ // HTML elements: selector lowercased → [mixedcase] → matches html3
+ // SVG: selector used as-is → [mixedcase] → no match (mixedCase != mixedcase)
+ results = root.querySelectorAll("[mixedcase]");
+ assert_equals(results.length, 1, "[mixedcase] should only match HTML element");
+ assert_equals(results[0].id, "html3", "[mixedcase] should match html3");
+
+ // Test 14: testAttr exact case
+ results = root.querySelectorAll("[testAttr]");
+ assert_equals(results.length, 2, "[testAttr] should match HTML and SVG");
+ ids = Array.from(results).map(el => el.id).sort();
+ assert_array_equals(ids, ["html1", "svg1"], "[testAttr] should match html1 and svg1");
+
+ // Test 15: Attribute value matching with prefix
+ results = root.querySelectorAll('[viewBox^="svg"]');
+ assert_equals(results.length, 1, '[viewBox^="svg"] should match 1 SVG element');
+ assert_equals(results[0].id, "svg1", '[viewBox^="svg"] should match svg1');
+
+ // Test 16: Attribute value matching with exact match
+ results = root.querySelectorAll('[dataIndex="0"]');
+ assert_equals(results.length, 1, '[dataIndex="0"] should match 1 element');
+ assert_equals(results[0].id, "html1", '[dataIndex="0"] should match html1');
+
+ // Test 17: Nested element query
+ results = root.querySelectorAll("[innerAttr]");
+ assert_equals(results.length, 1, "[innerAttr] should match nested circle");
+ assert_equals(results[0].id, "svg3", "[innerAttr] should match svg3");
+
+ // Test 18: displayStyle on MathML
+ results = root.querySelectorAll("[displayStyle]");
+ assert_equals(results.length, 1, "[displayStyle] should match MathML");
+ assert_equals(results[0].id, "math1", "[displayStyle] should match math1");
+
+ // Test 19: displaystyle lowercase - should not match anything (no HTML element has it)
+ results = root.querySelectorAll("[displaystyle]");
+ assert_equals(results.length, 0, "[displaystyle] should not match anything");
+
+ // Test 20: Case-sensitive value matching with 's' flag
+ results = root.querySelectorAll('[testAttr="alpha" s]');
+ assert_equals(results.length, 1, '[testAttr="alpha" s] should match 1 element');
+ assert_equals(results[0].id, "html1", '[testAttr="alpha" s] should match html1');
+
+ // Test 21: Case-insensitive value matching with 'i' flag
+ results = root.querySelectorAll('[testAttr="ALPHA" i]');
+ assert_equals(results.length, 1, '[testAttr="ALPHA" i] should match 1 element');
+ assert_equals(results[0].id, "html1", '[testAttr="ALPHA" i] should match html1');
+
+ // Test 22: ForeignHTML - HTML inside foreignObject should be case-insensitive
+ results = root.querySelectorAll("[ForeignHTML]");
+ assert_equals(results.length, 1, "[ForeignHTML] should match HTML in foreignObject");
+ assert_equals(results[0].id, "html4", "[ForeignHTML] should match html4");
+
+ // Test 23: foreignhtml lowercase - should match HTML element in foreignObject
+ results = root.querySelectorAll("[foreignhtml]");
+ assert_equals(results.length, 1, "[foreignhtml] should match HTML in foreignObject");
+ assert_equals(results[0].id, "html4", "[foreignhtml] should match html4");
+
+
+ // Test 24: DataType - should match both HTML elements (case-insensitive)
+ results = root.querySelectorAll("[DataType]");
+ assert_equals(results.length, 2, "[DataType] should match 2 HTML elements");
+ ids = Array.from(results).map(el => el.id).sort();
+ assert_array_equals(ids, ["html4", "html6"], "[DataType] should match html4 and html6");
+
+ // Test 25: datatype lowercase - should match both HTML elements
+ results = root.querySelectorAll("[datatype]");
+ assert_equals(results.length, 2, "[datatype] should match 2 HTML elements");
+ ids = Array.from(results).map(el => el.id).sort();
+ assert_array_equals(ids, ["html4", "html6"], "[datatype] should match html4 and html6");
+
+ // Test 26: CaseSensitive on SVG elements - case-sensitive
+ results = root.querySelectorAll("[CaseSensitive]");
+ assert_equals(results.length, 2, "[CaseSensitive] should match 2 SVG elements");
+ ids = Array.from(results).map(el => el.id).sort();
+ assert_array_equals(ids, ["svg6", "svg8"], "[CaseSensitive] should match svg6, svg8");
+
+ // Test 27: casesensitive lowercase - should NOT match SVG elements
+ results = root.querySelectorAll("[casesensitive]");
+ assert_equals(results.length, 0, "[casesensitive] should not match SVG elements");
+
+ // Test 28: NestedCase - HTML span inside foreignObject should be case-insensitive
+ results = root.querySelectorAll("[NestedCase]");
+ assert_equals(results.length, 1, "[NestedCase] should match HTML span in foreignObject");
+ assert_equals(results[0].id, "html5", "[NestedCase] should match html5");
+
+ // Test 29: nestedcase lowercase - should match HTML span
+ results = root.querySelectorAll("[nestedcase]");
+ assert_equals(results.length, 1, "[nestedcase] should match HTML span in foreignObject");
+ assert_equals(results[0].id, "html5", "[nestedcase] should match html5");
+
+ // Test 30: ReEnteredSVG - SVG inside HTML inside foreignObject should be case-sensitive
+ results = root.querySelectorAll("[ReEnteredSVG]");
+ assert_equals(results.length, 1, "[ReEnteredSVG] should match SVG nested in HTML in foreignObject");
+ assert_equals(results[0].id, "svg7", "[ReEnteredSVG] should match svg7");
+
+ // Test 31: reenteredsvg lowercase - should NOT match (SVG is case-sensitive)
+ results = root.querySelectorAll("[reenteredsvg]");
+ assert_equals(results.length, 0, "[reenteredsvg] should not match SVG element");
+
+ // Test 32: DeepestAttr - deeply nested SVG circle should be case-sensitive
+ results = root.querySelectorAll("[DeepestAttr]");
+ assert_equals(results.length, 1, "[DeepestAttr] should match deeply nested circle");
+ assert_equals(results[0].id, "svg8", "[DeepestAttr] should match svg8");
+
+ // Test 33: deepestattr lowercase - should NOT match
+ results = root.querySelectorAll("[deepestattr]");
+ assert_equals(results.length, 0, "[deepestattr] should not match SVG circle");
+
+ // Test 34: foreignAttr on foreignObject element - case-sensitive
+ results = root.querySelectorAll("[foreignAttr]");
+ assert_equals(results.length, 1, "[foreignAttr] should match foreignObject");
+ assert_equals(results[0].id, "foreign1", "[foreignAttr] should match foreign1");
+
+ // Test 35: foreignattr lowercase - should NOT match foreignObject (SVG namespace)
+ results = root.querySelectorAll("[foreignattr]");
+ assert_equals(results.length, 0, "[foreignattr] should not match foreignObject");
+
+ // Test 36: InnerHTML on HTML element inside foreignObject - case-insensitive
+ results = root.querySelectorAll("[InnerHTML]");
+ assert_equals(results.length, 1, "[InnerHTML] should match HTML in foreignObject");
+ assert_equals(results[0].id, "html6", "[InnerHTML] should match html6");
+
+ // Test 37: innerhtml lowercase - should match HTML in foreignObject
+ results = root.querySelectorAll("[innerhtml]");
+ assert_equals(results.length, 1, "[innerhtml] should match HTML in foreignObject");
+ assert_equals(results[0].id, "html6", "[innerhtml] should match html6");
+
+ // Test 38: DeepSVG on SVG element - case-sensitive
+ results = root.querySelectorAll("[DeepSVG]");
+ assert_equals(results.length, 1, "[DeepSVG] should match SVG element");
+ assert_equals(results[0].id, "svg6", "[DeepSVG] should match svg6");
+
+ // Test 39: deepsvg lowercase - should NOT match SVG
+ results = root.querySelectorAll("[deepsvg]");
+ assert_equals(results.length, 0, "[deepsvg] should not match SVG element");
+
+ // Test 40: SecondForeign on foreignObject element - case-sensitive
+ results = root.querySelectorAll("[SecondForeign]");
+ assert_equals(results.length, 1, "[SecondForeign] should match foreignObject");
+ assert_equals(results[0].id, "foreign2", "[SecondForeign] should match foreign2");
+
+ // Test 41: secondforeign lowercase - should NOT match foreignObject
+ results = root.querySelectorAll("[secondforeign]");
+ assert_equals(results.length, 0, "[secondforeign] should not match foreignObject");
+}
+
+test(() => {
+ const tree = buildTestTree();
+
+ // Test on disconnected tree first
+ runTests(tree);
+
+ // Now append to document and test again
+ container.appendChild(tree);
+ runTests(container);
+
+ container.innerHTML = "";
+}, "Mixed HTML/SVG/MathML tree with various mixed-case attributes");
+
+</script>
+</body>