Skip to content

Commit

Permalink
Implement Node#toggleAttribute; make indexed properties of Node#attri…
Browse files Browse the repository at this point in the history
…butes work
  • Loading branch information
cscott committed Aug 2, 2018
1 parent c11da0c commit 2424580
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 29 deletions.
77 changes: 59 additions & 18 deletions lib/Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,26 @@ Element.prototype = Object.create(ContainerNode.prototype, {
return this._numattrs > 0;
}},

toggleAttribute: { value: function toggleAttribute(qname, force) {
qname = String(qname);
if (!xml.isValidName(qname)) utils.InvalidCharacterError();
if (this.isHTML) qname = utils.toASCIILowerCase(qname);
var a = this._attrsByQName[qname];
if (a === undefined) {
if (force === undefined || force === true) {
this._setAttribute(qname, '');
return true;
}
return false;
} else {
if (force === undefined || force === false) {
this.removeAttribute(qname);
return false;
}
return true;
}
}},

// Set the attribute without error checking. The parser uses this.
_setAttribute: { value: function _setAttribute(qname, value) {
// XXX: the spec says that this next search should be done
Expand All @@ -485,6 +505,7 @@ Element.prototype = Object.create(ContainerNode.prototype, {

// Check for errors, and then set the attribute
setAttribute: { value: function setAttribute(qname, value) {
qname = String(qname);
if (!xml.isValidName(qname)) utils.InvalidCharacterError();
if (this.isHTML) qname = utils.toASCIILowerCase(qname);
this._setAttribute(qname, String(value));
Expand All @@ -494,7 +515,7 @@ Element.prototype = Object.create(ContainerNode.prototype, {
// The version with no error checking used by the parser
_setAttributeNS: { value: function _setAttributeNS(ns, qname, value) {
var pos = qname.indexOf(':'), prefix, lname;
if (pos === -1) {
if (pos < 0) {
prefix = null;
lname = qname;
}
Expand All @@ -503,33 +524,35 @@ Element.prototype = Object.create(ContainerNode.prototype, {
lname = qname.substring(pos+1);
}

var key = (ns === null || ns === undefined ? '' : ns) + '|' + lname;
if (ns === '') ns = null;
if (ns === '' || ns === undefined) ns = null;
var key = (ns === null ? '' : ns) + '|' + lname;

var attr = this._attrsByLName[key];
var isnew;
if (!attr) {
attr = new Attr(this, lname, prefix, ns);
isnew = true;
this._attrsByLName[key] = attr;
if (this._attributes) {
this._attributes[this._attrKeys.length] = attr;
}
this._attrKeys.push(key);

// We also have to make the attr searchable by qname.
// But we have to be careful because there may already
// be an attr with this qname.
this._addQName(attr);
}
else {
else if (false /* changed in DOM 4 */) {
// Calling setAttributeNS() can change the prefix of an
// existing attribute!
// existing attribute in DOM 2/3.
if (attr.prefix !== prefix) {
// Unbind the old qname
this._removeQName(attr);
// Update the prefix
attr.prefix = prefix;
// Bind the new qname
this._addQName(attr);

}

}
Expand All @@ -539,13 +562,13 @@ Element.prototype = Object.create(ContainerNode.prototype, {

// Do error checking then call _setAttributeNS
setAttributeNS: { value: function setAttributeNS(ns, qname, value) {
// Convert parameter types according to WebIDL
ns = (ns === null || ns === undefined || ns === '') ? null : String(ns);
qname = String(qname);
if (!xml.isValidName(qname)) utils.InvalidCharacterError();
if (!xml.isValidQName(qname)) utils.NamespaceError();
if (!xml.isValidQName(qname)) utils.InvalidCharacterError();

var pos = qname.indexOf(':');
var prefix = (pos === -1) ? null : qname.substring(0, pos);
if (ns === '' || ns === undefined) ns = null;
var prefix = (pos < 0) ? null : qname.substring(0, pos);

if ((prefix !== null && ns === null) ||
(prefix === 'xml' && ns !== NAMESPACE.XML) ||
Expand Down Expand Up @@ -589,12 +612,13 @@ Element.prototype = Object.create(ContainerNode.prototype, {
this._attrsByLName[key] = undefined;

var i = this._attrKeys.indexOf(key);
this._attrKeys.splice(i, 1);
attr.ownerElement = null;

if (this._attributes)
if (this._attributes) {
Array.prototype.splice.call(this._attributes, i, 1);
this._attributes[qname] = undefined;
}
this._attrKeys.splice(i, 1);

attr.ownerElement = null;
// Onchange handler for the attribute
if (attr.onchange)
attr.onchange(this, attr.localName, attr.value, null);
Expand All @@ -613,6 +637,9 @@ Element.prototype = Object.create(ContainerNode.prototype, {
this._attrsByLName[key] = undefined;

var i = this._attrKeys.indexOf(key);
if (this._attributes) {
Array.prototype.splice.call(this._attributes, i, 1);
}
this._attrKeys.splice(i, 1);

// Now find the same Attr object in the qname mapping and remove it
Expand Down Expand Up @@ -660,6 +687,9 @@ Element.prototype = Object.create(ContainerNode.prototype, {
var key = '|' + qname;
this._attrsByQName[qname] = attr;
this._attrsByLName[key] = attr;
if (this._attributes) {
this._attributes[this._attrKeys.length] = attr;
}
this._attrKeys.push(key);
return attr;
}},
Expand Down Expand Up @@ -694,14 +724,22 @@ Element.prototype = Object.create(ContainerNode.prototype, {
utils.assert(idx !== -1); // It must be here somewhere
if (target.length === 2) {
this._attrsByQName[qname] = target[1-idx];
}
else {
if (this._attributes) {
this._attributes[qname] = this._attrsByQName[qname];
}
} else {
target.splice(idx, 1);
if (this._attributes && this._attributes[qname] === attr) {
this._attributes[qname] = target[0];
}
}
}
else {
utils.assert(target === attr); // If only one, it must match
this._attrsByQName[qname] = undefined;
if (this._attributes) {
this._attributes[qname] = undefined;
}
}
}},

Expand Down Expand Up @@ -797,7 +835,7 @@ function Attr(elt, lname, prefix, namespace) {
this.namespaceURI = (namespace===null || namespace==='') ? null : ('' + namespace);
}

// Technically, Attr is supposed to extend Node.
// In DOM 3 Attr was supposed to extend Node; in DOM 4 that was abandoned.
Attr.prototype = Object.create(Object.prototype, {
name: { get: function() {
return this.prefix ? this.prefix + ':' + this.localName : this.localName;
Expand Down Expand Up @@ -864,11 +902,14 @@ function AttributesArray(elt) {
for (var name in elt._attrsByQName) {
this[name] = elt._attrsByQName[name];
}
for (var i = 0; i < elt._attrKeys.length; i++) {
this[i] = elt._attrsByLName[elt._attrKeys[i]];
}
}
AttributesArray.prototype = Object.create(NamedNodeMap.prototype, {
length: { get: function() {
return this.element._attrKeys.length;
} },
}, set: function() { /* ignore */ } },
item: { value: function(n) {
/* jshint bitwise: false */
n = n >>> 0;
Expand Down
11 changes: 0 additions & 11 deletions test/web-platform-blacklist.json
Original file line number Diff line number Diff line change
Expand Up @@ -4797,18 +4797,7 @@
"Document.append() with two elements as the argument, on a Document having no child."
],
"attributes.html": [
"When qualifiedName does not match the Name production, an INVALID_CHARACTER_ERR exception is to be thrown. (toggleAttribute)",
"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)",
"toggleAttribute should lowercase its name argument (upper case attribute)",
"toggleAttribute should lowercase its name argument (mixed case attribute)",
"toggleAttribute should not throw even when qualifiedName starts with 'xmlns'",
"Basic functionality should be intact. (toggleAttribute)",
"toggleAttribute should not change the order of previously set attributes.",
"toggleAttribute should set the first attribute with the given name",
"toggleAttribute should set the attribute with the given qualified name",
"Toggling element with inline style should make inline style disappear",
"When qualifiedName does not match the QName production, an INVALID_CHARACTER_ERR exception is to be thrown.",
"Setting the same attribute with another prefix should not change the prefix",
"Basic functionality of setAttributeNode",
"setAttributeNode should distinguish attributes with same local name and different namespaces",
"setAttributeNode doesn't have case-insensitivity even with an HTMLElement",
Expand Down

0 comments on commit 2424580

Please sign in to comment.