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 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 any properties which have been deprecated in HTML5.
15
+ Also Domino doesn't generally implement properties which have been deprecated in HTML5.
16
16
 
17
- Domino sticks to the [DOM level 4](http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-attr) working draft, which means that Attributes do not inherit the Node interface. Also [Element.attributes](http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-element-attributes) returns a read-only array instead of a NamedNodeMap.
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
 
@@ -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 (offset > this.data.length || offset < 0 || count < 0)
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
- this.data = this.data + data;
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
- var curtext = this.data;
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
- var curtext = this.data, len = curtext.length;
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.rooted && this.doc) this.doc.mutateRemove(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: nodeValue,
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() {
@@ -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
- for (var kid = this.firstChild; kid !== null; kid = kid.nextSibling) {
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
  }
@@ -35,7 +35,7 @@ var names = [
35
35
  'NO_MODIFICATION_ALLOWED_ERR',
36
36
  'NOT_FOUND_ERR',
37
37
  'NOT_SUPPORTED_ERR',
38
- null, // historical
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
- null,
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',
@@ -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
- // Even though these objects have no state
12
- function DOMImplementation() {}
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.isValidName(qualifiedName)) utils.InvalidCharacterError();
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
- var title = d.createElement('title');
70
- head.appendChild(title);
71
- title.appendChild(d.createTextNode(titleText));
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;
@@ -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
- fixIndex(this, getList(this));
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
- if (index >= this.length) {
18
+ var list = getList(this);
19
+ if (index < 0 || index >= list.length) {
16
20
  return null;
17
21
  }
18
- return getList(this)[index];
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
- var list = getList(this);
31
- if (list.indexOf(token) > -1) {
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
- list[idx] = newToken;
79
- this._setString(list.join(" ").trim());
80
- fixIndex(this, list);
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
- fixIndex(this, getList(this));
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
- clist.length = list.length;
101
- for (var i = 0; i < list.length; i++) {
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
- return str.split(/[ \t\r\n\f]+/g);
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
  }