name-validation.html (12716B)
1 <!DOCTYPE html> 2 <meta name=timeout content=long> 3 <link rel=author href="mailto:jarhar@chromium.org"> 4 <link rel=help href="https://github.com/whatwg/dom/pull/1079"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 8 <script> 9 function isAsciiAlpha(codePoint) { 10 return (codePoint >= 0x41 && codePoint <= 0x5A) || (codePoint >= 0x61 && codePoint <= 0x7A); 11 } 12 function isAsciiDigit(codePoint) { 13 return codePoint >= 0x30 && codePoint <= 0x39; 14 } 15 function isAsciiWhitespace(codePoint) { 16 return codePoint == 0x9 || codePoint == 0xA || codePoint == 0xC || codePoint == 0xD || codePoint == 0x20; 17 } 18 19 function debugString(str) { 20 const codePoints = []; 21 for (let i = 0; i < str.length; i++) { 22 codePoints.push(str.charCodeAt(i)); 23 } 24 return `code points: ${JSON.stringify(codePoints)}, string: "${str}"`; 25 } 26 27 const latin1CodePoint = 100; 28 const latin1 = String.fromCodePoint(latin1CodePoint); 29 const smallEmoji = 'smallEmoji🆖'; 30 const bigEmoji = 'bigEmoji🅱️'; 31 32 // Testing every variation of a namespace prefix with every variation of a 33 // local name would make the test take too long to run, so use these instead when 34 // combining with a namespace prefix. 35 const validElementLocalNamesShortened = [ 36 'div', `latin1${latin1}`, smallEmoji, bigEmoji 37 ]; 38 const invalidElementLocalNamesShortened = [ 39 '', 'space ', 'newline\n', 'null\0', `:soh${String.fromCodePoint(1)}`, '5' 40 ]; 41 const validAttributeLocalNamesShortened = [ 42 'attr', `latin1${latin1}`, smallEmoji, bigEmoji 43 ]; 44 const invalidAttributeLocalNamesShortened = [ 45 '', 'space ', 'newline\n', 'null\0' 46 ]; 47 48 const validElementLocalNames = validElementLocalNamesShortened.slice(); 49 const invalidElementLocalNames = invalidElementLocalNamesShortened.slice(); 50 const validAttributeLocalNames = validAttributeLocalNamesShortened.slice(); 51 const invalidAttributeLocalNames = invalidAttributeLocalNamesShortened.slice(); 52 const validNamespacePrefixes = [smallEmoji, bigEmoji]; 53 const invalidNamespacePrefixes = ['']; 54 const validDoctypes = ['']; 55 const invalidDoctypes = []; 56 57 const codePoints = []; 58 for (let i = 0; i < 0x80; i++) { 59 codePoints.push(i); 60 } 61 codePoints.push(latin1CodePoint); 62 63 // attributes and namespaces 64 for (const codePoint of codePoints) { 65 const str = String.fromCodePoint(codePoint); 66 if (codePoint == 0 || isAsciiWhitespace(codePoint) || codePoint == 0x2F || codePoint == 0x3E) { 67 invalidNamespacePrefixes.push(str); 68 invalidAttributeLocalNames.push(str); 69 } else if (codePoint == 0x3A) { 70 // colons are not valid namespace prefixes, but due to parsing they can 71 // never be considered as a namespace prefix, only as a separator between the 72 // prefix and the local name. 73 validAttributeLocalNames.push(str); 74 } else if (codePoint == 0x3D) { 75 validNamespacePrefixes.push(str); 76 invalidAttributeLocalNames.push(str); 77 } else { 78 validNamespacePrefixes.push(str); 79 validAttributeLocalNames.push(str); 80 } 81 } 82 83 // valid element local names 84 for (const firstChar of codePoints) { 85 for (const secondChar of codePoints) { 86 const str = `${String.fromCodePoint(firstChar)}${String.fromCodePoint(secondChar)}`; 87 if (isAsciiAlpha(firstChar)) { 88 if (!secondChar || secondChar == 0x2F || secondChar == 0x3E || isAsciiWhitespace(secondChar)) { 89 invalidElementLocalNames.push(str); 90 } else { 91 validElementLocalNames.push(str); 92 } 93 } else { 94 if (firstChar == 0x3A || firstChar == 0x5F || firstChar >= 0x80) { 95 if (isAsciiAlpha(secondChar) || 96 isAsciiDigit(secondChar) || 97 secondChar == 0x2D || 98 secondChar == 0x2E || 99 secondChar == 0x3A || 100 secondChar == 0x5F || 101 secondChar >= 0x80) { 102 validElementLocalNames.push(str); 103 } else { 104 invalidElementLocalNames.push(str); 105 } 106 } else { 107 invalidElementLocalNames.push(str); 108 } 109 } 110 } 111 } 112 113 // doctypes 114 for (const codePoint of codePoints) { 115 const str = String.fromCodePoint(codePoint); 116 if (codePoint == 0 || isAsciiWhitespace(codePoint) || codePoint == 0x3E) { 117 invalidDoctypes.push(str); 118 } else { 119 validDoctypes.push(str); 120 } 121 } 122 123 test(() => { 124 // This regex is provided in the spec and is used here to double check our 125 // test input. 126 const validNameRegex = /^(?:[A-Za-z][^\0\t\n\f\r\u0020/>]*|[:_\u0080-\u{10FFFF}][A-Za-z0-9-.:_\u0080-\u{10FFFF}]*)$/u; 127 for (const validName of validElementLocalNames) { 128 assert_true( 129 validNameRegex.test(validName), 130 `Regex should match: ${debugString(validName)}`); 131 try { 132 document.createElement(validName); 133 } catch (error) { 134 assert_unreached( 135 `document.createElement should not have thrown an error for: ${debugString(validName)} ${error.toString()}`); 136 } 137 } 138 for (const invalidName of invalidElementLocalNames) { 139 assert_false( 140 validNameRegex.test(invalidName), 141 `Regex should not match: ${debugString(invalidName)}`); 142 assert_throws_dom( 143 'InvalidCharacterError', 144 () => document.createElement(invalidName), 145 `document.createElement should throw an error for: ${debugString(invalidName)}`); 146 } 147 }, 'Valid and invalid characters in createElement.'); 148 149 test(() => { 150 for (const validNamespace of validNamespacePrefixes) { 151 for (const validName of validElementLocalNamesShortened) { 152 try { 153 document.createElementNS('namespaceuri', `${validNamespace}:${validName}`); 154 } catch (error) { 155 assert_unreached( 156 `document.createElementNS should not have thrown an error for: ${debugString(validNamespace)} ${debugString(validName)} ${error.toString()}`); 157 } 158 try { 159 document.implementation.createDocument('namespaceuri', `${validNamespace}:${validName}`); 160 } catch (error) { 161 assert_unreached( 162 `createDocument should not have thrown an error for: ${debugString(validNamespace)} ${debugString(validName)} ${error.toString()}`); 163 } 164 } 165 for (const invalidName of invalidElementLocalNamesShortened) { 166 assert_throws_dom( 167 'InvalidCharacterError', 168 () => document.createElementNS('namespaceuri', `${validNamespace}:${invalidName}`), 169 `document.createElementNS should throw an error for: ${debugString(validNamespace)} ${debugString(invalidName)}`); 170 assert_throws_dom( 171 'InvalidCharacterError', 172 () => document.implementation.createDocument('namespaceuri', `${validNamespace}:${invalidName}`), 173 `createDocument should throw an error for: ${debugString(validNamespace)} ${debugString(invalidName)}`); 174 } 175 } 176 for (const invalidNamespace of invalidNamespacePrefixes) { 177 for (const localName of validElementLocalNamesShortened.concat(invalidElementLocalNamesShortened)) { 178 assert_throws_dom( 179 'InvalidCharacterError', 180 () => document.createElementNS('namespaceuri', `${invalidNamespace}:${localName}`), 181 `document.createElementNS should throw an error for: ${debugString(invalidNamespace)} ${debugString(localName)}`); 182 assert_throws_dom( 183 'InvalidCharacterError', 184 () => document.implementation.createDocument('namespaceuri', `${invalidNamespace}:${localName}`), 185 `createDocument should throw an error for: ${debugString(invalidNamespace)} ${debugString(localName)}`); 186 } 187 } 188 }, 'Valid and invalid characters in createElementNS and createDocument.'); 189 190 test(() => { 191 for (const validAttributeName of validAttributeLocalNames) { 192 const element = document.createElement('div'); 193 try { 194 element.setAttribute(validAttributeName, 'value'); 195 } catch (error) { 196 assert_unreached( 197 `element.setAttribute should not have thrown an error for: ${debugString(validAttributeName)} ${error.toString()}`); 198 } 199 try { 200 element.toggleAttribute(validAttributeName); 201 } catch (error) { 202 assert_unreached( 203 `element.toggleAttribute should not have thrown an error for: ${debugString(validAttributeName)} ${error.toString()}`); 204 } 205 try { 206 element.toggleAttribute(validAttributeName, false); 207 } catch (error) { 208 assert_unreached( 209 `element.toggleAttribute with false should not have thrown an error for: ${debugString(validAttributeName)} ${error.toString()}`); 210 } 211 try { 212 document.createAttribute(validAttributeName); 213 } catch (error) { 214 assert_unreached( 215 `document.createAttribute should not have thrown an error for: ${debugString(validAttributeName)} ${error.toString()}`); 216 } 217 } 218 for (const invalidAttributeName of invalidAttributeLocalNames) { 219 const element = document.createElement('div'); 220 assert_throws_dom( 221 'InvalidCharacterError', 222 () => element.setAttribute(invalidAttributeName, 'value'), 223 `element.setAttribute should throw an error for: ${debugString(invalidAttributeName)}`); 224 assert_throws_dom( 225 'InvalidCharacterError', 226 () => element.toggleAttribute(invalidAttributeName), 227 `element.toggleAttribute should throw an error for: ${debugString(invalidAttributeName)}`); 228 assert_throws_dom( 229 'InvalidCharacterError', 230 () => element.toggleAttribute(invalidAttributeName, false), 231 `element.toggleAttribute with false should throw an error for: ${debugString(invalidAttributeName)}`); 232 assert_throws_dom( 233 'InvalidCharacterError', 234 () => document.createAttribute(invalidAttributeName), 235 `document.createAttribute should throw an error for: ${debugString(invalidAttributeName)}`); 236 } 237 }, 'Valid and invalid characters in setAttribute, toggleAttribute, and createAttribute.'); 238 239 test(() => { 240 for (const validNamespace of validNamespacePrefixes) { 241 for (const validLocalName of validAttributeLocalNamesShortened) { 242 const element = document.createElement('div'); 243 try { 244 element.setAttributeNS('namespaceuri', `${validNamespace}:${validLocalName}`, 'value'); 245 } catch (error) { 246 assert_unreached(`element.setAttributeNS should not have thrown an error for: ${debugString(validNamespace)} ${debugString(validLocalName)} ${error.toString()}`); 247 } 248 try { 249 document.createAttributeNS('namespaceuri', `${validNamespace}:${validLocalName}`); 250 } catch (error) { 251 assert_unreached(`document.createAttributeNS should not have thrown an error for: ${debugString(validNamespace)} ${debugString(validLocalName)} ${error.toString()}`); 252 } 253 } 254 for (const invalidLocalName of invalidAttributeLocalNamesShortened) { 255 const element = document.createElement('div'); 256 assert_throws_dom( 257 'InvalidCharacterError', 258 () => element.setAttributeNS('namespaceuri', `${validNamespace}:${invalidLocalName}`, 'value'), 259 `element.setAttributeNS should have thrown an error for: ${debugString(validNamespace)} ${debugString(invalidLocalName)}`); 260 assert_throws_dom( 261 'InvalidCharacterError', 262 () => document.createAttributeNS('namespaceuri', `${validNamespace}:${invalidLocalName}`), 263 `document.createAttributeNS should have thrown an error for: ${debugString(validNamespace)} ${debugString(invalidLocalName)}`); 264 } 265 } 266 for (const invalidNamespace of invalidNamespacePrefixes) { 267 for (const localName of validAttributeLocalNamesShortened.concat(invalidAttributeLocalNamesShortened)) { 268 const element = document.createElement('div'); 269 assert_throws_dom( 270 'InvalidCharacterError', 271 () => element.setAttributeNS('namespaceuri', `${invalidNamespace}:${localName}`, ''), 272 `element.setAttributeNS should have thrown an error for: ${debugString(invalidNamespace)} ${debugString(localName)}`); 273 assert_throws_dom( 274 'InvalidCharacterError', 275 () => document.createAttributeNS('namespaceuri', `${invalidNamespace}:${localName}`), 276 `document.createAttributeNS should have thrown an error for: ${debugString(invalidNamespace)} ${debugString(localName)}`); 277 } 278 } 279 }, 'Valid and invalid characters in setAttributeNS and createAttributeNS.'); 280 281 test(() => { 282 for (const validDoctype of validDoctypes) { 283 try { 284 document.implementation.createDocumentType(validDoctype, 'publicid', 'systemid'); 285 } catch (error) { 286 assert_unreached(`createDocumentType should not have thrown an error for ${debugString(validDoctype)} ${error.toString()}`); 287 } 288 } 289 for (const invalidDoctype of invalidDoctypes) { 290 assert_throws_dom( 291 'InvalidCharacterError', 292 () => document.implementation.createDocumentType(invalidDoctype, 'publicid', 'systemid'), 293 `createDocumentType should have thrown an error for: ${debugString(invalidDoctype)}`); 294 } 295 }, 'Valid and invalid characters in createDocumentType.'); 296 </script>