Explorar el Código

LibWeb: Use correct comparison logic in `NamedNodeMap::get_attribute()`

Previously, we were doing a case insensitive comparison, which could
return the wrong result if the attribute name was uppercase.
Tim Ledbetter hace 8 meses
padre
commit
f378f41526

+ 1 - 1
Libraries/LibWeb/DOM/NamedNodeMap.cpp

@@ -153,7 +153,7 @@ Attr const* NamedNodeMap::get_attribute(FlyString const& qualified_name, size_t*
     // 2. Return the first attribute in element’s attribute list whose qualified name is qualifiedName; otherwise null.
     for (auto const& attribute : m_attributes) {
         if (compare_as_lowercase) {
-            if (attribute->name().equals_ignoring_ascii_case(qualified_name))
+            if (attribute->name().equals_ignoring_ascii_case(qualified_name) && !AK::any_of(attribute->name().bytes(), is_ascii_upper_alpha))
                 return attribute;
         } else {
             if (attribute->name() == qualified_name)

+ 81 - 0
Tests/LibWeb/Text/expected/wpt-import/dom/nodes/attributes.txt

@@ -0,0 +1,81 @@
+Summary
+
+Harness status: OK
+
+Rerun
+
+Found 70 tests
+
+69 Pass
+1 Fail
+Details
+Result	Test Name	MessagePass	When qualifiedName does not match the Name production, an INVALID_CHARACTER_ERR exception is to be thrown. (toggleAttribute)	
+Pass	When qualifiedName does not match the Name production, an INVALID_CHARACTER_ERR exception is to be thrown, even if the attribute is already present. (toggleAttribute)	
+Pass	toggleAttribute should lowercase its name argument (upper case attribute)	
+Pass	toggleAttribute should lowercase its name argument (mixed case attribute)	
+Pass	toggleAttribute should not throw even when qualifiedName starts with 'xmlns'	
+Pass	Basic functionality should be intact. (toggleAttribute)	
+Pass	toggleAttribute should not change the order of previously set attributes.	
+Pass	toggleAttribute should set the first attribute with the given name	
+Pass	toggleAttribute should set the attribute with the given qualified name	
+Pass	Toggling element with inline style should make inline style disappear	
+Pass	When qualifiedName does not match the Name production, an INVALID_CHARACTER_ERR exception is to be thrown. (setAttribute)	
+Pass	When qualifiedName does not match the Name production, an INVALID_CHARACTER_ERR exception is to be thrown, even if the attribute is already present. (setAttribute)	
+Pass	setAttribute should lowercase its name argument (upper case attribute)	
+Pass	setAttribute should lowercase its name argument (mixed case attribute)	
+Pass	setAttribute should not throw even when qualifiedName starts with 'xmlns'	
+Pass	Basic functionality should be intact.	
+Pass	setAttribute should not change the order of previously set attributes.	
+Pass	setAttribute should set the first attribute with the given name	
+Pass	setAttribute should set the attribute with the given qualified name	
+Pass	When qualifiedName does not match the Name production, an INVALID_CHARACTER_ERR exception is to be thrown. (setAttributeNS)	
+Pass	When qualifiedName does not match the Name production, an INVALID_CHARACTER_ERR exception is to be thrown, even if the attribute is already present. (setAttributeNS)	
+Pass	When qualifiedName does not match the QName production, an INVALID_CHARACTER_ERR exception is to be thrown.	
+Pass	null and the empty string should result in a null namespace.	
+Pass	A namespace is required to use a prefix.	
+Pass	The xml prefix should not be allowed for arbitrary namespaces	
+Pass	XML-namespaced attributes don't need an xml prefix	
+Pass	The xmlns prefix should not be allowed for arbitrary namespaces	
+Pass	The xmlns qualified name should not be allowed for arbitrary namespaces	
+Pass	xmlns should be allowed as local name	
+Pass	The XMLNS namespace should require xmlns as prefix or qualified name	
+Pass	xmlns should be allowed as prefix in the XMLNS namespace	
+Pass	xmlns should be allowed as qualified name in the XMLNS namespace	
+Pass	Setting the same attribute with another prefix should not change the prefix	
+Pass	setAttribute should not throw even if a load is not allowed	
+Pass	Attributes should work in document fragments.	
+Pass	Attribute values should not be parsed.	
+Pass	Specified attributes should be accessible.	
+Pass	Entities in attributes should have been expanded while parsing.	
+Pass	Unset attributes return null	
+Pass	First set attribute is returned by getAttribute	
+Pass	Style attributes are not normalized	
+Pass	Only lowercase attributes are returned on HTML elements (upper case attribute)	
+Pass	Only lowercase attributes are returned on HTML elements (mixed case attribute)	
+Pass	First set attribute is returned with mapped attribute set first	
+Pass	First set attribute is returned with mapped attribute set later	
+Pass	Non-HTML element with upper-case attribute	
+Pass	Attribute with prefix in local name	
+Pass	Attribute loses its owner when removed	
+Pass	Basic functionality of getAttributeNode/getAttributeNodeNS	
+Pass	Basic functionality of setAttributeNode	
+Pass	setAttributeNode should distinguish attributes with same local name and different namespaces	
+Pass	setAttributeNode doesn't have case-insensitivity even with an HTMLElement 1	
+Pass	setAttributeNode doesn't have case-insensitivity even with an HTMLElement 2	
+Pass	setAttributeNode doesn't have case-insensitivity even with an HTMLElement 3	
+Pass	Basic functionality of setAttributeNodeNS	
+Pass	If attr’s element is neither null nor element, throw an InUseAttributeError.	
+Pass	Replacing an attr by itself	
+Pass	Basic functionality of removeAttributeNode	
+Pass	setAttributeNode on bound attribute should throw InUseAttributeError	
+Pass	setAttributeNode, if it fires mutation events, should fire one with the new node when resetting an existing attribute	
+Pass	setAttributeNode, if it fires mutation events, should fire one with the new node when resetting an existing attribute (outer shell)	
+Pass	setAttributeNode called with an Attr that has the same name as an existing one should not change attribute order	
+Pass	getAttributeNames tests	
+Pass	Own property correctness with basic attributes	
+Pass	Own property correctness with non-namespaced attribute before same-name namespaced one	
+Pass	Own property correctness with namespaced attribute before same-name non-namespaced one	
+Pass	Own property correctness with two namespaced attributes with the same name-with-prefix	
+Pass	Own property names should only include all-lowercase qualified names for an HTML element in an HTML document	
+Pass	Own property names should include all qualified names for a non-HTML element in an HTML document	
+Fail	Own property names should include all qualified names for an HTML element in a non-HTML document	

+ 883 - 0
Tests/LibWeb/Text/input/wpt-import/dom/nodes/attributes.html

@@ -0,0 +1,883 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Attributes tests</title>
+<link rel=help href="https://dom.spec.whatwg.org/#attr">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-setattribute">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-setattributens">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="attributes.js"></script>
+<script src="productions.js"></script>
+<div id="log"></div>
+<span id="test1"></span>
+<span class="&amp;&lt;&gt;foo"></span>
+<span id="test2">
+  <span ~=""></span>
+  <span ~></span>
+  <span></span>
+</span>
+<script>
+var XML = "http://www.w3.org/XML/1998/namespace"
+var XMLNS = "http://www.w3.org/2000/xmlns/"
+
+// toggleAttribute exhaustive tests
+// Step 1
+test(function() {
+  var el = document.createElement("foo")
+  for (var i = 0; i < invalid_names.length; i++) {
+    assert_throws_dom("INVALID_CHARACTER_ERR", function() { el.toggleAttribute(invalid_names[i], true) })
+  }
+  for (var i = 0; i < invalid_names.length; i++) {
+    assert_throws_dom("INVALID_CHARACTER_ERR", function() { el.toggleAttribute(invalid_names[i]) })
+  }
+  for (var i = 0; i < invalid_names.length; i++) {
+    assert_throws_dom("INVALID_CHARACTER_ERR", function() { el.toggleAttribute(invalid_names[i], false) })
+  }
+}, "When qualifiedName does not match the Name production, an " +
+   "INVALID_CHARACTER_ERR exception is to be thrown. (toggleAttribute)")
+test(function() {
+  var el = document.getElementById("test2")
+  for (var i = 0; i < el.children.length; i++) {
+    assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+      el.children[i].toggleAttribute("~", false)
+    })
+  }
+  for (var i = 0; i < el.children.length; i++) {
+    assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+      el.children[i].toggleAttribute("~")
+    })
+  }
+  for (var i = 0; i < el.children.length; i++) {
+    assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+      el.children[i].toggleAttribute("~", true)
+    })
+  }
+}, "When qualifiedName does not match the Name production, an " +
+   "INVALID_CHARACTER_ERR exception is to be thrown, even if the attribute " +
+   "is already present. (toggleAttribute)")
+
+// Step 2
+test(function() {
+  var el = document.createElement("div")
+  assert_true(el.toggleAttribute("ALIGN"))
+  assert_true(!el.hasAttributeNS("", "ALIGN"))
+  assert_true(el.hasAttributeNS("", "align"))
+  assert_true(el.hasAttribute("align"))
+  assert_true(!el.toggleAttribute("ALIGN"))
+  assert_true(!el.hasAttributeNS("", "ALIGN"))
+  assert_true(!el.hasAttributeNS("", "align"))
+  assert_true(!el.hasAttribute("align"))
+}, "toggleAttribute should lowercase its name argument (upper case attribute)")
+test(function() {
+  var el = document.createElement("div")
+  assert_true(el.toggleAttribute("CHEEseCaKe"))
+  assert_true(!el.hasAttributeNS("", "CHEEseCaKe"))
+  assert_true(el.hasAttributeNS("", "cheesecake"))
+  assert_true(el.hasAttribute("cheesecake"))
+}, "toggleAttribute should lowercase its name argument (mixed case attribute)")
+
+// Step 3
+test(function() {
+  var el = document.createElement("foo")
+  var tests = ["xmlns", "xmlns:a", "xmlnsx", "xmlns0"]
+  for (var i = 0; i < tests.length; i++) {
+    assert_true(el.toggleAttribute(tests[i]));
+    assert_true(el.hasAttribute(tests[i]));
+  }
+}, "toggleAttribute should not throw even when qualifiedName starts with 'xmlns'")
+
+// Step 4
+test(function() {
+  var el = document.createElement("foo")
+  for (var i = 0; i < valid_names.length; i++) {
+    assert_true(el.toggleAttribute(valid_names[i]))
+    assert_true(el.hasAttribute(valid_names[i]))
+    assert_true(!el.toggleAttribute(valid_names[i]))
+    assert_true(!el.hasAttribute(valid_names[i]))
+    // Check using force attr
+    assert_true(el.toggleAttribute(valid_names[i], true))
+    assert_true(el.hasAttribute(valid_names[i]))
+    assert_true(el.toggleAttribute(valid_names[i], true))
+    assert_true(el.hasAttribute(valid_names[i]))
+    assert_true(!el.toggleAttribute(valid_names[i], false))
+    assert_true(!el.hasAttribute(valid_names[i]))
+  }
+}, "Basic functionality should be intact. (toggleAttribute)")
+
+// Step 5
+test(function() {
+  var el = document.createElement("foo")
+  el.toggleAttribute("a")
+  el.toggleAttribute("b")
+  el.setAttribute("a", "thing")
+  el.toggleAttribute("c")
+  attributes_are(el, [["a", "thing"],
+                      ["b", ""],
+                      ["c", ""]])
+}, "toggleAttribute should not change the order of previously set attributes.")
+test(function() {
+  var el = document.createElement("baz")
+  el.setAttributeNS("ab", "attr", "fail")
+  el.setAttributeNS("kl", "attr", "pass")
+  el.toggleAttribute("attr")
+  attributes_are(el, [["attr", "pass", "kl"]])
+}, "toggleAttribute should set the first attribute with the given name")
+test(function() {
+  // Based on a test by David Flanagan.
+  var el = document.createElement("baz")
+  el.setAttributeNS("foo", "foo:bar", "1");
+  el.setAttributeNS("foo", "foo:bat", "2");
+  assert_equals(el.getAttribute("foo:bar"), "1")
+  assert_equals(el.getAttribute("foo:bat"), "2")
+  attr_is(el.attributes[0], "1", "bar", "foo", "foo", "foo:bar")
+  attr_is(el.attributes[1], "2", "bat", "foo", "foo", "foo:bat")
+  el.toggleAttribute("foo:bar");
+  assert_true(!el.hasAttribute("foo:bar"))
+  attr_is(el.attributes[0], "2", "bat", "foo", "foo", "foo:bat")
+}, "toggleAttribute should set the attribute with the given qualified name")
+
+test(function() {
+  var el = document.createElement("foo")
+  el.style = "color: red; background-color: green"
+  assert_equals(el.toggleAttribute("style"), false)
+}, "Toggling element with inline style should make inline style disappear")
+
+// setAttribute exhaustive tests
+// Step 1
+test(function() {
+  var el = document.createElement("foo")
+  for (var i = 0; i < invalid_names.length; i++) {
+    assert_throws_dom("INVALID_CHARACTER_ERR", function() { el.setAttribute(invalid_names[i], "test") })
+  }
+}, "When qualifiedName does not match the Name production, an " +
+   "INVALID_CHARACTER_ERR exception is to be thrown. (setAttribute)")
+test(function() {
+  var el = document.getElementById("test2")
+  for (var i = 0; i < el.children.length; i++) {
+    assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+      el.children[i].setAttribute("~", "test")
+    })
+  }
+}, "When qualifiedName does not match the Name production, an " +
+   "INVALID_CHARACTER_ERR exception is to be thrown, even if the attribute " +
+   "is already present. (setAttribute)")
+
+// Step 2
+test(function() {
+  var el = document.createElement("div")
+  el.setAttribute("ALIGN", "left")
+  assert_equals(el.getAttributeNS("", "ALIGN"), null)
+  assert_equals(el.getAttributeNS("", "align"), "left")
+  assert_equals(el.getAttribute("align"), "left")
+}, "setAttribute should lowercase its name argument (upper case attribute)")
+test(function() {
+  var el = document.createElement("div")
+  el.setAttribute("CHEEseCaKe", "tasty")
+  assert_equals(el.getAttributeNS("", "CHEEseCaKe"), null)
+  assert_equals(el.getAttributeNS("", "cheesecake"), "tasty")
+  assert_equals(el.getAttribute("cheesecake"), "tasty")
+}, "setAttribute should lowercase its name argument (mixed case attribute)")
+
+// Step 3
+test(function() {
+  var el = document.createElement("foo")
+  var tests = ["xmlns", "xmlns:a", "xmlnsx", "xmlns0"]
+  for (var i = 0; i < tests.length; i++) {
+    el.setAttribute(tests[i], "success");
+  }
+}, "setAttribute should not throw even when qualifiedName starts with 'xmlns'")
+
+// Step 4
+test(function() {
+  var el = document.createElement("foo")
+  for (var i = 0; i < valid_names.length; i++) {
+    el.setAttribute(valid_names[i], "test")
+    assert_equals(el.getAttribute(valid_names[i]), "test")
+  }
+}, "Basic functionality should be intact.")
+
+// Step 5
+test(function() {
+  var el = document.createElement("foo")
+  el.setAttribute("a", "1")
+  el.setAttribute("b", "2")
+  el.setAttribute("a", "3")
+  el.setAttribute("c", "4")
+  attributes_are(el, [["a", "3"],
+                      ["b", "2"],
+                      ["c", "4"]])
+}, "setAttribute should not change the order of previously set attributes.")
+test(function() {
+  var el = document.createElement("baz")
+  el.setAttributeNS("ab", "attr", "fail")
+  el.setAttributeNS("kl", "attr", "pass")
+  el.setAttribute("attr", "pass")
+  attributes_are(el, [["attr", "pass", "ab"],
+                      ["attr", "pass", "kl"]])
+}, "setAttribute should set the first attribute with the given name")
+test(function() {
+  // Based on a test by David Flanagan.
+  var el = document.createElement("baz")
+  el.setAttributeNS("foo", "foo:bar", "1");
+  assert_equals(el.getAttribute("foo:bar"), "1")
+  attr_is(el.attributes[0], "1", "bar", "foo", "foo", "foo:bar")
+  el.setAttribute("foo:bar", "2");
+  assert_equals(el.getAttribute("foo:bar"), "2")
+  attr_is(el.attributes[0], "2", "bar", "foo", "foo", "foo:bar")
+}, "setAttribute should set the attribute with the given qualified name")
+
+// setAttributeNS exhaustive tests
+// Step 1
+test(function() {
+  var el = document.createElement("foo")
+  for (var i = 0, il = invalid_names.length; i < il; ++i) {
+    assert_throws_dom("INVALID_CHARACTER_ERR",
+                      function() { el.setAttributeNS("a", invalid_names[i], "fail") })
+  }
+}, "When qualifiedName does not match the Name production, an " +
+   "INVALID_CHARACTER_ERR exception is to be thrown. (setAttributeNS)")
+
+test(function() {
+  var el = document.getElementById("test2")
+  for (var i = 0; i < el.children.length; i++) {
+    assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+      el.children[i].setAttributeNS(null, "~", "test")
+    })
+  }
+}, "When qualifiedName does not match the Name production, an " +
+   "INVALID_CHARACTER_ERR exception is to be thrown, even if the attribute " +
+   "is already present. (setAttributeNS)")
+
+// Step 2
+test(function() {
+  var el = document.createElement("foo")
+  for (var i = 0, il = invalid_qnames.length; i < il; ++i) {
+    assert_throws_dom("INVALID_CHARACTER_ERR",
+                      function() { el.setAttributeNS("a", invalid_qnames[i], "fail") },
+                      "Expected exception for " + invalid_qnames[i] + ".")
+  }
+}, "When qualifiedName does not match the QName production, an " +
+   "INVALID_CHARACTER_ERR exception is to be thrown.")
+
+// Step 3
+test(function() {
+  var el = document.createElement("foo")
+  el.setAttributeNS(null, "aa", "bb")
+  el.setAttributeNS("", "xx", "bb")
+  attributes_are(el, [["aa", "bb"],
+                      ["xx", "bb"]])
+}, "null and the empty string should result in a null namespace.")
+
+// Step 4
+test(function() {
+  var el = document.createElement("foo")
+  assert_throws_dom("NAMESPACE_ERR",
+                    function() { el.setAttributeNS("", "aa:bb", "fail") })
+  assert_throws_dom("NAMESPACE_ERR",
+                    function() { el.setAttributeNS(null, "aa:bb", "fail") })
+}, "A namespace is required to use a prefix.")
+
+// Step 5
+test(function() {
+  var el = document.createElement("foo")
+  assert_throws_dom("NAMESPACE_ERR",
+                    function() { el.setAttributeNS("a", "xml:bb", "fail") })
+}, "The xml prefix should not be allowed for arbitrary namespaces")
+test(function() {
+  var el = document.createElement("foo")
+  el.setAttributeNS(XML, "a:bb", "pass")
+  assert_equals(el.attributes.length, 1)
+  attr_is(el.attributes[0], "pass", "bb", XML, "a", "a:bb")
+}, "XML-namespaced attributes don't need an xml prefix")
+
+// Step 6
+test(function() {
+  var el = document.createElement("foo")
+  assert_throws_dom("NAMESPACE_ERR",
+                    function() { el.setAttributeNS("a", "xmlns:bb", "fail") })
+}, "The xmlns prefix should not be allowed for arbitrary namespaces")
+test(function() {
+  var el = document.createElement("foo")
+  assert_throws_dom("NAMESPACE_ERR",
+                    function() { el.setAttributeNS("a", "xmlns", "fail") })
+}, "The xmlns qualified name should not be allowed for arbitrary namespaces")
+test(function() {
+  var el = document.createElement("foo")
+  el.setAttributeNS("ns", "a:xmlns", "pass")
+  assert_equals(el.attributes.length, 1)
+  attr_is(el.attributes[0], "pass", "xmlns", "ns", "a", "a:xmlns")
+}, "xmlns should be allowed as local name")
+
+// Step 7
+test(function() {
+  var el = document.createElement("foo")
+  assert_throws_dom("NAMESPACE_ERR",
+                    function() { el.setAttributeNS(XMLNS, "a:xmlns", "fail") })
+  assert_throws_dom("NAMESPACE_ERR",
+                    function() { el.setAttributeNS(XMLNS, "b:foo", "fail") })
+}, "The XMLNS namespace should require xmlns as prefix or qualified name")
+test(function() {
+  var el = document.createElement("foo")
+  el.setAttributeNS(XMLNS, "xmlns:a", "pass")
+  assert_equals(el.attributes.length, 1)
+  attr_is(el.attributes[0], "pass", "a", XMLNS, "xmlns", "xmlns:a")
+}, "xmlns should be allowed as prefix in the XMLNS namespace")
+test(function() {
+  var el = document.createElement("foo")
+  el.setAttributeNS(XMLNS, "xmlns", "pass")
+  assert_equals(el.attributes.length, 1)
+  attr_is(el.attributes[0], "pass", "xmlns", XMLNS, null, "xmlns")
+}, "xmlns should be allowed as qualified name in the XMLNS namespace")
+
+// Step 8-9
+test(function() {
+  var el = document.createElement("foo")
+  el.setAttributeNS("a", "foo:bar", "X")
+  assert_equals(el.attributes.length, 1)
+  attr_is(el.attributes[0], "X", "bar", "a", "foo", "foo:bar")
+
+  el.setAttributeNS("a", "quux:bar", "Y")
+  assert_equals(el.attributes.length, 1)
+  attr_is(el.attributes[0], "Y", "bar", "a", "foo", "foo:bar")
+  el.removeAttributeNS("a", "bar")
+}, "Setting the same attribute with another prefix should not change the prefix")
+
+// Miscellaneous tests
+test(function() {
+  var el = document.createElement("iframe")
+  el.setAttribute("src", "file:///home")
+  assert_equals(el.getAttribute("src"), "file:///home")
+}, "setAttribute should not throw even if a load is not allowed")
+test(function() {
+  var docFragment = document.createDocumentFragment()
+  var newOne = document.createElement("newElement")
+  newOne.setAttribute("newdomestic", "Yes")
+  docFragment.appendChild(newOne)
+  var domesticNode = docFragment.firstChild
+  var attr = domesticNode.attributes.item(0)
+  attr_is(attr, "Yes", "newdomestic", null, null, "newdomestic")
+}, "Attributes should work in document fragments.")
+test(function() {
+  var el = document.createElement("foo")
+  el.setAttribute("x", "y")
+  var attr = el.attributes[0]
+  attr.value = "Y&lt;"
+  attr_is(attr, "Y&lt;", "x", null, null, "x")
+  assert_equals(el.getAttribute("x"), "Y&lt;")
+}, "Attribute values should not be parsed.")
+test(function() {
+  var el = document.getElementsByTagName("span")[0]
+  attr_is(el.attributes[0], "test1", "id", null, null, "id")
+}, "Specified attributes should be accessible.")
+test(function() {
+  var el = document.getElementsByTagName("span")[1]
+  attr_is(el.attributes[0], "&<>foo", "class", null, null, "class")
+}, "Entities in attributes should have been expanded while parsing.")
+
+test(function() {
+  var el = document.createElement("div")
+  assert_equals(el.hasAttribute("bar"), false)
+  assert_equals(el.hasAttributeNS(null, "bar"), false)
+  assert_equals(el.hasAttributeNS("", "bar"), false)
+  assert_equals(el.getAttribute("bar"), null)
+  assert_equals(el.getAttributeNS(null, "bar"), null)
+  assert_equals(el.getAttributeNS("", "bar"), null)
+}, "Unset attributes return null")
+test(function() {
+  var el = document.createElement("div")
+  el.setAttributeNS("ab", "attr", "t1")
+  el.setAttributeNS("kl", "attr", "t2")
+  assert_equals(el.hasAttribute("attr"), true)
+  assert_equals(el.hasAttributeNS("ab", "attr"), true)
+  assert_equals(el.hasAttributeNS("kl", "attr"), true)
+  assert_equals(el.getAttribute("attr"), "t1")
+  assert_equals(el.getAttributeNS("ab", "attr"), "t1")
+  assert_equals(el.getAttributeNS("kl", "attr"), "t2")
+  assert_equals(el.getAttributeNS(null, "attr"), null)
+  assert_equals(el.getAttributeNS("", "attr"), null)
+}, "First set attribute is returned by getAttribute")
+test(function() {
+  var el = document.createElement("div")
+  el.setAttribute("style", "color:#fff;")
+  assert_equals(el.hasAttribute("style"), true)
+  assert_equals(el.hasAttributeNS(null, "style"), true)
+  assert_equals(el.hasAttributeNS("", "style"), true)
+  assert_equals(el.getAttribute("style"), "color:#fff;")
+  assert_equals(el.getAttributeNS(null, "style"), "color:#fff;")
+  assert_equals(el.getAttributeNS("", "style"), "color:#fff;")
+}, "Style attributes are not normalized")
+test(function() {
+  var el = document.createElement("div")
+  el.setAttributeNS("", "ALIGN", "left")
+  assert_equals(el.hasAttribute("ALIGN"), false)
+  assert_equals(el.hasAttribute("align"), false)
+  assert_equals(el.hasAttributeNS(null, "ALIGN"), true)
+  assert_equals(el.hasAttributeNS(null, "align"), false)
+  assert_equals(el.hasAttributeNS("", "ALIGN"), true)
+  assert_equals(el.hasAttributeNS("", "align"), false)
+  assert_equals(el.getAttribute("ALIGN"), null)
+  assert_equals(el.getAttribute("align"), null)
+  assert_equals(el.getAttributeNS(null, "ALIGN"), "left")
+  assert_equals(el.getAttributeNS("", "ALIGN"), "left")
+  assert_equals(el.getAttributeNS(null, "align"), null)
+  assert_equals(el.getAttributeNS("", "align"), null)
+  el.removeAttributeNS("", "ALIGN")
+}, "Only lowercase attributes are returned on HTML elements (upper case attribute)")
+test(function() {
+  var el = document.createElement("div")
+  el.setAttributeNS("", "CHEEseCaKe", "tasty")
+  assert_equals(el.hasAttribute("CHEESECAKE"), false)
+  assert_equals(el.hasAttribute("CHEEseCaKe"), false)
+  assert_equals(el.hasAttribute("cheesecake"), false)
+  assert_equals(el.hasAttributeNS("", "CHEESECAKE"), false)
+  assert_equals(el.hasAttributeNS("", "CHEEseCaKe"), true)
+  assert_equals(el.hasAttributeNS("", "cheesecake"), false)
+  assert_equals(el.hasAttributeNS(null, "CHEESECAKE"), false)
+  assert_equals(el.hasAttributeNS(null, "CHEEseCaKe"), true)
+  assert_equals(el.hasAttributeNS(null, "cheesecake"), false)
+  assert_equals(el.getAttribute("CHEESECAKE"), null)
+  assert_equals(el.getAttribute("CHEEseCaKe"), null)
+  assert_equals(el.getAttribute("cheesecake"), null)
+  assert_equals(el.getAttributeNS(null, "CHEESECAKE"), null)
+  assert_equals(el.getAttributeNS("", "CHEESECAKE"), null)
+  assert_equals(el.getAttributeNS(null, "CHEEseCaKe"), "tasty")
+  assert_equals(el.getAttributeNS("", "CHEEseCaKe"), "tasty")
+  assert_equals(el.getAttributeNS(null, "cheesecake"), null)
+  assert_equals(el.getAttributeNS("", "cheesecake"), null)
+  el.removeAttributeNS("", "CHEEseCaKe")
+}, "Only lowercase attributes are returned on HTML elements (mixed case attribute)")
+test(function() {
+  var el = document.createElement("div")
+  document.body.appendChild(el)
+  el.setAttributeNS("", "align", "left")
+  el.setAttributeNS("xx", "align", "right")
+  el.setAttributeNS("", "foo", "left")
+  el.setAttributeNS("xx", "foo", "right")
+  assert_equals(el.hasAttribute("align"), true)
+  assert_equals(el.hasAttribute("foo"), true)
+  assert_equals(el.hasAttributeNS("xx", "align"), true)
+  assert_equals(el.hasAttributeNS(null, "foo"), true)
+  assert_equals(el.getAttribute("align"), "left")
+  assert_equals(el.getAttribute("foo"), "left")
+  assert_equals(el.getAttributeNS("xx", "align"), "right")
+  assert_equals(el.getAttributeNS(null, "foo"), "left")
+  assert_equals(el.getAttributeNS("", "foo"), "left")
+  el.removeAttributeNS("", "align")
+  el.removeAttributeNS("xx", "align")
+  el.removeAttributeNS("", "foo")
+  el.removeAttributeNS("xx", "foo")
+  document.body.removeChild(el)
+}, "First set attribute is returned with mapped attribute set first")
+test(function() {
+  var el = document.createElement("div")
+  el.setAttributeNS("xx", "align", "right")
+  el.setAttributeNS("", "align", "left")
+  el.setAttributeNS("xx", "foo", "right")
+  el.setAttributeNS("", "foo", "left")
+  assert_equals(el.hasAttribute("align"), true)
+  assert_equals(el.hasAttribute("foo"), true)
+  assert_equals(el.hasAttributeNS("xx", "align"), true)
+  assert_equals(el.hasAttributeNS(null, "foo"), true)
+  assert_equals(el.getAttribute("align"), "right")
+  assert_equals(el.getAttribute("foo"), "right")
+  assert_equals(el.getAttributeNS("xx", "align"), "right")
+  assert_equals(el.getAttributeNS(null, "foo"), "left")
+  assert_equals(el.getAttributeNS("", "foo"), "left")
+  el.removeAttributeNS("", "align")
+  el.removeAttributeNS("xx", "align")
+  el.removeAttributeNS("", "foo")
+  el.removeAttributeNS("xx", "foo")
+}, "First set attribute is returned with mapped attribute set later")
+
+test(function() {
+  var el = document.createElementNS("http://www.example.com", "foo")
+  el.setAttribute("A", "test")
+  assert_equals(el.hasAttribute("A"), true, "hasAttribute()")
+  assert_equals(el.hasAttributeNS("", "A"), true, "el.hasAttributeNS(\"\")")
+  assert_equals(el.hasAttributeNS(null, "A"), true, "el.hasAttributeNS(null)")
+  assert_equals(el.hasAttributeNS(undefined, "A"), true, "el.hasAttributeNS(undefined)")
+  assert_equals(el.hasAttributeNS("foo", "A"), false, "el.hasAttributeNS(\"foo\")")
+
+  assert_equals(el.getAttribute("A"), "test", "getAttribute()")
+  assert_equals(el.getAttributeNS("", "A"), "test", "el.getAttributeNS(\"\")")
+  assert_equals(el.getAttributeNS(null, "A"), "test", "el.getAttributeNS(null)")
+  assert_equals(el.getAttributeNS(undefined, "A"), "test", "el.getAttributeNS(undefined)")
+  assert_equals(el.getAttributeNS("foo", "A"), null, "el.getAttributeNS(\"foo\")")
+}, "Non-HTML element with upper-case attribute")
+
+test(function() {
+  var el = document.createElement("div")
+  el.setAttribute("pre:fix", "value 1")
+  el.setAttribute("fix", "value 2")
+
+  var prefixed = el.attributes[0]
+  assert_equals(prefixed.localName, "pre:fix", "prefixed local name")
+  assert_equals(prefixed.namespaceURI, null, "prefixed namespace")
+
+  var unprefixed = el.attributes[1]
+  assert_equals(unprefixed.localName, "fix", "unprefixed local name")
+  assert_equals(unprefixed.namespaceURI, null, "unprefixed namespace")
+
+  el.removeAttributeNS(null, "pre:fix")
+  assert_equals(el.attributes[0], unprefixed)
+}, "Attribute with prefix in local name")
+
+test(function() {
+  var el = document.createElement("div")
+  el.setAttribute("foo", "bar")
+  var attr = el.attributes[0]
+  assert_equals(attr.ownerElement, el)
+  el.removeAttribute("foo")
+  assert_equals(attr.ownerElement, null)
+}, "Attribute loses its owner when removed")
+
+test(function() {
+  var el = document.createElement("div")
+  el.setAttribute("foo", "bar")
+  var attr = el.attributes[0]
+  var attrNode = el.getAttributeNode("foo");
+  var attrNodeNS = el.getAttributeNodeNS("", "foo");
+  assert_equals(attr, attrNode);
+  assert_equals(attr, attrNodeNS);
+  el.setAttributeNS("x", "foo2", "bar2");
+  var attr2 = el.attributes[1];
+  var attrNodeNS2 = el.getAttributeNodeNS("x", "foo2");
+  assert_equals(attr2, attrNodeNS2);
+}, "Basic functionality of getAttributeNode/getAttributeNodeNS")
+
+test(function() {
+  var el = document.createElement("div")
+  el.setAttribute("foo", "bar")
+  var attrNode = el.getAttributeNode("foo");
+  var attrNodeNS = el.getAttributeNodeNS("", "foo");
+  assert_equals(attrNode, attrNodeNS);
+  el.removeAttribute("foo");
+  var el2 = document.createElement("div");
+  el2.setAttributeNode(attrNode);
+  assert_equals(attrNode, el2.getAttributeNode("foo"));
+  assert_equals(attrNode, el2.attributes[0]);
+  assert_equals(attrNode.ownerElement, el2);
+  assert_equals(attrNode.value, "bar");
+
+   var el3 = document.createElement("div");
+   el2.removeAttribute("foo");
+   el3.setAttribute("foo", "baz");
+   el3.setAttributeNode(attrNode);
+   assert_equals(el3.getAttribute("foo"), "bar");
+}, "Basic functionality of setAttributeNode")
+
+test(function() {
+  var el = document.createElement("div");
+  var attr1 = document.createAttributeNS("ns1", "p1:name");
+  attr1.value = "value1";
+  var attr2 = document.createAttributeNS("ns2", "p2:name");
+  attr2.value = "value2";
+  el.setAttributeNode(attr1);
+  el.setAttributeNode(attr2);
+  assert_equals(el.getAttributeNodeNS("ns1", "name").value, "value1");
+  assert_equals(el.getAttributeNodeNS("ns2", "name").value, "value2");
+}, "setAttributeNode should distinguish attributes with same local name and different namespaces")
+
+test(function() {
+  var el = document.createElement("div");
+  var attr1 = document.createAttributeNS("ns1", "p1:name");
+  attr1.value = "value1";
+  var attr2 = document.createAttributeNS("ns1", "p1:NAME");
+  attr2.value = "VALUE2";
+  el.setAttributeNode(attr1);
+  el.setAttributeNode(attr2);
+  assert_equals(el.getAttributeNodeNS("ns1", "name").value, "value1");
+  assert_equals(el.getAttributeNodeNS("ns1", "NAME").value, "VALUE2");
+}, "setAttributeNode doesn't have case-insensitivity even with an HTMLElement 1")
+
+test(function() {
+  var el = document.createElement("div");
+  var attr1 = document.createAttributeNS("ns1", "FOOBAR");
+  var attr2 = document.createAttributeNS("ns1", "FOOBAR");
+  assert_equals(el.setAttributeNode(attr1), null);
+  assert_equals(attr1.ownerElement, el);
+  assert_equals(attr2.ownerElement, null);
+  var oldAttr = el.setAttributeNode(attr2);
+  assert_equals(oldAttr, attr1);
+  assert_equals(attr1.ownerElement, null);
+  assert_equals(attr2.ownerElement, el);
+}, "setAttributeNode doesn't have case-insensitivity even with an HTMLElement 2")
+
+test(function() {
+  var el = document.createElement("div");
+  var attr1 = document.createAttributeNS("ns1", "foobar");
+  var attr2 = document.createAttributeNS("ns1", "FOOBAR");
+  assert_equals(el.setAttributeNode(attr1), null);
+  assert_equals(attr1.ownerElement, el);
+  assert_equals(attr2.ownerElement, null);
+  assert_equals(el.setAttributeNode(attr2), null);
+  assert_equals(attr1.ownerElement, el);
+  assert_equals(attr2.ownerElement, el);
+}, "setAttributeNode doesn't have case-insensitivity even with an HTMLElement 3")
+
+test(function() {
+  var el = document.createElement("div")
+  el.setAttributeNS("x", "foo", "bar")
+  var attrNode = el.getAttributeNodeNS("x", "foo");
+  el.removeAttribute("foo");
+  var el2 = document.createElement("div");
+  el2.setAttributeNS("x", "foo", "baz");
+  el2.setAttributeNodeNS(attrNode);
+  assert_equals(el2.getAttributeNS("x", "foo"), "bar");
+}, "Basic functionality of setAttributeNodeNS")
+
+test(function() {
+  var el = document.createElement("div");
+  var other = document.createElement("div");
+  var attr = document.createAttribute("foo");
+  assert_equals(el.setAttributeNode(attr), null);
+  assert_equals(attr.ownerElement, el);
+  assert_throws_dom("INUSE_ATTRIBUTE_ERR",
+                  function() { other.setAttributeNode(attr) },
+                  "Attribute already associated with el")
+}, "If attr’s element is neither null nor element, throw an InUseAttributeError.");
+
+test(function() {
+  var el = document.createElement("div");
+  var attr = document.createAttribute("foo");
+  assert_equals(el.setAttributeNode(attr), null);
+  el.setAttribute("bar", "qux");
+  assert_equals(el.setAttributeNode(attr), attr);
+  assert_equals(el.attributes[0], attr);
+}, "Replacing an attr by itself");
+
+test(function() {
+  var el = document.createElement("div")
+  el.setAttribute("foo", "bar")
+  var attrNode = el.getAttributeNode("foo");
+  el.removeAttributeNode(attrNode);
+  var el2 = document.createElement("div");
+  el2.setAttributeNode(attrNode);
+  assert_equals(el2.attributes[0], attrNode);
+  assert_equals(el.attributes.length, 0);
+}, "Basic functionality of removeAttributeNode")
+
+test(function() {
+  var el = document.createElement("div")
+  el.setAttribute("foo", "bar")
+  var attrNode = el.getAttributeNode("foo");
+  var el2 = document.createElement("div");
+  assert_throws_dom("INUSE_ATTRIBUTE_ERR", function(){el2.setAttributeNode(attrNode)});
+}, "setAttributeNode on bound attribute should throw InUseAttributeError")
+
+// Have to use an async_test to see what a DOMAttrModified listener sees,
+// because otherwise the event dispatch code will swallow our exceptions.  And
+// we want to make sure this test always happens, even when no mutation events
+// run.
+var setAttributeNode_mutation_test = async_test("setAttributeNode, if it fires mutation events, should fire one with the new node when resetting an existing attribute");
+
+test(function(){
+  var el = document.createElement("div")
+  var attrNode1 = document.createAttribute("foo");
+  attrNode1.value = "bar";
+  el.setAttributeNode(attrNode1);
+  var attrNode2 = document.createAttribute("foo");
+  attrNode2.value = "baz";
+
+  el.addEventListener("DOMAttrModified", function(e) {
+    // If this never gets called, that's OK, I guess.  But if it gets called, it
+    // better represent a single modification with attrNode2 as the relatedNode.
+    // We have to do an inner test() call here, because otherwise the exceptions
+    // our asserts trigger will get swallowed by the event dispatch code.
+    setAttributeNode_mutation_test.step(function() {
+      assert_equals(e.attrName, "foo");
+      assert_equals(e.attrChange, MutationEvent.MODIFICATION);
+      assert_equals(e.prevValue, "bar");
+      assert_equals(e.newValue, "baz");
+      assert_equals(e.relatedNode, attrNode2);
+    });
+  });
+
+  var oldNode = el.setAttributeNode(attrNode2);
+  assert_equals(oldNode, attrNode1,
+                "Must return the old attr node from a setAttributeNode call");
+}, "setAttributeNode, if it fires mutation events, should fire one with the new node when resetting an existing attribute (outer shell)");
+setAttributeNode_mutation_test.done();
+
+test(function(){
+  var el = document.createElement("div")
+  el.setAttribute("a", "b");
+  el.setAttribute("c", "d");
+
+  assert_array_equals(Array.prototype.map.call(el.attributes, function(a) { return a.name }),
+                      ["a", "c"]);
+  assert_array_equals(Array.prototype.map.call(el.attributes, function(a) { return a.value }),
+                      ["b", "d"]);
+
+  var attrNode = document.createAttribute("a");
+  attrNode.value = "e";
+  el.setAttributeNode(attrNode);
+
+  assert_array_equals(Array.prototype.map.call(el.attributes, function(a) { return a.name }),
+                      ["a", "c"]);
+  assert_array_equals(Array.prototype.map.call(el.attributes, function(a) { return a.value }),
+                      ["e", "d"]);
+}, "setAttributeNode called with an Attr that has the same name as an existing one should not change attribute order");
+
+test(function() {
+  var el = document.createElement("div");
+  el.setAttribute("foo", "bar");
+  assert_equals(el.getAttributeNames().length, 1);
+  assert_equals(el.getAttributeNames()[0], el.attributes[0].name);
+  assert_equals(el.getAttributeNames()[0], "foo");
+
+  el.removeAttribute("foo");
+  assert_equals(el.getAttributeNames().length, 0);
+
+  el.setAttribute("foo", "bar");
+  el.setAttributeNS("", "FOO", "bar");
+  el.setAttributeNS("dummy1", "foo", "bar");
+  el.setAttributeNS("dummy2", "dummy:foo", "bar");
+  assert_equals(el.getAttributeNames().length, 4);
+  assert_equals(el.getAttributeNames()[0], "foo");
+  assert_equals(el.getAttributeNames()[1], "FOO");
+  assert_equals(el.getAttributeNames()[2], "foo");
+  assert_equals(el.getAttributeNames()[3], "dummy:foo");
+  assert_equals(el.getAttributeNames()[0], el.attributes[0].name);
+  assert_equals(el.getAttributeNames()[1], el.attributes[1].name);
+  assert_equals(el.getAttributeNames()[2], el.attributes[2].name);
+  assert_equals(el.getAttributeNames()[3], el.attributes[3].name);
+
+  el.removeAttributeNS("", "FOO");
+  assert_equals(el.getAttributeNames().length, 3);
+  assert_equals(el.getAttributeNames()[0], "foo");
+  assert_equals(el.getAttributeNames()[1], "foo");
+  assert_equals(el.getAttributeNames()[2], "dummy:foo");
+  assert_equals(el.getAttributeNames()[0], el.attributes[0].name);
+  assert_equals(el.getAttributeNames()[1], el.attributes[1].name);
+  assert_equals(el.getAttributeNames()[2], el.attributes[2].name);
+}, "getAttributeNames tests");
+
+function getEnumerableOwnProps1(obj) {
+  var arr = [];
+  for (var prop in obj) {
+    if (obj.hasOwnProperty(prop)) {
+      arr.push(prop);
+    }
+  }
+  return arr;
+}
+
+function getEnumerableOwnProps2(obj) {
+  return Object.getOwnPropertyNames(obj).filter(
+    function (name) { return Object.getOwnPropertyDescriptor(obj, name).enumerable; })
+}
+
+test(function() {
+  var el = document.createElement("div");
+  el.setAttribute("a", "");
+  el.setAttribute("b", "");
+  assert_array_equals(getEnumerableOwnProps1(el.attributes),
+                      ["0", "1"])
+  assert_array_equals(getEnumerableOwnProps2(el.attributes),
+                      ["0", "1"])
+  assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+                      ["0", "1", "a", "b"])
+}, "Own property correctness with basic attributes");
+
+test(function() {
+  var el = document.createElement("div");
+  el.setAttributeNS("", "a", "");
+  el.setAttribute("b", "");
+  el.setAttributeNS("foo", "a", "");
+  assert_array_equals(getEnumerableOwnProps1(el.attributes),
+                      ["0", "1", "2"])
+  assert_array_equals(getEnumerableOwnProps2(el.attributes),
+                      ["0", "1", "2"])
+  assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+                      ["0", "1", "2", "a", "b"])
+  for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+    assert_true(el.attributes[propName] instanceof Attr,
+                "el.attributes has an Attr for property name " + propName);
+  }
+}, "Own property correctness with non-namespaced attribute before same-name namespaced one");
+
+test(function() {
+  var el = document.createElement("div");
+  el.setAttributeNS("foo", "a", "");
+  el.setAttribute("b", "");
+  el.setAttributeNS("", "a", "");
+  assert_array_equals(getEnumerableOwnProps1(el.attributes),
+                      ["0", "1", "2"])
+  assert_array_equals(getEnumerableOwnProps2(el.attributes),
+                      ["0", "1", "2"])
+  assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+                      ["0", "1", "2", "a", "b"])
+  for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+    assert_true(el.attributes[propName] instanceof Attr,
+                "el.attributes has an Attr for property name " + propName);
+  }
+}, "Own property correctness with namespaced attribute before same-name non-namespaced one");
+
+test(function() {
+  var el = document.createElement("div");
+  el.setAttributeNS("foo", "a:b", "");
+  el.setAttributeNS("foo", "c:d", "");
+  el.setAttributeNS("bar", "a:b", "");
+  assert_array_equals(getEnumerableOwnProps1(el.attributes),
+                      ["0", "1", "2"])
+  assert_array_equals(getEnumerableOwnProps2(el.attributes),
+                      ["0", "1", "2"])
+  assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+                      ["0", "1", "2", "a:b", "c:d"])
+  for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+    assert_true(el.attributes[propName] instanceof Attr,
+                "el.attributes has an Attr for property name " + propName);
+  }
+}, "Own property correctness with two namespaced attributes with the same name-with-prefix");
+
+test(function() {
+  var el = document.createElement("div");
+  el.setAttributeNS("foo", "A:B", "");
+  el.setAttributeNS("bar", "c:D", "");
+  el.setAttributeNS("baz", "e:F", "");
+  el.setAttributeNS("qux", "g:h", "");
+  el.setAttributeNS("", "I", "");
+  el.setAttributeNS("", "j", "");
+  assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+                      ["0", "1", "2", "3", "4", "5", "g:h", "j"])
+  for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+    assert_true(el.attributes[propName] instanceof Attr,
+                "el.attributes has an Attr for property name " + propName);
+  }
+}, "Own property names should only include all-lowercase qualified names for an HTML element in an HTML document");
+
+test(function() {
+  var el = document.createElementNS("", "div");
+  el.setAttributeNS("foo", "A:B", "");
+  el.setAttributeNS("bar", "c:D", "");
+  el.setAttributeNS("baz", "e:F", "");
+  el.setAttributeNS("qux", "g:h", "");
+  el.setAttributeNS("", "I", "");
+  el.setAttributeNS("", "j", "");
+  assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+                      ["0", "1", "2", "3", "4", "5", "A:B", "c:D", "e:F", "g:h", "I", "j"])
+  for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+    assert_true(el.attributes[propName] instanceof Attr,
+                "el.attributes has an Attr for property name " + propName);
+  }
+}, "Own property names should include all qualified names for a non-HTML element in an HTML document");
+
+test(function() {
+  var doc = document.implementation.createDocument(null, "");
+  assert_equals(doc.contentType, "application/xml");
+  var el = doc.createElementNS("http://www.w3.org/1999/xhtml", "div");
+  el.setAttributeNS("foo", "A:B", "");
+  el.setAttributeNS("bar", "c:D", "");
+  el.setAttributeNS("baz", "e:F", "");
+  el.setAttributeNS("qux", "g:h", "");
+  el.setAttributeNS("", "I", "");
+  el.setAttributeNS("", "j", "");
+  assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+                      ["0", "1", "2", "3", "4", "5", "A:B", "c:D", "e:F", "g:h", "I", "j"])
+  for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+    assert_true(el.attributes[propName] instanceof Attr,
+                "el.attributes has an Attr for property name " + propName);
+  }
+}, "Own property names should include all qualified names for an HTML element in a non-HTML document");
+</script>

+ 18 - 0
Tests/LibWeb/Text/input/wpt-import/dom/nodes/attributes.js

@@ -0,0 +1,18 @@
+function attr_is(attr, v, ln, ns, p, n) {
+  assert_equals(attr.value, v)
+  assert_equals(attr.nodeValue, v)
+  assert_equals(attr.textContent, v)
+  assert_equals(attr.localName, ln)
+  assert_equals(attr.namespaceURI, ns)
+  assert_equals(attr.prefix, p)
+  assert_equals(attr.name, n)
+  assert_equals(attr.nodeName, n);
+  assert_equals(attr.specified, true)
+}
+
+function attributes_are(el, l) {
+  for (var i = 0, il = l.length; i < il; i++) {
+    attr_is(el.attributes[i], l[i][1], l[i][0], (l[i].length < 3) ? null : l[i][2], null, l[i][0])
+    assert_equals(el.attributes[i].ownerElement, el)
+  }
+}

+ 3 - 0
Tests/LibWeb/Text/input/wpt-import/dom/nodes/productions.js

@@ -0,0 +1,3 @@
+var invalid_names = ["", "invalid^Name", "\\", "'", '"', "0", "0:a"] // XXX
+var valid_names = ["x", "X", ":", "a:0"]
+var invalid_qnames = [":a", "b:", "x:y:z"] // XXX