Automatic update from web-platform-tests Make everything in PIs case sensitive This includes PI targets and attribute names. Following the merged processing instruction spec in DOM+HTML. Also now we don't need the intermediate buffer when parsing. Existing upstream tests already assert this, though some needed a bit of result tweaking. Bug: 481087638 Change-Id: I50913cec8d0dbb2e48df5b4c52db6f1d92530f41 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7946164 Reviewed-by: Philip Jägenstedt <foolip@chromium.org> Commit-Queue: Noam Rosenthal <nrosenthal@google.com> Cr-Commit-Position: refs/heads/main@{#1659761} -- wpt-commits: 96a0f1ff8e94191d8b09e467c40d6eaa37e35ebf wpt-pr: 61197
305 lines
12 KiB
HTML
305 lines
12 KiB
HTML
<!doctype html>
|
|
<meta charset="utf-8" />
|
|
<title>Processing instruction attributes</title>
|
|
<link rel="help" href="https://github.com/WICG/declarative-partial-updates" />
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<div id="container"><?t a="b" x="yy"?></div>
|
|
|
|
<script>
|
|
const sources = {
|
|
"html-dom": (target, data) =>
|
|
document.createProcessingInstruction(target, data),
|
|
"html-parser": (target, data) => {
|
|
const div = document.createElement("div");
|
|
div.innerHTML = `<?${target} ${data}?>`;
|
|
return div.firstChild;
|
|
},
|
|
"xml-dom": (target, data) =>
|
|
new DOMParser()
|
|
.parseFromString("<root></root>", "application/xml")
|
|
.createProcessingInstruction(target, data),
|
|
"xml-parser": (target, data) =>
|
|
new DOMParser().parseFromString(
|
|
`<?${target} ${data}?>`,
|
|
"application/xml",
|
|
).firstChild,
|
|
};
|
|
|
|
test(() => {
|
|
const div = document.getElementById("container");
|
|
const pi = div.firstChild;
|
|
assert_equals(pi.nodeType, Node.PROCESSING_INSTRUCTION_NODE);
|
|
assert_equals(pi.getAttribute("a"), "b");
|
|
assert_equals(pi.getAttribute("x"), "yy");
|
|
}, "Processing instruction in main parser");
|
|
|
|
for (const [source, createProcessingInstruction] of Object.entries(sources)) {
|
|
test(() => {
|
|
const pi = createProcessingInstruction("t", 'a=b x="yy"');
|
|
assert_false(pi.hasAttributes());
|
|
assert_array_equals(pi.getAttributeNames(), []);
|
|
assert_equals(pi.getAttribute("a"), null);
|
|
assert_equals(pi.getAttribute("x"), null);
|
|
}, "Processing instruction attribute parsing rejects unquoted attributes from " + source);
|
|
|
|
test(() => {
|
|
const pi = createProcessingInstruction("t", 'a="b" x="yy"');
|
|
const mutation_observer = new MutationObserver((records) => {});
|
|
mutation_observer.observe(pi, { characterData: true, attributes: true });
|
|
assert_equals(pi.nodeType, Node.PROCESSING_INSTRUCTION_NODE);
|
|
assert_equals(pi.target, "t");
|
|
assert_true(pi.hasAttributes());
|
|
assert_equals(pi.data, `a="b" x="yy"`);
|
|
assert_array_equals(pi.getAttributeNames(), ["a", "x"]);
|
|
assert_equals(pi.getAttribute("a"), "b");
|
|
assert_equals(pi.getAttribute("x"), "yy");
|
|
assert_equals(pi.getAttribute("X"), null);
|
|
assert_equals(pi.getAttribute("nada"), null);
|
|
assert_true(pi.hasAttribute("a"));
|
|
assert_false(pi.hasAttribute("nope"));
|
|
pi.setAttribute("x", "yy2");
|
|
let records = mutation_observer.takeRecords();
|
|
assert_equals(records.length, 1);
|
|
assert_equals(records[0].type, "characterData");
|
|
assert_equals(records[0].target, pi);
|
|
assert_equals(pi.data, `a="b" x="yy2"`);
|
|
assert_equals(pi.getAttribute("x"), "yy2");
|
|
pi.removeAttribute("x");
|
|
records = mutation_observer.takeRecords();
|
|
assert_equals(records.length, 1);
|
|
assert_equals(records[0].type, "characterData");
|
|
assert_equals(records[0].target, pi);
|
|
assert_equals(pi.getAttribute("x"), null);
|
|
assert_equals(pi.data, `a="b"`);
|
|
pi.setAttribute("z", "1");
|
|
pi.setAttribute("c", "2");
|
|
records = mutation_observer.takeRecords();
|
|
assert_equals(records.length, 2);
|
|
assert_equals(records[0].type, "characterData");
|
|
assert_equals(records[0].target, pi);
|
|
assert_equals(records[1].type, "characterData");
|
|
assert_equals(records[1].target, pi);
|
|
assert_equals(
|
|
pi.data,
|
|
`a="b" z="1" c="2"`,
|
|
"attributes are added in order",
|
|
);
|
|
assert_array_equals(pi.getAttributeNames(), ["a", "z", "c"]);
|
|
pi.setAttribute("a", "3");
|
|
assert_equals(
|
|
pi.data,
|
|
`a="3" z="1" c="2"`,
|
|
"setAttribute modifies attributes in place",
|
|
);
|
|
assert_array_equals(pi.getAttributeNames(), ["a", "z", "c"]);
|
|
pi.data = "";
|
|
assert_false(pi.hasAttributes());
|
|
assert_array_equals(pi.getAttributeNames(), []);
|
|
pi.data = 'blabla=""';
|
|
assert_equals(pi.data, `blabla=""`);
|
|
assert_equals(pi.getAttribute("blabla"), "");
|
|
assert_equals(pi.getAttribute("BLABLA"), null);
|
|
assert_equals(pi.getAttribute("blaBLA"), null);
|
|
pi.setAttribute("BLAbla", "value");
|
|
assert_equals(pi.getAttribute("BLAbla"), "value");
|
|
assert_equals(pi.getAttribute("blabla"), "");
|
|
assert_true(pi.hasAttributes());
|
|
assert_array_equals(pi.getAttributeNames(), ["blabla", "BLAbla"]);
|
|
pi.removeAttribute("BLABLA");
|
|
assert_true(pi.hasAttributes());
|
|
assert_array_equals(pi.getAttributeNames(), ["blabla", "BLAbla"]);
|
|
pi.removeAttribute("blabla");
|
|
pi.removeAttribute("BLAbla");
|
|
assert_false(pi.hasAttributes());
|
|
assert_array_equals(pi.getAttributeNames(), []);
|
|
}, "Processing instruction attribute mutation from " + source);
|
|
|
|
test(() => {
|
|
const pi = createProcessingInstruction("t", '_x="yy"');
|
|
assert_true(pi.hasAttributes());
|
|
assert_array_equals(pi.getAttributeNames(), ["_x"]);
|
|
assert_equals(pi.getAttribute("_x"), "yy");
|
|
}, "Processing instruction attribute parsing allows _ in attribute names from " + source);
|
|
|
|
test(() => {
|
|
const pi = createProcessingInstruction("target", `attr="value"`);
|
|
assert_equals(pi.nodeType, Node.PROCESSING_INSTRUCTION_NODE);
|
|
assert_equals(pi.target, "target");
|
|
assert_equals(
|
|
pi.getAttribute("attr"),
|
|
"value",
|
|
"attribute value before toggling",
|
|
);
|
|
pi.toggleAttribute("attr", true);
|
|
assert_equals(
|
|
pi.getAttribute("attr"),
|
|
"value",
|
|
"toggleAttribute(true) does not change existing value",
|
|
);
|
|
pi.toggleAttribute("attr");
|
|
assert_false(
|
|
pi.hasAttribute("attr"),
|
|
"toggleAttribute() removes attribute",
|
|
);
|
|
assert_equals(pi.getAttribute("attr"), null);
|
|
pi.toggleAttribute("attr", false);
|
|
assert_false(
|
|
pi.hasAttribute("attr"),
|
|
"toggleAttribute(false) does not add attribute",
|
|
);
|
|
assert_equals(pi.getAttribute("attr"), null);
|
|
pi.toggleAttribute("attr");
|
|
assert_true(pi.hasAttribute("attr"), "toggleAttribute() adds attribute");
|
|
assert_equals(
|
|
pi.getAttribute("attr"),
|
|
"",
|
|
"toggleAttribute() adds attribute with empty value",
|
|
);
|
|
pi.toggleAttribute("attr", true);
|
|
assert_true(
|
|
pi.hasAttribute("attr"),
|
|
"toggleAttribute(true) does not remove attribute",
|
|
);
|
|
assert_equals(pi.getAttribute("attr"), "");
|
|
}, "Processing instruction toggleAttribute from " + source);
|
|
|
|
function test_attribute_value(value) {
|
|
test(() => {
|
|
const pi = createProcessingInstruction("target", "");
|
|
const element = pi.ownerDocument.createElement("el");
|
|
pi.setAttribute("attr", value);
|
|
element.setAttribute("attr", value);
|
|
assert_equals(pi.getAttribute("attr"), element.getAttribute("attr"));
|
|
const extracted = element.outerHTML.match(/el\s+([^>]+?)\s*\/?>/);
|
|
assert_equals(pi.data, extracted[1]);
|
|
}, `check attribute value: "${value}" (source: "${source}")`);
|
|
}
|
|
|
|
function test_valid_attribute_name(name) {
|
|
test(() => {
|
|
const pi = createProcessingInstruction("target", "");
|
|
pi.setAttribute(name, "value");
|
|
assert_true(pi.hasAttribute(name));
|
|
assert_equals(pi.getAttribute(name), "value");
|
|
assert_array_equals(pi.getAttributeNames(), [name]);
|
|
|
|
pi.toggleAttribute(name, false);
|
|
assert_false(pi.hasAttribute(name));
|
|
|
|
pi.toggleAttribute(name, true);
|
|
assert_true(pi.hasAttribute(name));
|
|
assert_equals(pi.getAttribute(name), "");
|
|
|
|
pi.removeAttribute(name);
|
|
assert_false(pi.hasAttribute(name));
|
|
}, `Valid attribute name: ${name} (source: ${source})`);
|
|
}
|
|
|
|
function test_invalid_attribute_name(name) {
|
|
test(() => {
|
|
const pi = document.createProcessingInstruction("target", "");
|
|
assert_throws_dom("InvalidCharacterError", () => {
|
|
pi.setAttribute(name, "value");
|
|
});
|
|
assert_throws_dom("InvalidCharacterError", () => {
|
|
pi.toggleAttribute(name, true);
|
|
});
|
|
assert_throws_dom("InvalidCharacterError", () => {
|
|
pi.toggleAttribute(name, false);
|
|
});
|
|
assert_throws_dom("InvalidCharacterError", () => {
|
|
pi.toggleAttribute(name);
|
|
});
|
|
}, `Invalid attribute name: ${name} (source: ${source})`);
|
|
}
|
|
|
|
function test_distinct_attribute_name(name, other) {
|
|
test(
|
|
() => {
|
|
const pi = createProcessingInstruction("target", "");
|
|
pi.setAttribute(name, "value");
|
|
assert_false(pi.hasAttribute(other));
|
|
assert_true(pi.hasAttribute(name));
|
|
assert_equals(pi.getAttribute(other), null);
|
|
assert_equals(pi.getAttribute(name), "value");
|
|
pi.removeAttribute(other);
|
|
assert_true(pi.hasAttribute(name));
|
|
pi.removeAttribute(name);
|
|
assert_false(pi.hasAttribute(name));
|
|
},
|
|
"Distinct attribute name (source: " +
|
|
source +
|
|
"): " +
|
|
name +
|
|
" and " +
|
|
other,
|
|
);
|
|
}
|
|
|
|
test_attribute_value("=");
|
|
test_attribute_value('axx"');
|
|
test_attribute_value("axx>");
|
|
test_attribute_value("some<>");
|
|
test_attribute_value("'\"\"'");
|
|
test_attribute_value('aa"aa"');
|
|
|
|
test_invalid_attribute_name("=");
|
|
test_invalid_attribute_name("a=");
|
|
test_invalid_attribute_name("=x");
|
|
test_invalid_attribute_name("b>");
|
|
test_invalid_attribute_name(">");
|
|
test_invalid_attribute_name(">x");
|
|
test_invalid_attribute_name("/x/");
|
|
test_invalid_attribute_name("/");
|
|
test_invalid_attribute_name("x/");
|
|
test_invalid_attribute_name("x\t");
|
|
test_invalid_attribute_name("\tx");
|
|
test_invalid_attribute_name("x\n");
|
|
test_invalid_attribute_name("\nx");
|
|
test_invalid_attribute_name("x\r");
|
|
test_invalid_attribute_name("\rx");
|
|
|
|
test_valid_attribute_name("$");
|
|
test_valid_attribute_name("_");
|
|
test_valid_attribute_name("$foo");
|
|
test_valid_attribute_name("_bar");
|
|
test_valid_attribute_name("a$b");
|
|
test_valid_attribute_name("a_b");
|
|
|
|
test_distinct_attribute_name("ABC", "abc");
|
|
test_distinct_attribute_name("abC", "abc");
|
|
test_distinct_attribute_name("x123A", "x123a");
|
|
|
|
// HTML parser ignores xml-stylesheet
|
|
if (source == "html-parser")
|
|
continue;
|
|
|
|
test(() => {
|
|
const pi = createProcessingInstruction(
|
|
"xml-stylesheet",
|
|
`type="text/css" href="style.css"`,
|
|
);
|
|
assert_equals(pi.nodeType, Node.PROCESSING_INSTRUCTION_NODE);
|
|
assert_equals(pi.target, "xml-stylesheet");
|
|
assert_equals(pi.data, `type="text/css" href="style.css"`);
|
|
assert_true(pi.hasAttributes());
|
|
assert_equals(pi.getAttribute("type"), "text/css");
|
|
assert_equals(pi.getAttribute("href"), "style.css");
|
|
assert_equals(pi.getAttribute("nada"), null);
|
|
assert_true(pi.hasAttribute("type"));
|
|
assert_false(pi.hasAttribute("nope"));
|
|
pi.setAttribute("href", "style2.css");
|
|
assert_equals(pi.getAttribute("href"), "style2.css");
|
|
pi.removeAttribute("href");
|
|
assert_equals(pi.getAttribute("href"), null);
|
|
assert_array_equals(pi.getAttributeNames(), ["type"]);
|
|
assert_true(pi.hasAttributes());
|
|
pi.data = 'type="text/css" href="style3.css"';
|
|
assert_equals(pi.getAttribute("type"), "text/css");
|
|
assert_equals(pi.getAttribute("href"), "style3.css");
|
|
assert_array_equals(pi.getAttributeNames(), ["type", "href"]);
|
|
}, "Processing instruction attributes with xml-stylesheet from " + source);
|
|
}
|
|
</script>
|