In @xmldom/xmldom, CharacterData stores text in two plain properties: data and nodeValue. The built-in mutation methods (appendData, replaceData, splitText) keep them in sync, but direct property assignment does not:
node.nodeValue = 'text' leaves data and length stale — XMLSerializer reads node.data, so the change is silently lost on serialization
node.data = 'text' leaves nodeValue and length stale — textContent reads nodeValue, so the change is not reflected
Reproduction
const { DOMParser, XMLSerializer } = require('@xmldom/xmldom');
const doc = new DOMParser().parseFromString('<root>Hello</root>', 'text/xml');
const text = doc.documentElement.firstChild;
text.nodeValue = 'Changed';
console.log(text.data); // 'Hello' — expected: 'Changed'
console.log(text.nodeValue); // 'Changed'
const xml = new XMLSerializer().serializeToString(doc);
console.log(xml); // '<root>Hello</root>' — expected: '<root>Changed</root>'
Expected behavior
Per the WHATWG DOM Living Standard §4.10, for CharacterData nodes the nodeValue getter and setter must be equivalent to data. Assigning to either property should update both, along with length.
What works correctly today
appendData, replaceData, insertData, deleteData, and splitText all keep data, nodeValue, and length in sync. The issue is only with direct property assignment.
Proposed fix
Add Object.defineProperty accessors for data and nodeValue on CharacterData.prototype, using data as the canonical store and nodeValue as an alias. This follows the same pattern already used in the codebase for textContent and LiveNodeList.length.
Related to #42 — karfau noted that adding aliases/getters for properties would improve spec compatibility. This addresses that for CharacterData specifically.
Discovered while round-tripping XML documents.
In
@xmldom/xmldom,CharacterDatastores text in two plain properties:dataandnodeValue. The built-in mutation methods (appendData,replaceData,splitText) keep them in sync, but direct property assignment does not:node.nodeValue = 'text'leavesdataandlengthstale —XMLSerializerreadsnode.data, so the change is silently lost on serializationnode.data = 'text'leavesnodeValueandlengthstale —textContentreadsnodeValue, so the change is not reflectedReproduction
Expected behavior
Per the WHATWG DOM Living Standard §4.10, for
CharacterDatanodes thenodeValuegetter and setter must be equivalent todata. Assigning to either property should update both, along withlength.What works correctly today
appendData,replaceData,insertData,deleteData, andsplitTextall keepdata,nodeValue, andlengthin sync. The issue is only with direct property assignment.Proposed fix
Add
Object.definePropertyaccessors fordataandnodeValueonCharacterData.prototype, usingdataas the canonical store andnodeValueas an alias. This follows the same pattern already used in the codebase fortextContentandLiveNodeList.length.Related to #42 — karfau noted that adding aliases/getters for properties would improve spec compatibility. This addresses that for
CharacterDataspecifically.Discovered while round-tripping XML documents.