LibWeb: Add WPT tests related to XPath evaluation

This commit is contained in:
Johannes Gustafsson 2025-09-29 22:18:07 +02:00 committed by Jelle Raaijmakers
parent d2030a5377
commit e9e58d83b3
64 changed files with 37681 additions and 0 deletions

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<head>
<link rel="help" href="https://github.com/servo/servo/issues/36971">
<meta name="assert" content="Using variables in xpath expression should not crash.">
</head>
<body>
<script>
// The exact behaviour here is not defined. Firefox throws an error, Chrome doesn't.
document.evaluate("$foo", document.createElement("div"));
</script>
</body>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Evaluating XPath expressions with orhpaned Attr as context node doesn't crash</title>
<link rel=author href="mailto:jarhar@chromium.org">
<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1236967">
<script src="../resources/testharnessreport.js"></script>
<body>
<script>
for (const expression of [
"..",
"parent",
"ancestor::*",
"ancestor-or-self::*",
"following::*",
"preceding::*",
]) {
const orphanedAttr = document.createAttribute("foo");
new XPathEvaluator().evaluate(expression, orphanedAttr, null, 2);
}
</script>

View File

@ -0,0 +1,12 @@
Harness status: OK
Found 7 tests
7 Pass
Pass "or" operator depending on the context node
Pass "=" operator depending on the context node
Pass "!=" operator depending on the context node
Pass "<" operator depending on the context node
Pass ">" operator depending on the context node
Pass ">=" operator depending on the context node
Pass "<=" operator depending on the context node

View File

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass XPath parent of documentElement

View File

@ -0,0 +1,7 @@
Harness status: OK
Found 2 tests
2 Pass
Pass Constructor with 'new'
Pass Constructor without 'new'

View File

@ -0,0 +1,9 @@
Harness status: OK
Found 4 tests
4 Fail
Fail evaluator from realm with XML associated document, context node in XML document, no namespace resolver
Fail evaluator from realm with XML associated document, context node in HTML document, no namespace resolver
Fail evaluator from realm with HTML associated document, context node in XML document, no namespace resolver
Fail evaluator from realm with HTML associated document, context node in HTML document, no namespace resolver

View File

@ -0,0 +1,14 @@
Harness status: OK
Found 8 tests
4 Pass
4 Fail
Fail evaluate operation on XML document, context node in XML document, no namespace resolver
Pass evaluate operation on HTML document, context node in HTML document, no namespace resolver
Pass evaluate operation on XML document, context node in HTML document, no namespace resolver
Fail evaluate operation on HTML document, context node in XML document, no namespace resolver
Fail evaluate operation on XML document, context node in XML document, with namespace resolver
Pass evaluate operation on HTML document, context node in HTML document, with namespace resolver
Pass evaluate operation on XML document, context node in HTML document, with namespace resolver
Fail evaluate operation on HTML document, context node in XML document, with namespace resolver

View File

@ -0,0 +1,9 @@
Harness status: OK
Found 4 tests
4 Fail
Fail expression from realm with XML associated document, context node in XML document, no namespace resolver
Fail expression from realm with XML associated document, context node in HTML document, no namespace resolver
Fail expression from realm with HTML associated document, context node in XML document, no namespace resolver
Fail expression from realm with HTML associated document, context node in HTML document, no namespace resolver

View File

@ -0,0 +1,14 @@
Harness status: OK
Found 8 tests
4 Pass
4 Fail
Fail expression from XML document, context node in XML document, no namespace resolver
Pass expression from HTML document, context node in HTML document, no namespace resolver
Pass expression from XML document, context node in HTML document, no namespace resolver
Fail expression from HTML document, context node in XML document, no namespace resolver
Fail expression from XML document, context node in XML document, with namespace resolver
Pass expression from HTML document, context node in HTML document, with namespace resolver
Pass expression from XML document, context node in HTML document, with namespace resolver
Fail expression from HTML document, context node in XML document, with namespace resolver

View File

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass concat() arguments depending on the context node

View File

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass contains() arguments depending on the context node

View File

@ -0,0 +1,14 @@
Harness status: OK
Found 8 tests
6 Pass
2 Fail
Pass id("test1"): <root><div id="test1">Match</div></root>
Pass id("test1 test2"): <root><div id="test1">First</div><div id="test2">Second</div></root>
Pass id("nonexistent"): <root><div id="test1">No match</div></root>
Pass id("Test1"): <root><div id="test1">No match</div></root>
Fail id("duplicate"): <root><div id="duplicate">First</div><div id="duplicate">Second</div></root>
Pass id("test-1"): <root><div id="test-1">Match</div></root>
Pass id(""): <root><div id="">Empty ID</div></root>
Fail id(" test1 "): <root><div id="test1">Match</div></root>

View File

@ -0,0 +1,13 @@
Harness status: OK
Found 7 tests
6 Pass
1 Fail
Pass lang("en"): <root><match xmlns:ns1="http://www.w3.org/XML/1998/namespace" ns1:lang="en"/></root>
Pass lang("en"): <root><match xmlns:ns1="http://www.w3.org/XML/1998/namespace" ns1:lang="EN"/></root>
Pass lang("en"): <root><match xmlns:ns1="http://www.w3.org/XML/1998/namespace" ns1:lang="en-us"/></root>
Pass lang("en"): <root><unmatch/></root>
Fail lang("ja"): <root xmlns:ns1="http://www.w3.org/XML/1998/namespace" ns1:lang="ja"><match/></root>
Pass lang("ja"): <root xmlns:ns1="http://www.w3.org/XML/1998/namespace" ns1:lang="ja-jp"><unmatch xmlns:ns2="http://www.w3.org/XML/1998/namespace" ns2:lang="ja_JP"/></root>
Pass lang("ko"): <root><unmatch xmlns:ns1="http://www.w3.org/XML/1998/namespace" ns1:lang="o"/></root>

View File

@ -0,0 +1,8 @@
Harness status: OK
Found 2 tests
1 Pass
1 Fail
Pass normalize-space() without arguments
Fail normalize-space() should handle only #x20, #x9, #xD, and #xA

View File

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass starts-with() arguments depending on the context node

View File

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass substring-after() arguments depending on the context node

View File

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass substring-before() arguments depending on the context node

View File

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass substring() arguments depending on the context node

View File

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass translate() arguments depending on the context node

View File

@ -0,0 +1,7 @@
Harness status: OK
Found 2 tests
2 Pass
Pass Literal: Only ' and " should be handled as literal quotes.
Pass ExprWhitespace: Only #x20 #x9 #xD or #xA must be handled as a whitespace.

View File

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass | operator should evaluate both sides of expressions with the same context node

View File

@ -0,0 +1,10 @@
Harness status: OK
Found 5 tests
5 Pass
Pass "+" operator depending on the context node
Pass "-" operator depending on the context node
Pass "*" operator depending on the context node
Pass "div" operator depending on the context node
Pass "mod" operator depending on the context node

View File

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass An expression in a predicate should not change the context node

View File

@ -0,0 +1,10 @@
Harness status: OK
Found 5 tests
5 Fail
Fail XPathNSResolver is cross-realm plain object without 'lookupNamespaceURI' property
Fail XPathNSResolver is cross-realm plain object with non-callable 'lookupNamespaceURI' property
Fail XPathNSResolver is cross-realm non-callable revoked Proxy
Fail XPathNSResolver is cross-realm callable revoked Proxy
Fail XPathNSResolver is cross-realm plain object with revoked Proxy as 'lookupNamespaceURI' property

View File

@ -0,0 +1,15 @@
Harness status: OK
Found 10 tests
10 Fail
Fail callable resolver
Fail callable resolver: result is not cached
Fail callable resolver: abrupt completion from Call
Fail callable resolver: no 'lookupNamespaceURI' lookups
Fail object resolver
Fail object resolver: this value and `prefix` argument
Fail object resolver: 'lookupNamespaceURI' is not cached
Fail object resolver: abrupt completion from Get
Fail object resolver: 'lookupNamespaceURI' is thruthy and not callable
Fail object resolver: 'lookupNamespaceURI' is falsy and not callable

View File

@ -0,0 +1,11 @@
Harness status: OK
Found 6 tests
6 Fail
Fail undefined
Fail null
Fail number
Fail boolean
Fail symbol
Fail object coercion (abrupt completion)

View File

@ -0,0 +1,13 @@
Harness status: OK
Found 7 tests
3 Pass
4 Fail
Pass Using an ordered iterator without modifying the dom should yield the expected elements in correct order without errors.
Pass Using an unordered iterator without modifying the dom should yield the correct number of elements without errors.
Pass invalidIteratorState should be false for non-iterable results.
Fail Calling iterateNext on a non-iterable XPathResult should throw a TypeError.
Fail Calling iterateNext on a non-iterable XPathResult after modifying the DOM should throw a TypeError.
Fail Calling iterateNext after having modified the DOM should throw an exception.
Fail Calling iterateNext after having modified the DOM should throw an exception even if the iterator is exhausted.

View File

@ -0,0 +1,21 @@
Harness status: OK
Found 15 tests
10 Pass
5 Fail
Pass Select html element based on attribute
Fail Select html element based on attribute mixed case
Pass Select both HTML and SVG elements based on attribute
Pass Select HTML element with non-ascii attribute 1
Pass Select HTML element with non-ascii attribute 2
Fail Select HTML element with non-ascii attribute 3
Pass Select SVG element based on mixed case attribute
Fail Select both HTML and SVG elements based on mixed case attribute
Pass Select SVG elements with refX attribute
Pass Select SVG elements with refX attribute incorrect case
Pass Select SVG elements with refX attribute lowercase
Pass Select SVG element with non-ascii attribute 1
Pass Select SVG element with non-ascii attribute 2
Fail xmlns attribute
Fail svg element with XLink attribute

View File

@ -0,0 +1,17 @@
Harness status: OK
Found 11 tests
4 Pass
7 Fail
Pass HTML elements no namespace prefix
Fail HTML elements namespace prefix
Fail HTML elements mixed use of prefix
Fail SVG elements no namespace prefix
Fail SVG elements namespace prefix
Fail HTML elements mixed case
Pass SVG elements mixed case selector
Pass Non-ascii HTML element
Pass Non-ascii HTML element2
Fail Non-ascii HTML element3
Fail Throw with invalid prefix

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
Harness status: OK
Found 4 tests
4 Pass
Pass createNSResolver() should return the specified node as is. (HTMLDocument)
Pass createNSResolver() resultant object should not add support of 'xml' prefix. (HTMLDocument)
Pass createNSResolver() should return the specified node as is. (XPathEvaluator)
Pass createNSResolver() resultant object should not add support of 'xml' prefix. (XPathEvaluator)

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#booleans">
<body>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="helpers.js"></script>
<div id="context"></div>
<script>
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_true(evaluateBoolean('(./span)[4] or ./br[2]', context));
}, '"or" operator depending on the context node');
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_true(evaluateBoolean('count((./span)[3]) = count(./br[2])', context));
}, '"=" operator depending on the context node');
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_false(evaluateBoolean('count((./span)[3]) != count(./br[2])', context));
}, '"!=" operator depending on the context node');
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_true(evaluateBoolean('count((./span)[3]) < count(./br)', context));
}, '"<" operator depending on the context node');
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_false(evaluateBoolean('count((./span)[3]) > count(./br[2])', context));
}, '">" operator depending on the context node');
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_false(evaluateBoolean('count((./span)[3]) >= count(./br)', context));
}, '">=" operator depending on the context node');
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_true(evaluateBoolean('count((./span)[3]) <= count(./br[2])', context));
}, '"<=" operator depending on the context node');
</script>
</body>

View File

@ -0,0 +1,31 @@
<!doctype html>
<title>XPath parent of documentElement</title>
<script src='../resources/testharness.js'></script>
<script src='../resources/testharnessreport.js'></script>
<body>
<script>
test(function() {
var result = document.evaluate("..", // expression
document.documentElement, // context node
null, // resolver
XPathResult.ANY_TYPE, // type
null); // result
var matched = [];
var cur;
while ((cur = result.iterateNext()) !== null) {
matched.push(cur);
}
assert_array_equals(matched, [document]);
// Evaluate again, but reuse result from previous evaluation.
result = document.evaluate("..", // expression
document.documentElement, // context node
null, // resolver
XPathResult.ANY_TYPE, // type
result); // result
matched = [];
while ((cur = result.iterateNext()) !== null) {
matched.push(cur);
}
assert_array_equals(matched, [document]);
});
</script>

View File

@ -0,0 +1,16 @@
<!doctype html>
<meta charset=utf-8>
<title>XPathEvaluator constructor</title>
<link rel=help href="http://wiki.whatwg.org/wiki/DOM_XPath">
<script src=../resources/testharness.js></script>
<script src=../resources/testharnessreport.js></script>
<div id=log></div>
<script>
test(function() {
var x = new XPathEvaluator();
assert_true(x instanceof XPathEvaluator);
}, "Constructor with 'new'");
test(function() {
assert_throws_js(TypeError, "var x = XPathEvaluator()");
}, "Constructor without 'new'");
</script>

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Cross-realm XPath evaluator</title>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<script>
function toArray(result) {
var a = [];
while (true) {
var node = result.iterateNext();
if (node === null) break;
a.push(node);
}
return a;
}
var html_ns = "http://www.w3.org/1999/xhtml";
var xml_doc = document.implementation.createDocument(html_ns, "html");
var html_doc = document.implementation.createHTMLDocument();
promise_test(async (t) => {
const iframe = document.createElement("iframe");
iframe.src = "/common/dummy.xml";
document.body.appendChild(iframe);
await new Promise(resolve => {
iframe.addEventListener("load", resolve, {once: true});
});
t.add_cleanup(() => iframe.remove());
const iframe_evaluator = new iframe.contentWindow.XPathEvaluator();
assert_array_equals(toArray(iframe_evaluator.evaluate("//html", xml_doc)), []);
}, "evaluator from realm with XML associated document, context node in XML document, no namespace resolver");
promise_test(async (t) => {
const iframe = document.createElement("iframe");
iframe.src = "/common/dummy.xml";
document.body.appendChild(iframe);
await new Promise(resolve => {
iframe.addEventListener("load", resolve, {once: true});
});
t.add_cleanup(() => iframe.remove());
const iframe_evaluator = new iframe.contentWindow.XPathEvaluator();
assert_array_equals(toArray(iframe_evaluator.evaluate("//html", html_doc)), [html_doc.documentElement]);
}, "evaluator from realm with XML associated document, context node in HTML document, no namespace resolver");
promise_test(async (t) => {
const iframe = document.createElement("iframe");
iframe.src = "/common/blank.html";
document.body.appendChild(iframe);
await new Promise(resolve => {
iframe.addEventListener("load", resolve, {once: true});
});
t.add_cleanup(() => iframe.remove());
const iframe_evaluator = new iframe.contentWindow.XPathEvaluator();
assert_array_equals(toArray(iframe_evaluator.evaluate("//html", xml_doc)), []);
}, "evaluator from realm with HTML associated document, context node in XML document, no namespace resolver");
promise_test(async (t) => {
const iframe = document.createElement("iframe");
iframe.src = "/common/blank.html";
document.body.appendChild(iframe);
await new Promise(resolve => {
iframe.addEventListener("load", resolve, {once: true});
});
t.add_cleanup(() => iframe.remove());
const iframe_evaluator = new iframe.contentWindow.XPathEvaluator();
assert_array_equals(toArray(iframe_evaluator.evaluate("//html", html_doc)), [html_doc.documentElement]);
}, "evaluator from realm with HTML associated document, context node in HTML document, no namespace resolver");
</script>

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Cross-document XPath evaluation</title>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<script>
function toArray(result) {
var a = [];
while (true) {
var node = result.iterateNext();
if (node === null) break;
a.push(node);
}
return a;
}
var html_ns = "http://www.w3.org/1999/xhtml";
var xml_doc = document.implementation.createDocument(html_ns, "html");
var html_doc = document.implementation.createHTMLDocument();
function ns_resolver(x) {
if (x === "html") {
return html_ns;
} else {
return null;
}
}
test(function() {
assert_array_equals(toArray(xml_doc.evaluate("//html", xml_doc)), []);
}, "evaluate operation on XML document, context node in XML document, no namespace resolver");
test(function() {
assert_array_equals(toArray(html_doc.evaluate("//html", html_doc)), [html_doc.documentElement]);
}, "evaluate operation on HTML document, context node in HTML document, no namespace resolver");
test(function() {
assert_array_equals(toArray(xml_doc.evaluate("//html", html_doc)), [html_doc.documentElement]);
}, "evaluate operation on XML document, context node in HTML document, no namespace resolver");
test(function() {
assert_array_equals(toArray(html_doc.evaluate("//html", xml_doc)), []);
}, "evaluate operation on HTML document, context node in XML document, no namespace resolver");
test(function() {
assert_array_equals(toArray(xml_doc.evaluate("//html", xml_doc, ns_resolver)), []);
}, "evaluate operation on XML document, context node in XML document, with namespace resolver");
test(function() {
assert_array_equals(toArray(html_doc.evaluate("//html", html_doc, ns_resolver)), [html_doc.documentElement]);
}, "evaluate operation on HTML document, context node in HTML document, with namespace resolver");
test(function() {
assert_array_equals(toArray(xml_doc.evaluate("//html", html_doc, ns_resolver)), [html_doc.documentElement]);
}, "evaluate operation on XML document, context node in HTML document, with namespace resolver");
test(function() {
assert_array_equals(toArray(html_doc.evaluate("//html", xml_doc, ns_resolver)), []);
}, "evaluate operation on HTML document, context node in XML document, with namespace resolver");
</script>

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Cross-realm XPath evaluator</title>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<script>
function toArray(result) {
var a = [];
while (true) {
var node = result.iterateNext();
if (node === null) break;
a.push(node);
}
return a;
}
var html_ns = "http://www.w3.org/1999/xhtml";
var xml_doc = document.implementation.createDocument(html_ns, "html");
var html_doc = document.implementation.createHTMLDocument();
promise_test(async (t) => {
const iframe = document.createElement("iframe");
iframe.src = "/common/dummy.xml";
document.body.appendChild(iframe);
await new Promise(resolve => {
iframe.addEventListener("load", resolve, {once: true});
});
t.add_cleanup(() => iframe.remove());
const iframe_expression = iframe.contentDocument.createExpression("//html");
assert_array_equals(toArray(iframe_expression.evaluate(xml_doc)), []);
}, "expression from realm with XML associated document, context node in XML document, no namespace resolver");
promise_test(async (t) => {
const iframe = document.createElement("iframe");
iframe.src = "/common/dummy.xml";
document.body.appendChild(iframe);
await new Promise(resolve => {
iframe.addEventListener("load", resolve, {once: true});
});
t.add_cleanup(() => iframe.remove());
const iframe_expression = iframe.contentDocument.createExpression("//html");
assert_array_equals(toArray(iframe_expression.evaluate(html_doc)), [html_doc.documentElement]);
}, "expression from realm with XML associated document, context node in HTML document, no namespace resolver");
promise_test(async (t) => {
const iframe = document.createElement("iframe");
iframe.src = "/common/blank.html";
document.body.appendChild(iframe);
await new Promise(resolve => {
iframe.addEventListener("load", resolve, {once: true});
});
t.add_cleanup(() => iframe.remove());
const iframe_expression = iframe.contentDocument.createExpression("//html");
assert_array_equals(toArray(iframe_expression.evaluate(xml_doc)), []);
}, "expression from realm with HTML associated document, context node in XML document, no namespace resolver");
promise_test(async (t) => {
const iframe = document.createElement("iframe");
iframe.src = "/common/blank.html";
document.body.appendChild(iframe);
await new Promise(resolve => {
iframe.addEventListener("load", resolve, {once: true});
});
t.add_cleanup(() => iframe.remove());
const iframe_expression = iframe.contentDocument.createExpression("//html");
assert_array_equals(toArray(iframe_expression.evaluate(html_doc)), [html_doc.documentElement]);
}, "expression from realm with HTML associated document, context node in HTML document, no namespace resolver");
</script>

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Cross-document XPath expressions</title>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<script>
function toArray(result) {
var a = [];
while (true) {
var node = result.iterateNext();
if (node === null) break;
a.push(node);
}
return a;
}
var html_ns = "http://www.w3.org/1999/xhtml";
var xml_doc = document.implementation.createDocument(html_ns, "html");
var html_doc = document.implementation.createHTMLDocument();
function ns_resolver(x) {
if (x === "html") {
return html_ns;
} else {
return null;
}
}
test(function() {
var xml_doc_expression = xml_doc.createExpression("//html");
assert_array_equals(toArray(xml_doc_expression.evaluate(xml_doc)), []);
}, "expression from XML document, context node in XML document, no namespace resolver");
test(function() {
var html_doc_expression = html_doc.createExpression("//html");
assert_array_equals(toArray(html_doc_expression.evaluate(html_doc)), [html_doc.documentElement]);
}, "expression from HTML document, context node in HTML document, no namespace resolver");
test(function() {
var xml_doc_expression = xml_doc.createExpression("//html");
assert_array_equals(toArray(xml_doc_expression.evaluate(html_doc)), [html_doc.documentElement]);
}, "expression from XML document, context node in HTML document, no namespace resolver");
test(function() {
var html_doc_expression = html_doc.createExpression("//html");
assert_array_equals(toArray(html_doc_expression.evaluate(xml_doc)), []);
}, "expression from HTML document, context node in XML document, no namespace resolver");
test(function() {
var xml_doc_expression = xml_doc.createExpression("//html", ns_resolver);
assert_array_equals(toArray(xml_doc_expression.evaluate(xml_doc)), []);
}, "expression from XML document, context node in XML document, with namespace resolver");
test(function() {
var html_doc_expression = html_doc.createExpression("//html", ns_resolver);
assert_array_equals(toArray(html_doc_expression.evaluate(html_doc)), [html_doc.documentElement]);
}, "expression from HTML document, context node in HTML document, with namespace resolver");
test(function() {
var xml_doc_expression = xml_doc.createExpression("//html", ns_resolver);
assert_array_equals(toArray(xml_doc_expression.evaluate(html_doc)), [html_doc.documentElement]);
}, "expression from XML document, context node in HTML document, with namespace resolver");
test(function() {
var html_doc_expression = html_doc.createExpression("//html", ns_resolver);
assert_array_equals(toArray(html_doc_expression.evaluate(xml_doc)), []);
}, "expression from HTML document, context node in XML document, with namespace resolver");
</script>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#function-concat">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="helpers.js"></script>
<body>
<div id="context"></div>
<script>
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span>foo</span><span>bar</span><b>ber</b>';
assert_equals(evaluateString('concat((./span)[2], ./b)', context), 'barber');
}, 'concat() arguments depending on the context node');
</script>
</body>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#function-contains">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="helpers.js"></script>
<body>
<div id="context"></div>
<script>
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span>bar bar</span><span>bar<b>ber</b></span><b>bar</b>';
assert_true(evaluateBoolean('contains((./span)[1], ./b)', context));
}, 'contains() arguments depending on the context node');
</script>
</body>

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#function-id">
<body>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script>
// Test the id() function with various scenarios
function testIdFunction(expression, xmlString, expectedIds) {
let doc = (new DOMParser()).parseFromString(xmlString, 'text/xml');
test(() => {
let result = doc.evaluate(expression, doc.documentElement, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
assert_equals(result.resultType, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
let actualIds = [];
for (let i = 0; i < result.snapshotLength; i++) {
actualIds.push(result.snapshotItem(i).getAttribute('id'));
}
actualIds.sort();
expectedIds.sort();
assert_array_equals(actualIds, expectedIds, `Expected IDs ${expectedIds}, got ${actualIds}`);
}, `${expression}: ${doc.documentElement.outerHTML}`);
}
// Test single ID
testIdFunction('id("test1")', '<root><div id="test1">Match</div></root>', ['test1']);
// Test multiple IDs in space-separated string
testIdFunction('id("test1 test2")', '<root><div id="test1">First</div><div id="test2">Second</div></root>', ['test1', 'test2']);
// Test non-existent ID
testIdFunction('id("nonexistent")', '<root><div id="test1">No match</div></root>', []);
// Test mixed case IDs (should be case-sensitive)
testIdFunction('id("Test1")', '<root><div id="test1">No match</div></root>', []);
// Test multiple elements with same ID (should return all)
testIdFunction('id("duplicate")', '<root><div id="duplicate">First</div><div id="duplicate">Second</div></root>', ['duplicate', 'duplicate']);
// Test IDs with special characters
testIdFunction('id("test-1")', '<root><div id="test-1">Match</div></root>', ['test-1']);
// Test empty ID string
testIdFunction('id("")', '<root><div id="">Empty ID</div></root>', []);
// Test whitespace in ID string
testIdFunction('id(" test1 ")', '<root><div id="test1">Match</div></root>', ['test1']);
</script>
</body>

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#function-lang">
<body>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script>
// Set the context node to the first child of the root element, and evaluate
// the specified XPath expression. The test passes if
// - The first child element name is 'match' and XPath result is true, or
// - The first child element name is not 'match' and XPath result is false.
function testFirstChild(expression, xmlString) {
let doc = (new DOMParser()).parseFromString(xmlString, 'text/xml');
test(() => {
let element = doc.documentElement.firstChild;
let result = doc.evaluate(expression, element, null, XPathResult.BOOLEAN_TYPE, null);
assert_equals(result.resultType, XPathResult.BOOLEAN_TYPE);
assert_equals(result.booleanValue, element.localName == 'match', element.outerHTML);
}, `${expression}: ${doc.documentElement.outerHTML}`);
}
testFirstChild('lang("en")', '<root><match xml:lang="en"/></root>');
testFirstChild('lang("en")', '<root><match xml:lang="EN"/></root>');
testFirstChild('lang("en")', '<root><match xml:lang="en-us"/></root>');
testFirstChild('lang("en")', '<root><unmatch/></root>');
// XPath 1.0 says:
// if the context node has no xml:lang attribute, by the value of the
// xml:lang attribute on the nearest ancestor of the context node that has
// an xml:lang attribute.
testFirstChild('lang("ja")', '<root xml:lang="ja"><match/></root>');
// XPath 1.0 says:
// if there is some suffix starting with - such that the attribute value is
// equal to the argument ignoring that suffix of the attribute value
testFirstChild('lang("ja")', '<root xml:lang="ja-jp"><unmatch xml:lang="ja_JP"/></root>');
// XPath 3.1 is not to be followed as per: https://github.com/whatwg/dom/issues/1199
testFirstChild('lang("ko")', '<root><unmatch xml:lang="&#x212A;o"/></root>');
</script>
</body>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#function-normalize-space">
<link rel="help" href="https://www.w3.org/TR/xpath-functions-31/#func-normalize-space">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<div id="target"> a <br> b</div>
<script>
function normalizeSpace(exp) {
return document.evaluate(`normalize-space("${exp}")`, document).stringValue;
}
test(() => {
assert_equals(document.evaluate('normalize-space()', document.querySelector('#target')).stringValue, 'a b');
}, 'normalize-space() without arguments');
test(() => {
assert_equals(normalizeSpace(' a \t b\r\nc '), 'a b c');
assert_equals(normalizeSpace('y\x0B\x0C\x0E\x0Fz'), 'y\x0b\x0c\x0e\x0fz');
assert_equals(normalizeSpace('\xA0 \u3000'), '\xA0 \u3000');
}, 'normalize-space() should handle only #x20, #x9, #xD, and #xA');
</script>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#function-starts-with">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="helpers.js"></script>
<body>
<div id="context"></div>
<script>
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span>foo</span><span>bar<b>ber</b></span><b>bar</b>';
assert_true(evaluateBoolean('starts-with((./span)[2], ./b)', context));
}, 'starts-with() arguments depending on the context node');
</script>
</body>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#function-substring-after">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="helpers.js"></script>
<body>
<div id="context"></div>
<script>
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span>^^^bar$$$</span><span>bar<b>^</b></span><b>bar</b>';
assert_equals(evaluateString('substring-after((./span)[1], ./b)', context), '$$$');
}, 'substring-after() arguments depending on the context node');
</script>
</body>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#function-substring-before">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="helpers.js"></script>
<body>
<div id="context"></div>
<script>
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span>^^^bar$$$</span><span>bar<b>$</b></span><b>bar</b>';
assert_equals(evaluateString('substring-before((./span)[1], ./b)', context), '^^^');
}, 'substring-before() arguments depending on the context node');
</script>
</body>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#function-substring">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="helpers.js"></script>
<body>
<div id="context"></div>
<script>
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span>^^^bar$$$</span><span></span><br><br><br><br>';
assert_equals(evaluateString('substring((./span)[1], count(./br))', context), 'bar$$$');
}, 'substring() arguments depending on the context node');
</script>
</body>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#function-translate">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="helpers.js"></script>
<body>
<div id="context"></div>
<script>
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span>^^^bar$$$</span><span><b>^^^</b></span><b>bar</b><b>foo</b>';
assert_equals(evaluateString('translate((./span)[1], (./b)[1], ./b[2])', context), '^^^foo$$$');
}, 'translate() arguments depending on the context node');
</script>
</body>

View File

@ -0,0 +1,14 @@
function evaluateBoolean(expression, context) {
let doc = context.ownerDocument || context;
return doc.evaluate(expression, context, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
}
function evaluateNumber(expression, context) {
let doc = context.ownerDocument || context;
return doc.evaluate(expression, context, null, XPathResult.NUMBER_TYPE, null).numberValue;
}
function evaluateString(expression, context) {
let doc = context.ownerDocument || context;
return doc.evaluate(expression, context, null, XPathResult.STRING_TYPE, null).stringValue;
}

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#exprlex">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<script>
function parse(expression) {
document.evaluate(expression, document, null, XPathResult.ANY_TYPE, null);
}
// https://www.w3.org/TR/1999/REC-xpath-19991116/#NT-Literal
test(() => {
parse(' \'a"bc\' ');
parse(' "a\'bc" ');
assert_throws_dom('SyntaxError', () => { parse(' \u2019xyz\u2019 '); });
}, 'Literal: Only \' and " should be handled as literal quotes.');
// https://www.w3.org/TR/1999/REC-xpath-19991116/#NT-ExprWhitespace
test(() => {
parse(' \t\r\n.\r\n\t ');
assert_throws_dom('SyntaxError', () => { parse('\x0B\x0C .'); });
assert_throws_dom('SyntaxError', () => { parse('\x0E\x0F .'); });
assert_throws_dom('SyntaxError', () => { parse('\u3000 .'); });
assert_throws_dom('SyntaxError', () => { parse('\u2029 .'); });
}, 'ExprWhitespace: Only #x20 #x9 #xD or #xA must be handled as a whitespace.');
</script>
</body>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#node-sets">
<body>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script>
function nodesetToSet(result) {
const set = new Set();
for (let node = result.iterateNext(); node; node = result.iterateNext()) {
set.add(node);
}
return set;
}
test(() => {
const doc = document.implementation.createHTMLDocument();
doc.documentElement.innerHTML = '<body><div></div></body>';
const result = nodesetToSet(doc.evaluate('(.//div)[1]|.', doc.documentElement));
assert_equals(result.size, 2);
assert_true(result.has(doc.documentElement));
assert_true(result.has(doc.body.firstChild));
}, '| operator should evaluate both sides of expressions with the same context node');
</script>
</body>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#numbers">
<body>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="helpers.js"></script>
<div id="context"></div>
<script>
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_equals(evaluateNumber('count((./span)[1]) + count(./br)', context), 3);
}, '"+" operator depending on the context node');
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_equals(evaluateNumber('count((./span)[1]) - count(./br)', context), -1);
}, '"-" operator depending on the context node');
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_equals(evaluateNumber('count((./span)[1]) * count(./br)', context), 2);
}, '"*" operator depending on the context node');
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_equals(evaluateNumber('count((./span)[1]) div count(./br)', context), 0.5);
}, '"div" operator depending on the context node');
test(() => {
const context = document.querySelector('#context');
context.innerHTML = '<span></span><span></span><span></span><br><br>';
assert_equals(evaluateNumber('count((./span)[1]) mod count(./br)', context), 1);
}, '"mod" operator depending on the context node');
</script>
</body>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/1999/REC-xpath-19991116/#predicates">
<body>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script>
function nodesetToSet(result) {
const set = new Set();
for (let node = result.iterateNext(); node; node = result.iterateNext()) {
set.add(node);
}
return set;
}
test(() => {
const doc = document.implementation.createHTMLDocument();
doc.body.innerHTML = '<table></table>' +
'<table><tr><th><th><th><th></table>' +
'<table></table>';
const result = nodesetToSet(doc.evaluate('(//table)[count((//table)[2]/descendant::th)-1]', doc.documentElement));
assert_equals(result.size, 1);
assert_true(result.has(doc.body.lastChild));
}, 'An expression in a predicate should not change the context node');
</script>
</body>

View File

@ -0,0 +1,94 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Cross-realm XPathNSResolver throws TypeError of its associated Realm</title>
<link rel="help" href="https://webidl.spec.whatwg.org/#ref-for-prepare-to-run-script">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<iframe name="evaluateGlobalObject" src="resources/empty-document.html"></iframe>
<iframe name="resolverGlobalObject" src="resources/empty-document.html"></iframe>
<iframe name="lookupNamespaceURIGlobalObject" src="resources/empty-document.html"></iframe>
<iframe name="relevantGlobalObject" src="resources/empty-document.html"></iframe>
<iframe name="incumbentGlobalObject" src="resources/empty-document.html"></iframe>
<script>
setup({ allow_uncaught_exception: true });
const expectedDOMExceptionType = "NAMESPACE_ERR";
test_onload(() => {
const resolver = new resolverGlobalObject.Object;
assert_reports_exception(() => {
assert_throws_dom(expectedDOMExceptionType, evaluateGlobalObject.DOMException, bind_evaluate(resolver));
});
}, "XPathNSResolver is cross-realm plain object without 'lookupNamespaceURI' property");
test_onload(() => {
const resolver = new resolverGlobalObject.Object;
resolver.lookupNamespaceURI = new lookupNamespaceURIGlobalObject.Object;
assert_reports_exception(() => {
assert_throws_dom(expectedDOMExceptionType, evaluateGlobalObject.DOMException, bind_evaluate(resolver));
});
}, "XPathNSResolver is cross-realm plain object with non-callable 'lookupNamespaceURI' property");
test_onload(() => {
const { proxy, revoke } = resolverGlobalObject.Proxy.revocable(new resolverGlobalObject.Object, {});
revoke();
assert_reports_exception(() => {
assert_throws_dom(expectedDOMExceptionType, evaluateGlobalObject.DOMException, bind_evaluate(proxy));
});
}, "XPathNSResolver is cross-realm non-callable revoked Proxy");
test_onload(() => {
const { proxy, revoke } = resolverGlobalObject.Proxy.revocable(new resolverGlobalObject.Function, {});
revoke();
assert_reports_exception(() => {
assert_throws_dom(expectedDOMExceptionType, evaluateGlobalObject.DOMException, bind_evaluate(proxy));
});
}, "XPathNSResolver is cross-realm callable revoked Proxy");
test_onload(() => {
const { proxy, revoke } = lookupNamespaceURIGlobalObject.Proxy.revocable(new lookupNamespaceURIGlobalObject.Function, {});
revoke();
const resolver = new resolverGlobalObject.Object;
resolver.lookupNamespaceURI = proxy;
assert_reports_exception(() => {
assert_throws_dom(expectedDOMExceptionType, evaluateGlobalObject.DOMException, bind_evaluate(resolver));
});
}, "XPathNSResolver is cross-realm plain object with revoked Proxy as 'lookupNamespaceURI' property");
function test_onload(fn, desc) {
async_test(t => { window.addEventListener("load", t.step_func_done(fn)); }, desc);
}
function assert_reports_exception(fn) {
let error;
const onErrorHandler = event => {
error = event.error;
event.preventDefault();
};
resolverGlobalObject.addEventListener("error", onErrorHandler);
fn();
resolverGlobalObject.removeEventListener("error", onErrorHandler);
assert_equals(typeof error, "object");
assert_equals(error.constructor, evaluateGlobalObject.TypeError);
}
function bind_evaluate(resolver) {
const boundEvaluate = new incumbentGlobalObject.Function("evaluate", "relevantDocument", "resolver", `
evaluate.call(relevantDocument, "/foo:bar", relevantDocument.documentElement, resolver);
`);
return () => {
boundEvaluate(evaluateGlobalObject.document.evaluate, relevantGlobalObject.document, resolver);
};
}
</script>

View File

@ -0,0 +1,146 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>XPathNSResolver implements callback interface</title>
<link rel="help" href="https://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#XPathEvaluator">
<link rel="help" href="https://webidl.spec.whatwg.org/#call-a-user-objects-operation">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="resources/invalid_namespace_test.js"></script>
<div id=log></div>
<script>
"use strict";
test(() => {
let resolverCalls = 0;
document.evaluate("/foo:bar", document.documentElement, () => {
resolverCalls++;
return "";
});
assert_equals(resolverCalls, 1);
}, "callable resolver");
test(() => {
let resolverCalls = 0;
const resolver = () => {
resolverCalls++;
return "";
};
document.evaluate("/foo:bar", document.documentElement, resolver);
document.evaluate("/foo:bar", document.documentElement, resolver);
assert_equals(resolverCalls, 2);
}, "callable resolver: result is not cached");
promise_test(t => {
const testError = { name: "test" };
const resolver = () => {
throw testError;
};
return promise_rejects_exactly(t, testError,
invalid_namespace_test(t, resolver)
);
}, "callable resolver: abrupt completion from Call");
test(() => {
let resolverCalls = 0;
const resolver = () => {
resolverCalls++;
return "";
};
let resolverGets = 0;
Object.defineProperty(resolver, "lookupNamespaceURI", {
get() {
resolverGets++;
},
});
document.evaluate("/foo:bar", document.documentElement, resolver);
assert_equals(resolverCalls, 1);
assert_equals(resolverGets, 0);
}, "callable resolver: no 'lookupNamespaceURI' lookups");
test(() => {
let resolverCalls = 0;
document.evaluate("/foo:bar", document.documentElement, {
lookupNamespaceURI() {
resolverCalls++;
return "";
},
});
assert_equals(resolverCalls, 1);
}, "object resolver");
test(() => {
let thisValue, prefixArg;
const resolver = {
lookupNamespaceURI(prefix) {
thisValue = this;
prefixArg = prefix;
return "";
},
};
document.evaluate("/foo:bar", document.documentElement, resolver);
assert_equals(thisValue, resolver);
assert_equals(prefixArg, "foo");
}, "object resolver: this value and `prefix` argument");
test(() => {
let resolverCalls = 0;
const lookupNamespaceURI = () => {
resolverCalls++;
return "";
};
let resolverGets = 0;
const resolver = {
get lookupNamespaceURI() {
resolverGets++;
return lookupNamespaceURI;
},
};
document.evaluate("/foo:bar", document.documentElement, resolver);
document.evaluate("/foo:bar", document.documentElement, resolver);
assert_equals(resolverCalls, 2);
assert_equals(resolverGets, 2);
}, "object resolver: 'lookupNamespaceURI' is not cached");
promise_test(t => {
const testError = { name: "test" };
const resolver = {
get lookupNamespaceURI() {
throw testError;
},
};
return promise_rejects_exactly(t, testError,
invalid_namespace_test(t, resolver)
);
}, "object resolver: abrupt completion from Get");
promise_test(t => {
const resolver = {
lookupNamespaceURI: {},
};
return promise_rejects_js(t, TypeError,
invalid_namespace_test(t, resolver)
);
}, "object resolver: 'lookupNamespaceURI' is thruthy and not callable");
promise_test(t => {
return promise_rejects_js(t, TypeError,
invalid_namespace_test(t, {})
);
}, "object resolver: 'lookupNamespaceURI' is falsy and not callable");
</script>
</body>

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>XPathNSResolver non-string return value</title>
<link rel="help" href="https://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#XPathEvaluator">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="resources/invalid_namespace_test.js"></script>
<div id=log></div>
<script>
"use strict";
promise_test(t => {
return invalid_namespace_test(t, () => undefined);
}, "undefined");
promise_test(t => {
return invalid_namespace_test(t, () => null);
}, "null");
test(t => {
let resolverCalls = 0;
document.evaluate("/foo:bar", document.documentElement, () => {
resolverCalls++;
return 0;
});
assert_equals(resolverCalls, 1);
}, "number");
test(t => {
let resolverCalls = 0;
document.evaluate("/foo:bar", document.documentElement, () => {
resolverCalls++;
return false;
});
assert_equals(resolverCalls, 1);
}, "boolean");
promise_test(t => {
return promise_rejects_js(t, TypeError,
invalid_namespace_test(t, () => Symbol())
);
}, "symbol");
promise_test(t => {
const testError = { name: "test" };
const resolverResult = {
toString: () => { throw testError; },
valueOf: t.unreached_func("`valueOf` should not be called."),
};
return promise_rejects_exactly(t, testError,
invalid_namespace_test(t, () => resolverResult)
);
}, "object coercion (abrupt completion)");
</script>
</body>

View File

@ -0,0 +1,3 @@
<!DOCTYPE html>
<meta charset="utf-8">
<body>

View File

@ -0,0 +1,23 @@
"use strict";
setup({ allow_uncaught_exception: true });
const invalid_namespace_test = (t, resolver, resolverWindow = window) => {
const result = new Promise((resolve, reject) => {
const handler = event => {
reject(event.error);
};
resolverWindow.addEventListener("error", handler);
t.add_cleanup(() => {
resolverWindow.removeEventListener("error", handler);
});
t.step_timeout(resolve, 0);
});
assert_throws_dom("NAMESPACE_ERR", () => {
document.evaluate("/foo:bar", document.documentElement, resolver);
});
return result;
};

View File

@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>
<head>
<title>Invalidation of iterators over XPath results</title>
<link rel="author" title="Simon Wülker" href="mailto:simon.wuelker@arcor.de">
<link rel="help" href="https://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#XPathResult-iterateNext">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
</head>
<body>
<ul id="list">
<li id="first-child"></li>
<li id="second-child"></li>
</ul>
<script>
function make_xpath_query(result_type) {
return document.evaluate(
"//li",
document,
null,
result_type,
null
);
}
function invalidate_iterator(test) {
let new_element = document.createElement("li");
document.getElementById("list").appendChild(new_element);
test.add_cleanup(() => {
new_element.remove();
})
}
test((t) => {
let iterator = make_xpath_query(XPathResult.ORDERED_NODE_ITERATOR_TYPE);
assert_equals(iterator.iterateNext(), document.getElementById("first-child"));
assert_equals(iterator.iterateNext(), document.getElementById("second-child"));
assert_equals(iterator.iterateNext(), null);
assert_false(iterator.invalidIteratorState);
}, "Using an ordered iterator without modifying the dom should yield the expected elements in correct order without errors.");
test((t) => {
let iterator = make_xpath_query(XPathResult.UNORDERED_NODE_ITERATOR_TYPE);
assert_not_equals(iterator.iterateNext(), null);
assert_not_equals(iterator.iterateNext(), null);
assert_equals(iterator.iterateNext(), null);
assert_false(iterator.invalidIteratorState);
}, "Using an unordered iterator without modifying the dom should yield the correct number of elements without errors.");
test((t) => {
let non_iterator_query = make_xpath_query(XPathResult.BOOLEAN_TYPE);
assert_false(non_iterator_query.invalidIteratorState);
invalidate_iterator(t);
assert_false(non_iterator_query.invalidIteratorState);
}, "invalidIteratorState should be false for non-iterable results.");
test((t) => {
let non_iterator_query = make_xpath_query(XPathResult.BOOLEAN_TYPE);
assert_throws_js(TypeError, () => non_iterator_query.iterateNext());
}, "Calling iterateNext on a non-iterable XPathResult should throw a TypeError.");
test((t) => {
let non_iterator_query = make_xpath_query(XPathResult.BOOLEAN_TYPE);
invalidate_iterator(t);
assert_throws_js(TypeError, () => non_iterator_query.iterateNext());
}, "Calling iterateNext on a non-iterable XPathResult after modifying the DOM should throw a TypeError.");
test((t) => {
let iterator = make_xpath_query(XPathResult.ORDERED_NODE_ITERATOR_TYPE);
iterator.iterateNext();
invalidate_iterator(t);
assert_throws_dom(
"InvalidStateError",
() => iterator.iterateNext(),
);
}, "Calling iterateNext after having modified the DOM should throw an exception.");
test((t) => {
let iterator = make_xpath_query(XPathResult.ORDERED_NODE_ITERATOR_TYPE);
iterator.iterateNext();
iterator.iterateNext();
invalidate_iterator(t);
assert_throws_dom(
"InvalidStateError",
() => iterator.iterateNext(),
);
}, "Calling iterateNext after having modified the DOM should throw an exception even if the iterator is exhausted.");
</script>
</body>
</html>

View File

@ -0,0 +1,102 @@
<!doctype html>
<meta charset="utf8">
<title>XPath in text/html: attributes</title>
<link rel="help" href="http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#Interfaces">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<div id="log" nonÄsciiAttribute><span></span></div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path id="a" refx />
<path id="b" nonÄscii xlink:href />
</svg>
<script>
function test_xpath_succeeds(path, expected, resolver) {
resolver = resolver ? resolver : null;
var res = document.evaluate(path, document, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
actual = [];
for (var i=0;;i++) {
var node = res.snapshotItem(i);
if (node === null) {
break;
}
actual.push(node);
}
assert_array_equals(actual, expected);
}
function test_xpath_throws(path, error_code, resolver) {
resolver = resolver ? resolver : null;
assert_throws_dom(error_code, function() {document.evaluate(path, document, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)})
}
function ns_resolver(x) {
map = {"html":"http://www.w3.org/1999/xhtml",
"svg":"http://www.w3.org/2000/svg",
"math":"http://www.w3.org/1998/Math/MathML",
"xlink":"http://www.w3.org/1999/xlink"};
var rv = map.hasOwnProperty(x) ? map[x] : null;
return rv;
}
test(function() {
test_xpath_succeeds("//div[@id='log']", [document.getElementById("log")]);
}, "Select html element based on attribute");
test(function() {
test_xpath_succeeds("//div[@Id='log']", [document.getElementById("log")]);
}, "Select html element based on attribute mixed case");
test(function() {
test_xpath_succeeds("//*[@id]", [document.getElementById("log")].concat(Array.prototype.slice.call(document.getElementsByTagName("path"))));
}, "Select both HTML and SVG elements based on attribute");
test(function() {
test_xpath_succeeds("//*[@nonÄsciiattribute]", [document.getElementById("log")]);
}, "Select HTML element with non-ascii attribute 1");
test(function() {
test_xpath_succeeds("//*[@nonäsciiattribute]", []);
}, "Select HTML element with non-ascii attribute 2");
test(function() {
test_xpath_succeeds("//*[@nonÄsciiAttribute]", [document.getElementById("log")]);
}, "Select HTML element with non-ascii attribute 3");
test(function() {
test_xpath_succeeds("//svg:path[@Id]", [], ns_resolver);
}, "Select SVG element based on mixed case attribute");
test(function() {
test_xpath_succeeds("//*[@Id]", [document.getElementById("log")]);
}, "Select both HTML and SVG elements based on mixed case attribute");
test(function() {
test_xpath_succeeds("//*[@refX]", [document.getElementById("a")]);
}, "Select SVG elements with refX attribute");
test(function() {
test_xpath_succeeds("//*[@Refx]", []);
}, "Select SVG elements with refX attribute incorrect case");
test(function() {
test_xpath_succeeds("//*[@refx]", []);
}, "Select SVG elements with refX attribute lowercase");
test(function() {
test_xpath_succeeds("//*[@nonÄscii]", [document.getElementById("b")]);
}, "Select SVG element with non-ascii attribute 1");
test(function() {
test_xpath_succeeds("//*[@nonäscii]", []);
}, "Select SVG element with non-ascii attribute 2");
test(function() {
test_xpath_succeeds("//*[@xmlns]", []);
}, "xmlns attribute");
test(function() {
test_xpath_succeeds("//*[@xlink:href]", [document.getElementById("b")], ns_resolver);
}, "svg element with XLink attribute");
</script>

View File

@ -0,0 +1,87 @@
<!doctype html>
<meta charset="utf8">
<title>XPath in text/html: elements</title>
<link rel="help" href="http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#Interfaces">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<div id="log"><span></span></div>
<div><span></span></div>
<dØdd></dØdd>
<svg>
<path />
<path />
</svg>
<script>
function test_xpath_succeeds(path, expected, resolver) {
resolver = resolver ? resolver : null;
var res = document.evaluate(path, document, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
actual = [];
for (var i=0;;i++) {
var node = res.snapshotItem(i);
if (node === null) {
break;
}
actual.push(node);
}
assert_array_equals(actual, expected);
}
function test_xpath_throws(path, error_code, resolver) {
resolver = resolver ? resolver : null;
assert_throws_dom(error_code, function() {document.evaluate(path, document, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)})
}
function ns_resolver(x) {
map = {"html":"http://www.w3.org/1999/xhtml",
"svg":"http://www.w3.org/2000/svg",
"math":"http://www.w3.org/1998/Math/MathML"};
var rv = map.hasOwnProperty(x) ? map[x] : null;
return rv;
}
test(function() {
test_xpath_succeeds("//div", document.getElementsByTagName("div"));
}, "HTML elements no namespace prefix");
test(function() {
test_xpath_succeeds("//html:div", document.getElementsByTagName("div"), ns_resolver);
}, "HTML elements namespace prefix");
test(function() {
test_xpath_succeeds("//html:div/span", document.getElementsByTagName("span"), ns_resolver);
}, "HTML elements mixed use of prefix");
test(function() {
test_xpath_succeeds("//path", []);
}, "SVG elements no namespace prefix");
test(function() {
test_xpath_succeeds("//svg:path", document.getElementsByTagName("path"), ns_resolver);
}, "SVG elements namespace prefix");
test(function() {
test_xpath_succeeds("//DiV", document.getElementsByTagName("div"));
}, "HTML elements mixed case");
test(function() {
test_xpath_succeeds("//svg:PatH", [], ns_resolver);
}, "SVG elements mixed case selector");
test(function() {
test_xpath_succeeds("//dØdd", document.getElementsByTagName("dØdd"), ns_resolver);
}, "Non-ascii HTML element");
test(function() {
test_xpath_succeeds("//dødd", [], ns_resolver);
}, "Non-ascii HTML element2");
test(function() {
test_xpath_succeeds("//DØDD", document.getElementsByTagName("dØdd"), ns_resolver);
}, "Non-ascii HTML element3");
test(function() {
test_xpath_throws("//invalid:path", "NAMESPACE_ERR");
}, "Throw with invalid prefix");
</script>

View File

@ -0,0 +1,59 @@
<!doctype html>
<title>XPath tests</title>
<meta name="timeout" content="long">
<script src='../resources/testharness.js'></script>
<script src='../resources/testharnessreport.js'></script>
<script>
setup({ explicit_done: true });
function find_child_element(context, element) {
for (var i = 0; i < context.childNodes.length; i++) {
var child = context.childNodes[i];
if (child.nodeType === Node.ELEMENT_NODE && child.tagName === element)
return child;
}
}
function xpath_test(test_el) {
/* note this func adopts the tree! */
var new_doc = document.implementation.createDocument("", "");
var xpath = find_child_element(test_el, "xpath");
var result = find_child_element(test_el, "result");
var namespace = find_child_element(result, "namespace");
var localname = find_child_element(result, "localname");
var nth = find_child_element(result, "nth");
var tree = find_child_element(test_el, "tree");
var actual_tree = new_doc.adoptNode(tree.firstElementChild);
new_doc.appendChild(actual_tree);
test(function() {
var result = new_doc.evaluate(xpath.textContent, // expression
actual_tree, // context node
new_doc.createNSResolver(actual_tree), // resolver
XPathResult.ANY_TYPE, // type
null); // result
var matched = [];
var cur;
while ((cur = result.iterateNext()) !== null) {
matched.push(cur);
}
assert_equals(matched.length, 1, "Should match one node");
var similar = new_doc.getElementsByTagNameNS(namespace.textContent,
localname.textContent);
assert_equals(matched[0], similar[nth.textContent]);
});
}
var xhr = new XMLHttpRequest();
xhr.open("GET", "xml_xpath_tests.xml");
xhr.onload = function(e) {
var tests = xhr.responseXML.documentElement;
for (var i = 0; i < tests.childNodes.length; i++) {
var child = tests.childNodes[i];
if (child.nodeType === Node.ELEMENT_NODE) {
xpath_test(child);
}
}
done();
};
xhr.send();
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<script src='../resources/testharness.js'></script>
<script src='../resources/testharnessreport.js'></script>
<body>
<script>
[document, new XPathEvaluator()].forEach(evaluator => {
test(() => {
assert_equals(evaluator.createNSResolver(document), document, 'Document');
const fragment = document.createDocumentFragment();
assert_equals(evaluator.createNSResolver(fragment), fragment,
'DocumentFragment');
assert_equals(evaluator.createNSResolver(document.doctype),
document.doctype, 'DocumentType');
assert_equals(evaluator.createNSResolver(document.body), document.body,
'Element');
assert_equals(evaluator.createNSResolver(document.body.firstChild),
document.body.firstChild, 'Text');
const attr = document.createAttribute('foo');
assert_equals(evaluator.createNSResolver(attr), attr, 'Attr');
}, `createNSResolver() should return the specified node as is. (${evaluator.constructor.name})`);
function createAndLookup(evaluator, node) {
return evaluator.createNSResolver(node).lookupNamespaceURI('xml');
}
test(() => {
assert_equals(createAndLookup(evaluator, new Document()), null, 'Document');
assert_equals(createAndLookup(evaluator, new DocumentFragment()), null,
'DocumentFragment');
assert_equals(createAndLookup(evaluator, document.doctype), null,
'DocumentType');
assert_equals(createAndLookup(evaluator, document.createElement('body')),
'http://www.w3.org/XML/1998/namespace', 'Element');
assert_equals(createAndLookup(evaluator, document.createTextNode('foo')),
null, 'Text');
assert_equals(createAndLookup(evaluator, document.createAttribute('bar')),
null, 'Attr');
}, `createNSResolver() resultant object should not add support of 'xml' prefix. (${evaluator.constructor.name})`);
});
</script>
</body>