domino 2.0.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -1
- package/README.md +7 -4
- package/lib/CharacterData.js +15 -18
- package/lib/ChildNode.js +6 -1
- package/lib/Comment.js +7 -1
- package/lib/ContainerNode.js +7 -2
- package/lib/DOMException.js +2 -2
- package/lib/DOMImplementation.js +18 -10
- package/lib/DOMTokenList.js +67 -19
- package/lib/Document.js +150 -57
- package/lib/DocumentType.js +3 -6
- package/lib/Element.js +412 -126
- package/lib/FilteredElementList.js +13 -12
- package/lib/HTMLParser.js +15 -2
- package/lib/NamedNodeMap.js +41 -0
- package/lib/Node.js +43 -12
- package/lib/NodeIterator.js +110 -39
- package/lib/NodeList.es5.js +15 -0
- package/lib/NodeList.es6.js +12 -0
- package/lib/NodeList.js +9 -9
- package/lib/ProcessingInstruction.js +7 -1
- package/lib/Text.js +16 -5
- package/lib/TreeWalker.js +143 -98
- package/lib/URLUtils.js +194 -186
- package/lib/Window.js +1 -1
- package/lib/htmlelts.js +58 -12
- package/lib/impl.js +1 -0
- package/lib/index.js +2 -2
- package/lib/select.js +2 -1
- package/lib/utils.js +6 -1
- package/package.json +3 -2
- package/test/domino.js +155 -4
- package/test/w3c/level1/core/hc_characterdataindexsizeerrdeletedatacountnegative.js +1 -0
- package/test/w3c/level1/core/hc_characterdataindexsizeerrreplacedatacountnegative.js +1 -0
- package/test/w3c/level1/core/hc_characterdataindexsizeerrsubstringcountnegative.js +1 -0
- package/test/web-platform-blacklist.json +4669 -0
- package/test/web-platform-tests.js +289 -223
- package/test/xss.js +85 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,56 @@
|
|
|
1
|
+
# domino 2.1.0 (13 Aug 2018)
|
|
2
|
+
* Fix `ContainerNode#removeChildren()` when there is more than one child (#129)
|
|
3
|
+
* Implement `Document#scrollingElement` (#107)
|
|
4
|
+
* Implement setter for `Element#outerHTML` (#102)
|
|
5
|
+
* Handle null/undefined in setter for `Node#textContent`
|
|
6
|
+
* Handle null/undefined/negative values in `CharacterData` interface methods
|
|
7
|
+
* Spec-correctness fixes for `DOMTokenList`, including handling of duplicate
|
|
8
|
+
keys.
|
|
9
|
+
* Fix `[src=...]` selectors in `Document#querySelector()` and similar
|
|
10
|
+
* Spec-correctness fixes for `Document#createElement()` and
|
|
11
|
+
`Document#createElementNS()`, including proper exception type and type
|
|
12
|
+
coercion.
|
|
13
|
+
* Implement `Attr#cloneNode()`, `Element#getAttributeNode()`,
|
|
14
|
+
`Element#getAttributeNodeNS()`, `Element#setAttributeNode()`,
|
|
15
|
+
`Element#setAttributeNodeNS()`, and `Element#removeAttributeNode()`
|
|
16
|
+
(DOM3 compatibility)
|
|
17
|
+
* Implement `Document#createAttribute()` and `Document#createAttributeNS()`
|
|
18
|
+
* Implement `Element#hasAttributes()`, `Element#toggleAttribute()`, and
|
|
19
|
+
`Element#getAttributeNames()`
|
|
20
|
+
* Implement `Text#wholeText`
|
|
21
|
+
* Implement `Document#cloneNode()` and `DocumentType#cloneNode()`
|
|
22
|
+
* Spec-correctness fixes for `Node#lookupPrefix()`,
|
|
23
|
+
`Node#lookupNamespaceURI()`, and `Node#isDefaultNamespace`, including
|
|
24
|
+
proper type coercion and reconciling DOM 3 and DOM 4 specifications.
|
|
25
|
+
* Ensure `Document#title` continues to use correct whitespace stripping
|
|
26
|
+
for node > 4, and properly set `<title>` when `undefined` is passed to
|
|
27
|
+
`DOMImplementation#createHTMLDocument()`
|
|
28
|
+
* Ensure `Element#attributes` implements `NamedNodeMap` and that indexed
|
|
29
|
+
properties of `Element#attributes` work (previously you needed to use
|
|
30
|
+
the `item()` accessor method)
|
|
31
|
+
* Improve stubs for `HTMLElement#style`, `Document#documentURI`, and
|
|
32
|
+
`Document#contentType`
|
|
33
|
+
* Implement proper accessors for `HTMLSelectElement#autocomplete`,
|
|
34
|
+
`HTMLTextAreaElement#type/value/defaultValue/textLength`, and
|
|
35
|
+
`HTMLInputElement#width/height/minLength`
|
|
36
|
+
* Implement `Element#insertAdjacentElement()`, `Element#insertAdjacentText()`,
|
|
37
|
+
and `Element#insertAdjacentHTML()` (#102)
|
|
38
|
+
* Spec-correctness fixes for `TreeWalker` and `NodeIterator`: read-only
|
|
39
|
+
properties, proper exception types, type coercion of `NodeFilter` results.
|
|
40
|
+
* Implement `NodeIterator` pre-removal steps. Note that in the absence
|
|
41
|
+
of weak references, be cautious about the number of `NodeIterator`s you
|
|
42
|
+
create on any single document, since domino does not artificially limit
|
|
43
|
+
these.
|
|
44
|
+
See https://github.com/tc39/proposal-weakrefs/issues/17 for details.
|
|
45
|
+
* Preserve prefix of SVG elements during parsing. (#102)
|
|
46
|
+
|
|
1
47
|
# domino 2.0.3 (12 Jul 2018)
|
|
2
48
|
* Define `blur()`, `focus()` and `forceSpellCheck()` on `HTMLElement` (#125)
|
|
3
49
|
* Stringify argument tokens for DOMTokenList methods (#126)
|
|
4
50
|
* Fix `HTMLAnchorElement#hash` when `href` attribute contains bare
|
|
5
51
|
fragment (#127)
|
|
6
52
|
* Implement case-insensitive CSS attribute matching (#128)
|
|
7
|
-
* Implement DOMTokenList#replace()`, `DOMTokenList#toggle(token, force)`,
|
|
53
|
+
* Implement `DOMTokenList#replace()`, `DOMTokenList#toggle(token, force)`,
|
|
8
54
|
and `DOMTokenList#value`. Fix handling of non-space whitespace. (#111)
|
|
9
55
|
|
|
10
56
|
# domino 2.0.2 (28 Mar 2018)
|
package/README.md
CHANGED
|
@@ -6,21 +6,24 @@ As the name might suggest, domino's goal is to provide a <b>DOM in No</b>de.
|
|
|
6
6
|
|
|
7
7
|
In contrast to the original [dom.js](https://github.com/andreasgal/dom.js) project, domino was not designed to run untrusted code. Hence it doesn't have to hide its internals behind a proxy facade which makes the code not only simpler, but also [more performant](https://github.com/fgnass/dombench).
|
|
8
8
|
|
|
9
|
-
Domino currently doesn't use any harmony features like proxies or WeakMaps and therefore also runs in older Node versions.
|
|
9
|
+
Domino currently doesn't use any harmony/ES6 features like proxies or WeakMaps and therefore also runs in older Node versions.
|
|
10
10
|
|
|
11
11
|
## Speed over Compliance
|
|
12
12
|
|
|
13
13
|
Domino is intended for _building_ pages rather than scraping them. Hence Domino doesn't execute scripts nor does it download external resources.
|
|
14
14
|
|
|
15
|
-
Also Domino doesn't implement
|
|
15
|
+
Also Domino doesn't generally implement properties which have been deprecated in HTML5.
|
|
16
16
|
|
|
17
|
-
Domino sticks to
|
|
17
|
+
Domino sticks to [DOM level 4](http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-attr), which means that Attributes do not inherit the Node interface.
|
|
18
18
|
|
|
19
19
|
<b>Note that</b> because domino does not use proxies,
|
|
20
20
|
`Element.attributes` is not a true JavaScript array; it is an object
|
|
21
21
|
with a `length` property and an `item(n)` accessor method. See
|
|
22
22
|
[github issue #27](https://github.com/fgnass/domino/issues/27) for
|
|
23
|
-
further discussion.
|
|
23
|
+
further discussion. It does however implement direct indexed accessors
|
|
24
|
+
(`element.attributes[i]`) and is live.
|
|
25
|
+
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
## CSS Selector Support
|
|
26
29
|
|
package/lib/CharacterData.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* jshint bitwise: false */
|
|
1
2
|
"use strict";
|
|
2
3
|
module.exports = CharacterData;
|
|
3
4
|
|
|
@@ -28,8 +29,13 @@ CharacterData.prototype = Object.create(Leaf.prototype, {
|
|
|
28
29
|
// units from the offsetth UTF-16 code unit to the
|
|
29
30
|
// offset+countth UTF-16 code unit in data.
|
|
30
31
|
substringData: { value: function substringData(offset, count) {
|
|
31
|
-
if (
|
|
32
|
+
if (arguments.length < 2) { throw new TypeError("Not enough arguments"); }
|
|
33
|
+
// Convert arguments to WebIDL "unsigned long"
|
|
34
|
+
offset = offset >>> 0;
|
|
35
|
+
count = count >>> 0;
|
|
36
|
+
if (offset > this.data.length || offset < 0 || count < 0) {
|
|
32
37
|
utils.IndexSizeError();
|
|
38
|
+
}
|
|
33
39
|
return this.data.substring(offset, offset+count);
|
|
34
40
|
}},
|
|
35
41
|
|
|
@@ -37,7 +43,8 @@ CharacterData.prototype = Object.create(Leaf.prototype, {
|
|
|
37
43
|
// The appendData(data) method must append data to the context
|
|
38
44
|
// object's data.
|
|
39
45
|
appendData: { value: function appendData(data) {
|
|
40
|
-
|
|
46
|
+
if (arguments.length < 1) { throw new TypeError("Not enough arguments"); }
|
|
47
|
+
this.data += String(data);
|
|
41
48
|
}},
|
|
42
49
|
|
|
43
50
|
// void insertData(unsigned long offset, DOMString data);
|
|
@@ -51,11 +58,7 @@ CharacterData.prototype = Object.create(Leaf.prototype, {
|
|
|
51
58
|
// offset UTF-16 code units.
|
|
52
59
|
//
|
|
53
60
|
insertData: { value: function insertData(offset, data) {
|
|
54
|
-
|
|
55
|
-
if (offset > curtext.length || offset < 0) utils.IndexSizeError();
|
|
56
|
-
var prefix = curtext.substring(0, offset),
|
|
57
|
-
suffix = curtext.substring(offset);
|
|
58
|
-
this.data = prefix + data + suffix;
|
|
61
|
+
return this.replaceData(offset, 0, data);
|
|
59
62
|
}},
|
|
60
63
|
|
|
61
64
|
|
|
@@ -72,17 +75,7 @@ CharacterData.prototype = Object.create(Leaf.prototype, {
|
|
|
72
75
|
// Starting from offset UTF-16 code units remove count
|
|
73
76
|
// UTF-16 code units from the context object's data.
|
|
74
77
|
deleteData: { value: function deleteData(offset, count) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (offset > len || offset < 0) utils.IndexSizeError();
|
|
78
|
-
|
|
79
|
-
if (offset+count > len)
|
|
80
|
-
count = len - offset;
|
|
81
|
-
|
|
82
|
-
var prefix = curtext.substring(0, offset),
|
|
83
|
-
suffix = curtext.substring(offset+count);
|
|
84
|
-
|
|
85
|
-
this.data = prefix + suffix;
|
|
78
|
+
return this.replaceData(offset, count, '');
|
|
86
79
|
}},
|
|
87
80
|
|
|
88
81
|
|
|
@@ -96,6 +89,10 @@ CharacterData.prototype = Object.create(Leaf.prototype, {
|
|
|
96
89
|
// exceptions these methods might have thrown.
|
|
97
90
|
replaceData: { value: function replaceData(offset, count, data) {
|
|
98
91
|
var curtext = this.data, len = curtext.length;
|
|
92
|
+
// Convert arguments to correct WebIDL type
|
|
93
|
+
offset = offset >>> 0;
|
|
94
|
+
count = count >>> 0;
|
|
95
|
+
data = String(data);
|
|
99
96
|
|
|
100
97
|
if (offset > len || offset < 0) utils.IndexSizeError();
|
|
101
98
|
|
package/lib/ChildNode.js
CHANGED
|
@@ -62,7 +62,12 @@ var ChildNode = {
|
|
|
62
62
|
if (this.parentNode === null) return;
|
|
63
63
|
|
|
64
64
|
// Send mutation events if necessary
|
|
65
|
-
if (this.
|
|
65
|
+
if (this.doc) {
|
|
66
|
+
this.doc._preremoveNodeIterators(this);
|
|
67
|
+
if (this.rooted) {
|
|
68
|
+
this.doc.mutateRemove(this);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
66
71
|
|
|
67
72
|
// Remove this node from its parents array of children
|
|
68
73
|
// and update the structure id for all ancestors
|
package/lib/Comment.js
CHANGED
|
@@ -14,6 +14,7 @@ function Comment(doc, data) {
|
|
|
14
14
|
var nodeValue = {
|
|
15
15
|
get: function() { return this._data; },
|
|
16
16
|
set: function(v) {
|
|
17
|
+
if (v === null || v === undefined) { v = ''; } else { v = String(v); }
|
|
17
18
|
this._data = v;
|
|
18
19
|
if (this.rooted)
|
|
19
20
|
this.ownerDocument.mutateValue(this);
|
|
@@ -24,7 +25,12 @@ Comment.prototype = Object.create(CharacterData.prototype, {
|
|
|
24
25
|
nodeName: { value: '#comment' },
|
|
25
26
|
nodeValue: nodeValue,
|
|
26
27
|
textContent: nodeValue,
|
|
27
|
-
data:
|
|
28
|
+
data: {
|
|
29
|
+
get: nodeValue.get,
|
|
30
|
+
set: function(v) {
|
|
31
|
+
nodeValue.set.call(this, v===null ? '' : String(v));
|
|
32
|
+
},
|
|
33
|
+
},
|
|
28
34
|
|
|
29
35
|
// Utility methods
|
|
30
36
|
clone: { value: function clone() {
|
package/lib/ContainerNode.js
CHANGED
|
@@ -59,8 +59,13 @@ ContainerNode.prototype = Object.create(Node.prototype, {
|
|
|
59
59
|
// Remove all of this node's children. This is a minor
|
|
60
60
|
// optimization that only calls modify() once.
|
|
61
61
|
removeChildren: { value: function removeChildren() {
|
|
62
|
-
var root = this.rooted ? this.ownerDocument : null
|
|
63
|
-
|
|
62
|
+
var root = this.rooted ? this.ownerDocument : null,
|
|
63
|
+
next = this.firstChild,
|
|
64
|
+
kid;
|
|
65
|
+
while (next !== null) {
|
|
66
|
+
kid = next;
|
|
67
|
+
next = kid.nextSibling;
|
|
68
|
+
|
|
64
69
|
if (root) root.mutateRemove(kid);
|
|
65
70
|
kid.parentNode = null;
|
|
66
71
|
}
|
package/lib/DOMException.js
CHANGED
|
@@ -35,7 +35,7 @@ var names = [
|
|
|
35
35
|
'NO_MODIFICATION_ALLOWED_ERR',
|
|
36
36
|
'NOT_FOUND_ERR',
|
|
37
37
|
'NOT_SUPPORTED_ERR',
|
|
38
|
-
|
|
38
|
+
'INUSE_ATTRIBUTE_ERR', // historical
|
|
39
39
|
'INVALID_STATE_ERR',
|
|
40
40
|
'SYNTAX_ERR',
|
|
41
41
|
'INVALID_MODIFICATION_ERR',
|
|
@@ -70,7 +70,7 @@ var messages = [
|
|
|
70
70
|
'NO_MODIFICATION_ALLOWED_ERR (7): the object can not be modified',
|
|
71
71
|
'NOT_FOUND_ERR (8): the object can not be found here',
|
|
72
72
|
'NOT_SUPPORTED_ERR (9): this operation is not supported',
|
|
73
|
-
|
|
73
|
+
'INUSE_ATTRIBUTE_ERR (10): setAttributeNode called on owned Attribute',
|
|
74
74
|
'INVALID_STATE_ERR (11): the object is in an invalid state',
|
|
75
75
|
'SYNTAX_ERR (12): the string did not match the expected pattern',
|
|
76
76
|
'INVALID_MODIFICATION_ERR (13): the object can not be modified in this way',
|
package/lib/DOMImplementation.js
CHANGED
|
@@ -8,8 +8,9 @@ var utils = require('./utils');
|
|
|
8
8
|
var xml = require('./xmlnames');
|
|
9
9
|
|
|
10
10
|
// Each document must have its own instance of the domimplementation object
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
function DOMImplementation(contextObject) {
|
|
12
|
+
this.contextObject = contextObject;
|
|
13
|
+
}
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
// Feature/version pairs that DOMImplementation.hasFeature() returns
|
|
@@ -28,10 +29,9 @@ DOMImplementation.prototype = {
|
|
|
28
29
|
},
|
|
29
30
|
|
|
30
31
|
createDocumentType: function createDocumentType(qualifiedName, publicId, systemId) {
|
|
31
|
-
if (!xml.
|
|
32
|
-
if (!xml.isValidQName(qualifiedName)) utils.NamespaceError();
|
|
32
|
+
if (!xml.isValidQName(qualifiedName)) utils.InvalidCharacterError();
|
|
33
33
|
|
|
34
|
-
return new DocumentType(qualifiedName, publicId, systemId);
|
|
34
|
+
return new DocumentType(this.contextObject, qualifiedName, publicId, systemId);
|
|
35
35
|
},
|
|
36
36
|
|
|
37
37
|
createDocument: function createDocument(namespace, qualifiedName, doctype) {
|
|
@@ -50,25 +50,33 @@ DOMImplementation.prototype = {
|
|
|
50
50
|
e = null;
|
|
51
51
|
|
|
52
52
|
if (doctype) {
|
|
53
|
-
if (doctype.ownerDocument) utils.WrongDocumentError();
|
|
54
53
|
d.appendChild(doctype);
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
if (e) d.appendChild(e);
|
|
57
|
+
if (namespace === utils.NAMESPACE.HTML) {
|
|
58
|
+
d._contentType = 'application/xhtml+xml';
|
|
59
|
+
} else if (namespace === utils.NAMESPACE.SVG) {
|
|
60
|
+
d._contentType = 'image/svg+xml';
|
|
61
|
+
} else {
|
|
62
|
+
d._contentType = 'application/xml';
|
|
63
|
+
}
|
|
58
64
|
|
|
59
65
|
return d;
|
|
60
66
|
},
|
|
61
67
|
|
|
62
68
|
createHTMLDocument: function createHTMLDocument(titleText) {
|
|
63
69
|
var d = new Document(true, null);
|
|
64
|
-
d.appendChild(new DocumentType('html'));
|
|
70
|
+
d.appendChild(new DocumentType(d, 'html'));
|
|
65
71
|
var html = d.createElement('html');
|
|
66
72
|
d.appendChild(html);
|
|
67
73
|
var head = d.createElement('head');
|
|
68
74
|
html.appendChild(head);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
if (titleText !== undefined) {
|
|
76
|
+
var title = d.createElement('title');
|
|
77
|
+
head.appendChild(title);
|
|
78
|
+
title.appendChild(d.createTextNode(titleText));
|
|
79
|
+
}
|
|
72
80
|
html.appendChild(d.createElement('body'));
|
|
73
81
|
d.modclock = 1; // Start tracking modifications
|
|
74
82
|
return d;
|
package/lib/DOMTokenList.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// DOMTokenList implementation based on https://github.com/Raynos/DOM-shim
|
|
3
|
+
// XXX: should cache the getList(this) value more aggressively!
|
|
3
4
|
var utils = require('./utils');
|
|
4
5
|
|
|
5
6
|
module.exports = DOMTokenList;
|
|
@@ -7,15 +8,18 @@ module.exports = DOMTokenList;
|
|
|
7
8
|
function DOMTokenList(getter, setter) {
|
|
8
9
|
this._getString = getter;
|
|
9
10
|
this._setString = setter;
|
|
10
|
-
|
|
11
|
+
this._length = 0;
|
|
12
|
+
this._update();
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
Object.defineProperties(DOMTokenList.prototype, {
|
|
16
|
+
length: { get: function() { return this._length; } },
|
|
14
17
|
item: { value: function(index) {
|
|
15
|
-
|
|
18
|
+
var list = getList(this);
|
|
19
|
+
if (index < 0 || index >= list.length) {
|
|
16
20
|
return null;
|
|
17
21
|
}
|
|
18
|
-
return
|
|
22
|
+
return list[index];
|
|
19
23
|
}},
|
|
20
24
|
|
|
21
25
|
contains: { value: function(token) {
|
|
@@ -25,29 +29,34 @@ Object.defineProperties(DOMTokenList.prototype, {
|
|
|
25
29
|
}},
|
|
26
30
|
|
|
27
31
|
add: { value: function() {
|
|
32
|
+
var list = getList(this);
|
|
28
33
|
for (var i = 0, len = arguments.length; i < len; i++) {
|
|
29
34
|
var token = handleErrors(arguments[i]);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return;
|
|
35
|
+
if (list.indexOf(token) < 0) {
|
|
36
|
+
list.push(token);
|
|
33
37
|
}
|
|
34
|
-
list.push(token);
|
|
35
|
-
this._setString(list.join(" ").trim());
|
|
36
|
-
fixIndex(this, list);
|
|
37
38
|
}
|
|
39
|
+
// Note: as per spec, if handleErrors() throws any errors, we never
|
|
40
|
+
// make it here and none of the changes take effect.
|
|
41
|
+
// Also per spec: we run the "update steps" even if no change was
|
|
42
|
+
// made (ie, if the token already existed)
|
|
43
|
+
this._update(list);
|
|
38
44
|
}},
|
|
39
45
|
|
|
40
46
|
remove: { value: function() {
|
|
47
|
+
var list = getList(this);
|
|
41
48
|
for (var i = 0, len = arguments.length; i < len; i++) {
|
|
42
49
|
var token = handleErrors(arguments[i]);
|
|
43
|
-
var list = getList(this);
|
|
44
50
|
var index = list.indexOf(token);
|
|
45
51
|
if (index > -1) {
|
|
46
52
|
list.splice(index, 1);
|
|
47
|
-
this._setString(list.join(" ").trim());
|
|
48
53
|
}
|
|
49
|
-
fixIndex(this, list);
|
|
50
54
|
}
|
|
55
|
+
// Note: as per spec, if handleErrors() throws any errors, we never
|
|
56
|
+
// make it here and none of the changes take effect.
|
|
57
|
+
// Also per spec: we run the "update steps" even if no change was
|
|
58
|
+
// made (ie, if the token wasn't previously present)
|
|
59
|
+
this._update(list);
|
|
51
60
|
}},
|
|
52
61
|
|
|
53
62
|
toggle: { value: function toggle(token, force) {
|
|
@@ -68,16 +77,33 @@ Object.defineProperties(DOMTokenList.prototype, {
|
|
|
68
77
|
}},
|
|
69
78
|
|
|
70
79
|
replace: { value: function replace(token, newToken) {
|
|
80
|
+
// weird corner case of spec: if `token` contains whitespace, but
|
|
81
|
+
// `newToken` is the empty string, we must throw SyntaxError not
|
|
82
|
+
// InvalidCharacterError (sigh)
|
|
83
|
+
if (String(newToken)==='') { utils.SyntaxError(); }
|
|
71
84
|
token = handleErrors(token);
|
|
72
85
|
newToken = handleErrors(newToken);
|
|
73
86
|
var list = getList(this);
|
|
74
87
|
var idx = list.indexOf(token);
|
|
75
88
|
if (idx < 0) {
|
|
89
|
+
// Note that, per spec, we do not run the update steps on this path.
|
|
76
90
|
return false;
|
|
77
91
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
var idx2 = list.indexOf(newToken);
|
|
93
|
+
if (idx2 < 0) {
|
|
94
|
+
list[idx] = newToken;
|
|
95
|
+
} else {
|
|
96
|
+
// "replace the first instance of either `token` or `newToken` with
|
|
97
|
+
// `newToken` and remove all other instances"
|
|
98
|
+
if (idx < idx2) {
|
|
99
|
+
list[idx] = newToken;
|
|
100
|
+
list.splice(idx2, 1);
|
|
101
|
+
} else {
|
|
102
|
+
// idx2 is already `newToken`
|
|
103
|
+
list.splice(idx, 1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
this._update(list);
|
|
81
107
|
return true;
|
|
82
108
|
}},
|
|
83
109
|
|
|
@@ -91,16 +117,32 @@ Object.defineProperties(DOMTokenList.prototype, {
|
|
|
91
117
|
},
|
|
92
118
|
set: function(v) {
|
|
93
119
|
this._setString(v);
|
|
94
|
-
|
|
120
|
+
this._update();
|
|
95
121
|
}
|
|
96
122
|
},
|
|
123
|
+
|
|
124
|
+
// Called when the setter is called from outside this interface.
|
|
125
|
+
_update: { value: function(list) {
|
|
126
|
+
if (list) {
|
|
127
|
+
fixIndex(this, list);
|
|
128
|
+
this._setString(list.join(" ").trim());
|
|
129
|
+
} else {
|
|
130
|
+
fixIndex(this, getList(this));
|
|
131
|
+
}
|
|
132
|
+
} },
|
|
97
133
|
});
|
|
98
134
|
|
|
99
135
|
function fixIndex(clist, list) {
|
|
100
|
-
|
|
101
|
-
|
|
136
|
+
var oldLength = clist._length;
|
|
137
|
+
var i;
|
|
138
|
+
clist._length = list.length;
|
|
139
|
+
for (i = 0; i < list.length; i++) {
|
|
102
140
|
clist[i] = list[i];
|
|
103
141
|
}
|
|
142
|
+
// Clear/free old entries.
|
|
143
|
+
for (; i < oldLength; i++) {
|
|
144
|
+
clist[i] = undefined;
|
|
145
|
+
}
|
|
104
146
|
}
|
|
105
147
|
|
|
106
148
|
function handleErrors(token) {
|
|
@@ -119,6 +161,12 @@ function getList(clist) {
|
|
|
119
161
|
if (str === "") {
|
|
120
162
|
return [];
|
|
121
163
|
} else {
|
|
122
|
-
|
|
164
|
+
var seen = Object.create(null);
|
|
165
|
+
return str.split(/[ \t\r\n\f]+/g).filter(function(n) {
|
|
166
|
+
var key = '$' + n;
|
|
167
|
+
if (seen[key]) { return false; }
|
|
168
|
+
seen[key] = true;
|
|
169
|
+
return true;
|
|
170
|
+
});
|
|
123
171
|
}
|
|
124
172
|
}
|