From 15b3ffd685816efa6a5a1e853f845b96651d0f5b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 20 Sep 2026 05:46:06 +0000 Subject: [PATCH] Fix innerHTML/innerText to coerce non-string values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Browsers stringify assignments (null → ''). Setting a number no longer throws; innerText now has a matching setter that replaces text content. Co-authored-by: David --- cjs/interface/element.js | 4 ++++ cjs/mixin/inner-html.js | 1 + esm/interface/element.js | 4 ++++ esm/mixin/inner-html.js | 1 + test/html/element.js | 15 +++++++++++++++ types/esm/html/style-element.d.ts | 2 -- types/esm/interface/element.d.ts | 1 + types/esm/interface/image.d.ts | 2 +- worker.js | 5 +++++ 9 files changed, 32 insertions(+), 3 deletions(-) diff --git a/cjs/interface/element.js b/cjs/interface/element.js index 5d513d6a..7c454bce 100644 --- a/cjs/interface/element.js +++ b/cjs/interface/element.js @@ -176,6 +176,10 @@ class Element extends ParentNode { return text.join(''); } + set innerText(text) { + this.textContent = text === null ? '' : String(text); + } + /** * @returns {String} */ diff --git a/cjs/mixin/inner-html.js b/cjs/mixin/inner-html.js index ec106d80..7f430070 100644 --- a/cjs/mixin/inner-html.js +++ b/cjs/mixin/inner-html.js @@ -17,6 +17,7 @@ exports.getInnerHtml = getInnerHtml; * @param {String} html */ const setInnerHtml = (node, html) => { + html = html === null ? '' : String(html); const {ownerDocument} = node; const {constructor} = ownerDocument; const document = new constructor; diff --git a/esm/interface/element.js b/esm/interface/element.js index c98f5f7d..3a2f1a5a 100644 --- a/esm/interface/element.js +++ b/esm/interface/element.js @@ -178,6 +178,10 @@ export class Element extends ParentNode { return text.join(''); } + set innerText(text) { + this.textContent = text === null ? '' : String(text); + } + /** * @returns {String} */ diff --git a/esm/mixin/inner-html.js b/esm/mixin/inner-html.js index ac37e8f7..76e84960 100644 --- a/esm/mixin/inner-html.js +++ b/esm/mixin/inner-html.js @@ -15,6 +15,7 @@ export const getInnerHtml = node => node.childNodes.join(''); * @param {String} html */ export const setInnerHtml = (node, html) => { + html = html === null ? '' : String(html); const {ownerDocument} = node; const {constructor} = ownerDocument; const document = new constructor; diff --git a/test/html/element.js b/test/html/element.js index 3c3fedd8..4ed0ff29 100644 --- a/test/html/element.js +++ b/test/html/element.js @@ -159,3 +159,18 @@ assert(node.innerHTML, ''); node.innerHTML = '
The quick brown fox
Jumped over
The lazy\ndog
'; assert(node.innerText, 'The quick brown fox\nJumped over\nThe lazy dog', 'innerText newlines'); assert(node.textContent, 'The quick brown foxJumped overThe lazy\ndog', 'textContent no newlines'); + +// #308 — non-string innerHTML / innerText must coerce like browsers +const {document: coerceDoc} = parseHTML(''); +coerceDoc.body.innerHTML = 1; +assert(coerceDoc.body.innerHTML, '1', 'innerHTML number coerce'); +coerceDoc.body.innerHTML = null; +assert(coerceDoc.body.innerHTML, '', 'innerHTML null coerce'); +coerceDoc.body.innerHTML = undefined; +assert(coerceDoc.body.innerHTML, 'undefined', 'innerHTML undefined coerce'); +coerceDoc.body.innerText = 1; +assert(coerceDoc.body.innerText, '1', 'innerText number coerce'); +assert(coerceDoc.body.textContent, '1', 'innerText number as text content'); +coerceDoc.body.innerText = null; +assert(coerceDoc.body.innerText, '', 'innerText null coerce'); +assert(coerceDoc.body.innerHTML, '', 'innerText null clears children'); diff --git a/types/esm/html/style-element.d.ts b/types/esm/html/style-element.d.ts index 74e49397..64a4470a 100644 --- a/types/esm/html/style-element.d.ts +++ b/types/esm/html/style-element.d.ts @@ -3,8 +3,6 @@ */ export class HTMLStyleElement extends TextElement implements globalThis.HTMLStyleElement { get sheet(): any; - set innerText(value: string); - get innerText(): string; [SHEET]: any; } import { TextElement } from './text-element.js'; diff --git a/types/esm/interface/element.d.ts b/types/esm/interface/element.d.ts index eae53136..adf216dc 100644 --- a/types/esm/interface/element.d.ts +++ b/types/esm/interface/element.d.ts @@ -32,6 +32,7 @@ export class Element extends ParentNode implements globalThis.Element { get tabIndex(): number; set slot(value: any); get slot(): any; + set innerText(text: string); get innerText(): string; set textContent(text: string); /** diff --git a/types/esm/interface/image.d.ts b/types/esm/interface/image.d.ts index e5e7f2d1..b6df3089 100644 --- a/types/esm/interface/image.d.ts +++ b/types/esm/interface/image.d.ts @@ -121,7 +121,7 @@ export function ImageClass(ownerDocument: any): { readonly style: any; tabIndex: number; slot: any; - readonly innerText: string; + innerText: string; textContent: string; innerHTML: string; outerHTML: string; diff --git a/worker.js b/worker.js index 2671c7f5..1b08b1fe 100644 --- a/worker.js +++ b/worker.js @@ -7134,6 +7134,7 @@ const getInnerHtml = node => node.childNodes.join(''); * @param {String} html */ const setInnerHtml = (node, html) => { + html = html === null ? '' : String(html); const {ownerDocument} = node; const {constructor} = ownerDocument; const document = new constructor; @@ -7673,6 +7674,10 @@ let Element$1 = class Element extends ParentNode { return text.join(''); } + set innerText(text) { + this.textContent = text === null ? '' : $String(text); + } + /** * @returns {String} */