Skip to content

Commit e67d5fe

Browse files
authored
Allow more characters when creating various nodes
Stop using XML grammar productions for validating element, attribute, and doctype names. These were overly-restrictive, as it is possible to create nodes with names that don't match those productions using the HTML parser. After this change, each construct has its own custom name validation algorithm. The only remaining dependency on XML naming rules is for processing instructions, which are uncommon and cannot be created with the HTML parser anyway. Closes #769. Closes #849. Closes #1373.
1 parent 388779b commit e67d5fe

File tree

1 file changed

+119
-66
lines changed

1 file changed

+119
-66
lines changed

dom.bs

Lines changed: 119 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,9 @@ Indent: 1
1212
</pre>
1313

1414
<pre class=anchors>
15-
urlPrefix: https://www.w3.org/TR/xml/#NT-
16-
type: type
17-
text: Name; url: Name
18-
text: Char; url: Char
19-
text: PubidChar; url: PubidChar
20-
urlPrefix: https://www.w3.org/TR/xml-names/#NT-
21-
type: type
22-
text: QName; url: QName
15+
urlPrefix: https://www.w3.org/TR/xml/#NT-; spec: XML
16+
type: dfn
17+
text: Name; url: Name; for: XML
2318
url: https://w3c.github.io/DOM-Parsing/#dom-range-createcontextualfragment
2419
type: method; text: createContextualFragment(); for: Range
2520
type: interface
@@ -177,19 +172,64 @@ first <a>following</a> <a for=tree>sibling</a> or null if it has no <a for=tree>
177172
added.
178173

179174

180-
<h3 id=namespaces>Namespaces</h3>
175+
<h3 id=namespaces>Name validation</h3>
181176

182-
<p>To <dfn export>validate</dfn> a <var>qualifiedName</var>, <a>throw</a> an
183-
"{{InvalidCharacterError!!exception}}" {{DOMException}} if <var>qualifiedName</var> does not match
184-
the <code><a type>QName</a></code> production.
177+
<p>A [=string=] is a <dfn>valid namespace prefix</dfn> if its [=string/length=] is at least 1 and it
178+
does not contain [=ASCII whitespace=], U+0000 NULL, U+002F (/), or U+003E (>).
185179

186-
<p>To <dfn export>validate and extract</dfn> a <var>namespace</var> and <var>qualifiedName</var>,
187-
run these steps:
180+
<p>A [=string=] is a <dfn>valid attribute local name</dfn> if its [=string/length=] is at least 1
181+
and it does not contain [=ASCII whitespace=], U+0000 NULL, U+002F (/), U+003D (=), or U+003E (>).
182+
183+
<p>A [=string=] |name| is a <dfn>valid element local name</dfn> if the following steps return true:
188184

189185
<ol>
190-
<li><p>If <var>namespace</var> is the empty string, then set it to null.
186+
<li><p>If |name|'s [=string/length=] is 0, then return false.
187+
188+
<li>
189+
<p>If |name|'s 0th [=code point=] is an [=ASCII alpha=], then:
190+
191+
<ol>
192+
<li><p>If |name| contains [=ASCII whitespace=], U+0000 NULL, U+002F (/), or U+003E (>), then
193+
return false.
194+
195+
<li><p>Return true.
196+
</ol>
197+
198+
<li><p>If |name|'s 0th [=code point=] is not U+003A (:), U+005F (_), or in the range U+0080
199+
to U+10FFFF, inclusive, then return false.
200+
201+
<li><p>If |name|'s subsequent [=code points=], if any, are not [=ASCII alphas=], [=ASCII digits=],
202+
U+002D (-), U+002E (.), U+003A (:), U+005F (_), or in the range U+0080 to U+10FFFF, inclusive, then
203+
return false.
204+
205+
<li><p>Return true.
206+
</ol>
207+
208+
<p class=note>This concept is used to validate [=/element=] [=Element/local names=], when
209+
constructed by DOM APIs. The intention is to allow any name that is possible to construct using the
210+
HTML parser (the branch where the first [=code point=] is an [=ASCII alpha=]), plus some additional
211+
possibilities. For those additional possibilities, the ASCII range is restricted for historical
212+
reasons, but beyond ASCII anything is allowed.
213+
214+
<div class=note>
215+
<p>The following JavaScript-compatible regular expression is an implementation of
216+
[=valid element local name=]:
217+
218+
<pre><code class=lang-javascript>
219+
/^(?:[A-Za-z][^\0\t\n\f\r\u0020/>]*|[:_\u0080-\u{10FFFF}][A-Za-z0-9-.:_\u0080-\u{10FFFF}]*)$/u
220+
</code></pre>
221+
</div>
222+
223+
<p>A [=string=] is a <dfn>valid doctype name</dfn> if it does not contain [=ASCII whitespace=],
224+
U+0000 NULL, or U+003E (>).
191225

192-
<li><p><a>Validate</a> <var>qualifiedName</var>.
226+
<p class="note">The empty string is a [=valid doctype name=].
227+
228+
<p>To <dfn>validate and extract</dfn> a <var>namespace</var> and <var>qualifiedName</var>, given a
229+
<var>context</var>:
230+
231+
<ol>
232+
<li><p>If <var>namespace</var> is the empty string, then set it to null.
193233

194234
<li><p>Let <var>prefix</var> be null.
195235

@@ -207,6 +247,15 @@ run these steps:
207247
<li><p>Set <var>localName</var> to <var>splitResult</var>[1].
208248
</ol>
209249

250+
<li><p>If <var>prefix</var> is not a [=valid namespace prefix=], then [=throw=] an
251+
"{{InvalidCharacterError}}" {{DOMException}}.
252+
253+
<li><p>If <var>context</var> is "<code>attribute</code>" and <var>localName</var> is not a
254+
[=valid attribute local name=], then [=throw=] an "{{InvalidCharacterError}}" {{DOMException}}.
255+
256+
<li><p>If <var>context</var> is "<code>element</code>" and <var>localName</var> is not a
257+
[=valid element local name=], then [=throw=] an "{{InvalidCharacterError}}" {{DOMException}}.
258+
210259
<li><p>If <var>prefix</var> is non-null and <var>namespace</var> is null, then <a>throw</a> a
211260
"{{NamespaceError!!exception}}" {{DOMException}}.
212261

@@ -221,9 +270,20 @@ run these steps:
221270
nor <var>prefix</var> is "<code>xmlns</code>", then <a>throw</a> a "{{NamespaceError!!exception}}"
222271
{{DOMException}}.
223272

224-
<li><p>Return <var>namespace</var>, <var>prefix</var>, and <var>localName</var>.
273+
<li><p>Return (<var>namespace</var>, <var>prefix</var>, <var>localName</var>).
225274
</ol>
226275

276+
<div class="note">
277+
<p>Various APIs in this specification used to validate namespace prefixes, attribute local names,
278+
element local names, and doctype names more strictly. This was done in a way that aligned with
279+
various XML-related specifications. (Although not all rules from the those specifications were
280+
enforced.)
281+
282+
<p>This was found to be annoying for web developers, especially since it meant there were some
283+
names that could be created by the HTML parser, but not by DOM APIs. So, the validations have been
284+
loosened to just those described above.
285+
</div>
286+
227287

228288

229289
<h2 id=events>Events</h2>
@@ -5633,8 +5693,8 @@ method steps are to return the <a>list of elements with class names <var>classNa
56335693
<p>When supplied, <var>options</var>'s {{ElementCreationOptions/is}} can be used to create a
56345694
<a>customized built-in element</a>.
56355695

5636-
<p>If <var>localName</var> does not match the <code><a type>Name</a></code> production an
5637-
"{{InvalidCharacterError!!exception}}" {{DOMException}} will be thrown.
5696+
<p>If <var>localName</var> is not a <a>valid element local name</a> an
5697+
"{{InvalidCharacterError}}" {{DOMException}} will be thrown.
56385698

56395699
<p>When both <var>options</var>'s {{ElementCreationOptions/customElementRegistry}} and
56405700
<var>options</var>'s {{ElementCreationOptions/is}} are supplied, a
@@ -5654,8 +5714,9 @@ method steps are to return the <a>list of elements with class names <var>classNa
56545714
<p>When supplied, <var>options</var>'s {{ElementCreationOptions/is}} can be used to create a
56555715
<a>customized built-in element</a>.
56565716

5657-
<p>If <var>qualifiedName</var> does not match the <code><a type>QName</a></code> production an
5658-
"{{InvalidCharacterError!!exception}}" {{DOMException}} will be thrown.
5717+
<p>If <var>qualifiedName</var> is not a (possibly-prefixed)
5718+
<a>valid element local name</a> an "{{InvalidCharacterError}}" {{DOMException}} will be
5719+
thrown.
56595720

56605721
<p>If one of the following conditions is true a "{{NamespaceError!!exception}}" {{DOMException}}
56615722
will be thrown:
@@ -5700,8 +5761,7 @@ method steps are to return the <a>list of elements with class names <var>classNa
57005761
<a for=/>node</a> whose
57015762
<a for=ProcessingInstruction>target</a> is <var>target</var> and
57025763
<a for=CharacterData>data</a> is <var>data</var>.
5703-
If <var>target</var> does not match the
5704-
<code><a type>Name</a></code> production an
5764+
If <var>target</var> does not match the <code>[=XML/Name=]</code> production an
57055765
"{{InvalidCharacterError!!exception}}" {{DOMException}} will be thrown.
57065766
If <var>data</var> contains "<code>?></code>" an
57075767
"{{InvalidCharacterError!!exception}}" {{DOMException}} will be thrown.
@@ -5718,7 +5778,7 @@ method steps are to return the <a>list of elements with class names <var>classNa
57185778
method steps are:
57195779

57205780
<ol>
5721-
<li><p>If <var>localName</var> does not match the <code><a type>Name</a></code> production, then
5781+
<li><p>If <var>localName</var> is not a <a>valid element local name</a>, then
57225782
<a>throw</a> an "{{InvalidCharacterError!!exception}}" {{DOMException}}.
57235783

57245784
<li><p>If <a>this</a> is an <a>HTML document</a>, then set <var>localName</var> to
@@ -5739,8 +5799,9 @@ method steps are:
57395799
<var>namespace</var>, <var>qualifiedName</var>, and <var>options</var>, are as follows:
57405800

57415801
<ol>
5742-
<li><p>Let <var>namespace</var>, <var>prefix</var>, and <var>localName</var> be the result of
5743-
passing <var>namespace</var> and <var>qualifiedName</var> to <a>validate and extract</a>.
5802+
<li><p>Let (<var>namespace</var>, <var>prefix</var>, <var>localName</var>) be the result of
5803+
[=validate and extract|validating and extracting=] <var>namespace</var> and
5804+
<var>qualifiedName</var> given "<code>element</code>".
57445805

57455806
<li><p>Let <var>registry</var> and <var>is</var> be the result of
57465807
<a>flattening element creation options</a> given <var>options</var> and <a>this</a>.
@@ -5795,9 +5856,6 @@ return a new {{DocumentFragment}} <a for=/>node</a> whose <a for=Node>node docum
57955856
to return a new {{Text}} <a for=/>node</a> whose <a for=CharacterData>data</a> is <var>data</var>
57965857
and <a for=Node>node document</a> is <a>this</a>.
57975858

5798-
<p class=note>No check is performed that <var>data</var> consists of
5799-
characters that match the <code><a type>Char</a></code> production.
5800-
58015859
<p>The <dfn method for=Document><code>createCDATASection(<var>data</var>)</code></dfn> method steps
58025860
are:
58035861

@@ -5816,18 +5874,13 @@ are:
58165874
to return a new {{Comment}} <a for=/>node</a> whose <a for=CharacterData>data</a> is <var>data</var>
58175875
and <a for=Node>node document</a> is <a>this</a>.
58185876

5819-
<p class=note>No check is performed that <var>data</var> consists of
5820-
characters that match the <code><a type>Char</a></code> production
5821-
or that it contains two adjacent hyphens or ends with a hyphen.
5822-
58235877
<p>The
58245878
<dfn method for=Document><code>createProcessingInstruction(<var>target</var>, <var>data</var>)</code></dfn>
58255879
method steps are:
58265880

58275881
<ol>
58285882
<li>If <var>target</var> does not match the
5829-
<!--<code data-anolis-type>PITarget</code>-->
5830-
<code><a type>Name</a></code> production,
5883+
<code>[=XML/Name=]</code> production,
58315884
then <a>throw</a> an "{{InvalidCharacterError!!exception}}" {{DOMException}}. <!-- DOM3 does not check for "xml" -->
58325885

58335886
<li>If <var>data</var> contains the string
@@ -5841,11 +5894,6 @@ method steps are:
58415894
<a for=Node>node document</a> set to <a>this</a>.
58425895
</ol>
58435896

5844-
<p class=note>No check is performed that <var>target</var> contains
5845-
"<code>xml</code>" or "<code>:</code>", or that
5846-
<var>data</var> contains characters that match the
5847-
<code><a type>Char</a></code> production.
5848-
58495897
<hr>
58505898

58515899
<dl class=domintro>
@@ -5972,8 +6020,8 @@ these steps:
59726020
steps are:
59736021

59746022
<ol>
5975-
<li><p>If <var>localName</var> does not match the <code><a type>Name</a></code> production in XML,
5976-
then <a>throw</a> an "{{InvalidCharacterError!!exception}}" {{DOMException}}.
6023+
<li><p>If <var>localName</var> is not a <a>valid attribute local name</a>, then
6024+
<a>throw</a> an "{{InvalidCharacterError!!exception}}" {{DOMException}}.
59776025

59786026
<li>If <a>this</a> is an <a>HTML document</a>, then set <var>localName</var> to
59796027
<var>localName</var> in <a>ASCII lowercase</a>.
@@ -5987,8 +6035,9 @@ steps are:
59876035
method steps are:
59886036

59896037
<ol>
5990-
<li><p>Let <var>namespace</var>, <var>prefix</var>, and <var>localName</var> be the result of
5991-
passing <var>namespace</var> and <var>qualifiedName</var> to <a>validate and extract</a>.
6038+
<li><p>Let (<var>namespace</var>, <var>prefix</var>, <var>localName</var>) be the result of
6039+
[=validate and extract|validating and extracting=] <var>namespace</var> and
6040+
<var>qualifiedName</var> given "<code>attribute</code>".
59926041

59936042
<li><p>Return a new <a>attribute</a> whose <a for=Attr>namespace</a> is <var>namespace</var>,
59946043
<a for=Attr>namespace prefix</a> is <var>prefix</var>, <a for=Attr>local name</a> is
@@ -6117,7 +6166,7 @@ created and associate it with that <a for=/>document</a>.
61176166
<pre class=idl>
61186167
[Exposed=Window]
61196168
interface DOMImplementation {
6120-
[NewObject] DocumentType createDocumentType(DOMString qualifiedName, DOMString publicId, DOMString systemId);
6169+
[NewObject] DocumentType createDocumentType(DOMString name, DOMString publicId, DOMString systemId);
61216170
[NewObject] XMLDocument createDocument(DOMString? namespace, [LegacyNullToEmptyString] DOMString qualifiedName, optional DocumentType? doctype = null);
61226171
[NewObject] Document createHTMLDocument(optional DOMString title);
61236172

@@ -6126,16 +6175,12 @@ interface DOMImplementation {
61266175
</pre>
61276176

61286177
<dl class=domintro>
6129-
<dt><code><var>doctype</var> = <var>document</var> . {{Document/implementation}} . {{createDocumentType(qualifiedName, publicId, systemId)}}</code>
6178+
<dt><code><var>doctype</var> = <var>document</var> . {{Document/implementation}} . {{createDocumentType(name, publicId, systemId)}}</code>
61306179

61316180
<dd>
6132-
Returns a <a>doctype</a>, with the given
6133-
<var>qualifiedName</var>, <var>publicId</var>, and
6134-
<var>systemId</var>. If <var>qualifiedName</var> does not
6135-
match the <code><a type>Name</a></code> production, an
6136-
"{{InvalidCharacterError!!exception}}" {{DOMException}} is thrown, and if it does not match the
6137-
<code><a type>QName</a></code> production, a
6138-
"{{NamespaceError!!exception}}" {{DOMException}} is thrown.
6181+
Returns a <a>doctype</a>, with the given <var>name</var>, <var>publicId</var>, and <var>systemId</var>.
6182+
6183+
If <var>name</var> is not a <a>valid doctype name</a>, an "{{InvalidCharacterError}}" {{DOMException}} is thrown.
61396184

61406185
<dt><code><var>doc</var> = <var>document</var> . {{Document/implementation}} . <a method for=DOMImplementation lt=createDocument()>createDocument(<var>namespace</var>, <var>qualifiedName</var> [, <var>doctype</var> = null])</a></code>
61416186

@@ -6164,22 +6209,19 @@ interface DOMImplementation {
61646209
<div class=impl>
61656210

61666211
<p>The
6167-
<dfn method for=DOMImplementation><code>createDocumentType(<var>qualifiedName</var>, <var>publicId</var>, <var>systemId</var>)</code></dfn>
6212+
<dfn method for=DOMImplementation><code>createDocumentType(<var>name</var>, <var>publicId</var>, <var>systemId</var>)</code></dfn>
61686213
method steps are:
61696214

61706215
<ol>
6171-
<li><p><a>Validate</a> <var>qualifiedName</var>.
6216+
<li><p>If <var>name</var> is not a <a>valid doctype name</a>, then throw an "{{InvalidCharacterError}}"
6217+
{{DOMException}}.
61726218

6173-
<li><p>Return a new <a>doctype</a>, with <var>qualifiedName</var> as its
6219+
<li><p>Return a new <a>doctype</a>, with <var>name</var> as its
61746220
<a for=DocumentType>name</a>, <var>publicId</var> as its <a>public ID</a>, and <var>systemId</var>
61756221
as its <a>system ID</a>, and with its <a for=Node>node document</a> set to the associated
61766222
<a for=/>document</a> of <a>this</a>.
61776223
</ol>
61786224

6179-
<p class=note>No check is performed that <var>publicId</var> code points match the
6180-
<code><a type>PubidChar</a></code> production or that <var>systemId</var> does not contain both a
6181-
'<code>"</code>' and a "<code>'</code>".
6182-
61836225
<p>The
61846226
<dfn method for=DOMImplementation><code>createDocument(<var>namespace</var>, <var>qualifiedName</var>, <var>doctype</var>)</code></dfn>
61856227
method steps are:
@@ -7284,8 +7326,14 @@ method steps are:
72847326
method steps are:
72857327

72867328
<ol>
7287-
<li><p>If <var>qualifiedName</var> does not match the <code><a type>Name</a></code> production in
7288-
XML, then <a>throw</a> an "{{InvalidCharacterError!!exception}}" {{DOMException}}.
7329+
<li>
7330+
<p>If <var>qualifiedName</var> is not a <a>valid attribute local name</a>, then <a>throw</a> an
7331+
"{{InvalidCharacterError!!exception}}" {{DOMException}}.
7332+
7333+
<p class="note" id="node-setAttribute-qualifiedName">Despite the parameter naming,
7334+
<var>qualifiedName</var> is only used as a [=Attr/qualified name=] if an [=attribute=] already
7335+
exists with that qualified name. Otherwise, it is used as the [=Attr/local name=] of the new
7336+
attribute. We only need to validate it for the latter case.
72897337

72907338
<li><p>If <a>this</a> is in the <a>HTML namespace</a> and its <a for=Node>node document</a> is an
72917339
<a>HTML document</a>, then set <var>qualifiedName</var> to <var>qualifiedName</var> in
@@ -7309,8 +7357,9 @@ method steps are:
73097357
method steps are:
73107358

73117359
<ol>
7312-
<li><p>Let <var>namespace</var>, <var>prefix</var>, and <var>localName</var> be the result of
7313-
passing <var>namespace</var> and <var>qualifiedName</var> to <a>validate and extract</a>.
7360+
<li><p>Let (<var>namespace</var>, <var>prefix</var>, <var>localName</var>) be the result of
7361+
[=validate and extract|validating and extracting=] <var>namespace</var> and
7362+
<var>qualifiedName</var> given "<code>element</code>".
73147363

73157364
<li><p><a>Set an attribute value</a> for <a>this</a> using <var>localName</var>, <var>value</var>,
73167365
and also <var>prefix</var> and <var>namespace</var>.
@@ -7343,8 +7392,12 @@ steps are:
73437392
method steps are:
73447393

73457394
<ol>
7346-
<li><p>If <var>qualifiedName</var> does not match the <code><a type>Name</a></code> production in
7347-
XML, then <a>throw</a> an "{{InvalidCharacterError!!exception}}" {{DOMException}}.
7395+
<li>
7396+
<p>If <var>qualifiedName</var> is not a <a>valid attribute local name</a>, then <a>throw</a> an
7397+
"{{InvalidCharacterError!!exception}}" {{DOMException}}.
7398+
7399+
<p class="note">See <a href="#node-setAttribute-qualifiedName">the discussion above</a> about why
7400+
we validate it as a local name, instead of a qualified name.
73487401

73497402
<li><p>If <a>this</a> is in the <a>HTML namespace</a> and its <a for=Node>node document</a> is an
73507403
<a>HTML document</a>, then set <var>qualifiedName</var> to <var>qualifiedName</var> in

0 commit comments

Comments
 (0)