Skip to content

CharacterData: direct property assignment to nodeValue or data does not stay in sync #989

Description

@stevenobiajulu

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions