Repository: preactjs/preact (JavaScript)

Issue:
MathML: HTML phrasing children of <mi> are created in MathML namespace
- [x] Check if updating to the latest Preact version resolves the issue

**Describe the bug**
In JSX under <math>, Preact creates `<ins>/<del>` inside `<mi>` in the MathML namespace, so they don’t render. HTML + MathML requires these to be HTML namespace phrasing elements.

**To Reproduce**
Preact REPL: [Preact REPL](https://preactjs.com/repl/?code=aW1wb3J0IHsgcmVuZGVyIH0gZnJvbSAncHJlYWN0JzsKaW1wb3J0IHsgdXNlUmVmLCB1c2VFZmZlY3QgfSBmcm9tICdwcmVhY3QvaG9va3MnOwoKZnVuY3Rpb24gQXBwKCkgewogIGNvbnN0IGRvbVJlZiA9IHVzZVJlZigpOwogIGNvbnN0IGxvZ1JlZiA9IHVzZVJlZigpOwogIAogIHVzZUVmZmVjdCgoKSA9PiB7CiAgICBjb25zdCBNQVRITUxfTlMgPSAnaHR0cDovL3d3dy53My5vcmcvMTk5OC9NYXRoL01hdGhNTCc7CiAgICBjb25zdCBIVE1MX05TID0gJ2h0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwnOwogICAgCiAgICBjb25zdCBtYXRoID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKE1BVEhNTF9OUywgJ21hdGgnKTsKICAgIGNvbnN0IG1yb3cgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoTUFUSE1MX05TLCAnbXJvdycpOwogICAgY29uc3QgbWkgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoTUFUSE1MX05TLCAnbWknKTsKICAgIGNvbnN0IGlucyA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUyhIVE1MX05TLCAnaW5zJyk7CiAgICAKICAgIGlucy5jbGFzc05hbWUgPSAnYWRkZWQnOwogICAgaW5zLnRleHRDb250ZW50ID0gJzEyMyc7CiAgICAKICAgIG1pLmFwcGVuZENoaWxkKGlucyk7CiAgICBtcm93LmFwcGVuZENoaWxkKG1pKTsKICAgIG1hdGguYXBwZW5kQ2hpbGQobXJvdyk7CiAgICAKICAgIGRvbVJlZi5jdXJyZW50LmFwcGVuZENoaWxkKG1hdGgpOwogICAgCiAgICBjb25zdCBucyA9IChlbCkgPT4gZWwgPyBlbC5uYW1lc3BhY2VVUkkgOiAnTi9BJzsKICAgIGNvbnN0IHJvd3MgPSBbCiAgICAgIFsnSlNYIDxpbnM%2BIG5hbWVzcGFjZScsIG5zKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJyNqc3ggaW5zJykpXSwKICAgICAgWydET00gPGlucz4gbmFtZXNwYWNlJywgbnMoZG9jdW1lbnQucXVlcnlTZWxlY3RvcignI2RvbSBpbnMnKSldLAogICAgICBbJ0hUTUwgPGlucz4gbmFtZXNwYWNlJywgbnMoZG9jdW1lbnQucXVlcnlTZWxlY3RvcignI2h0bWwgaW5zJykpXSwKICAgIF07CiAgICBsb2dSZWYuY3VycmVudC50ZXh0Q29udGVudCA9IHJvd3MubWFwKChbaywgdl0pID0%2BIGAke2t9OiAke3Z9YCkuam9pbignXG4nKTsKICB9LCBbXSk7CiAgCiAgcmV0dXJuICgKICAgIDxkaXY%2BCiAgICAgIDxoMz4xKSBKU1ggKGJyb2tlbiAtIGlucyBnZXRzIE1hdGhNTCBuYW1lc3BhY2UpPC9oMz4KICAgICAgPGRpdiBpZD0ianN4Ij4KICAgICAgICA8bWF0aD4KICAgICAgICAgIDxtcm93PgogICAgICAgICAgICA8bWk%2BPGlucz4xMjM8L2lucz48L21pPgogICAgICAgICAgPC9tcm93PgogICAgICAgIDwvbWF0aD4KICAgICAgPC9kaXY%2BCiAgICAgIAogICAgICA8aDM%2BMikgTWFudWFsIERPTSB3aXRoIGNyZWF0ZUVsZW1lbnROUyAod29ya3MpPC9oMz4KICAgICAgPGRpdiBpZD0iZG9tIiByZWY9e2RvbVJlZn0gLz4KICAgICAgCiAgICAgIDxoMz4zKSBSYXcgSFRNTCBzdHJpbmcgKHdvcmtzKTwvaDM%2BCiAgICAgIDxkaXYgCiAgICAgICAgaWQ9Imh0bWwiCiAgICAgICAgZGFuZ2Vyb3VzbHlTZXRJbm5lckhUTUw9e3sKICAgICAgICAgIF9faHRtbDogJzxtYXRoIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk4L01hdGgvTWF0aE1MIj48bXJvdz48bWk%2BPGlucyBjbGFzcz0iYWRkZWQiPjEyMzwvaW5zPjwvbWk%2BPC9tcm93PjwvbWF0aD4nCiAgICAgICAgfX0KICAgICAgLz4KICAgICAgCiAgICAgIDxwcmUgcmVmPXtsb2dSZWZ9IHN0eWxlPXt7IGJhY2tncm91bmQ6ICcjZjdmN2Y3JywgcGFkZGluZzogJzhweCcsIG1hcmdpblRvcDogJzE2cHgnIH19IC8%2BCiAgICAgIAogICAgICA8c3R5bGU%2Be2AKICAgICAgICBtYXRoIHsgZm9udC1zaXplOiAyNHB4OyB9CiAgICAgICAgaW5zIHsgYmFja2dyb3VuZDogcmdiYSgwLCAyNTUsIDAsIC4xMik7IHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lOyB9CiAgICAgICAgaDMgeyBmb250OiAxNHB4LzEuNCBzeXN0ZW0tdWk7IG1hcmdpbjogMTJweCAwIDZweDsgfQogICAgICBgfTwvc3R5bGU%2BCiAgICA8L2Rpdj4KICApOwp9CgpyZW5kZXIoPEFwcCAvPiwgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2FwcCcpKTs%3D)
JSFiddle: https://jsfiddle.net/3e460jvw/

**Expected behavior**
Children like `<ins>/<del>` under `<mi>` are created in HTML namespace and render.

> When the MathML token elements ([mi](https://w3c.github.io/mathml-core/#the-mi-element), [mo](https://w3c.github.io/mathml-core/#operator-fence-separator-or-accent-mo), [mn](https://w3c.github.io/mathml-core/#number-mn), [ms](https://w3c.github.io/mathml-core/#string-literal-ms), and [mtext](https://w3c.github.io/mathml-core/#text-mtext)) are descendants of HTML elements, they may contain [phrasing content](https://html.spec.whatwg.org/multipage/dom.html#phrasing-content-2) elements from the [HTML namespace](https://infra.spec.whatwg.org/#html-namespace).

Spec: https://html.spec.whatwg.org/multipage/embedded-content-other.html#mathml
https://w3c.github.io/mathml-core/#token-elements


Discussion:
Unfortunately I'm not sure we can reasonably accommodate this behavior.

Whereas SVG has `<foreignObject>`, allowing us to quickly determine what namespace a child should be created under, supporting phrasing content under a handful of MathML elements is rather arbitrary and forces us to check if a node name happens to sit in an array of whitelisted elements which is obviously awful for perf & bundle size.



Patch:
diff --git a/src/constants.js b/src/constants.js
index 7a823a3efd..1ad82b208e 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -31,3 +31,5 @@ export const NULL = null;
 export const UNDEFINED = undefined;
 export const EMPTY_OBJ = /** @type {any} */ ({});
 export const EMPTY_ARR = [];
+
+export const MATHML_TOKEN_ELEMENTS = /(mi|mn|mo|ms$|mte|msp)/;
diff --git a/src/diff/index.js b/src/diff/index.js
index 73c4a01ebb..427d3cf314 100644
--- a/src/diff/index.js
+++ b/src/diff/index.js
@@ -12,7 +12,8 @@ import {
 	RESET_MODE,
 	SVG_NAMESPACE,
 	UNDEFINED,
-	XHTML_NAMESPACE
+	XHTML_NAMESPACE,
+	MATHML_TOKEN_ELEMENTS
 } from '../constants';
 import { BaseComponent, getDomSibling } from '../component';
 import { Fragment } from '../create-element';
@@ -608,6 +609,13 @@ function diffElementNodes(
 		} else {
 			if (oldHtml) dom.innerHTML = '';
 
+			if (
+				nodeType == 'foreignObject' ||
+				(namespace == MATH_NAMESPACE && MATHML_TOKEN_ELEMENTS.test(nodeType))
+			) {
+				namespace = XHTML_NAMESPACE;
+			}
+
 			diffChildren(
 				// @ts-expect-error
 				nodeType == 'template' ? dom.content : dom,
@@ -615,7 +623,7 @@ function diffElementNodes(
 				newVNode,
 				oldVNode,
 				globalContext,
-				nodeType == 'foreignObject' ? XHTML_NAMESPACE : namespace,
+				namespace,
 				excessDomChildren,
 				commitQueue,
 				excessDomChildren


Test patch:
diff --git a/test/browser/mathml.test.jsx b/test/browser/mathml.test.jsx
index 79439f4016..0f5f43023e 100644
--- a/test/browser/mathml.test.jsx
+++ b/test/browser/mathml.test.jsx
@@ -2,6 +2,9 @@ import { createElement, Component, render } from 'preact';
 import { setupRerender } from 'preact/test-utils';
 import { setupScratch, teardown } from '../_util/helpers';
 
+const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
+const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
+
 describe('mathml', () => {
 	let scratch;
 
@@ -18,7 +21,7 @@ describe('mathml', () => {
 
 		let namespace = scratch.querySelector('math').namespaceURI;
 
-		expect(namespace).to.equal('http://www.w3.org/1998/Math/MathML');
+		expect(namespace).to.equal(MATH_NAMESPACE);
 	});
 
 	it('should render children with the correct namespace URI', () => {
@@ -31,12 +34,12 @@ describe('mathml', () => {
 
 		let namespace = scratch.querySelector('mrow').namespaceURI;
 
-		expect(namespace).to.equal('http://www.w3.org/1998/Math/MathML');
+		expect(namespace).to.equal(MATH_NAMESPACE);
 	});
 
 	it('should inherit correct namespace URI from parent', () => {
 		const math = document.createElementNS(
-			'http://www.w3.org/1998/Math/MathML',
+			MATH_NAMESPACE,
 			'math'
 		);
 		scratch.appendChild(math);
@@ -44,14 +47,14 @@ describe('mathml', () => {
 		render(<mrow />, scratch.firstChild);
 
 		let namespace = scratch.querySelector('mrow').namespaceURI;
-		expect(namespace).to.equal('http://www.w3.org/1998/Math/MathML');
+		expect(namespace).to.equal(MATH_NAMESPACE);
 	});
 
 	it('should inherit correct namespace URI from parent upon updating', () => {
 		setupRerender();
 
 		const math = document.createElementNS(
-			'http://www.w3.org/1998/Math/MathML',
+			MATH_NAMESPACE,
 			'math'
 		);
 		scratch.appendChild(math);
@@ -62,7 +65,7 @@ describe('mathml', () => {
 				// eslint-disable-next-line
 				this.setState({ show: false }, () => {
 					expect(scratch.querySelector('mo').namespaceURI).to.equal(
-						'http://www.w3.org/1998/Math/MathML'
+						MATH_NAMESPACE
 					);
 				});
 			}
@@ -90,4 +93,24 @@ describe('mathml', () => {
 		expect(scratch.firstChild).to.be.an('HTMLDivElement');
 		expect(scratch.firstChild.firstChild).to.be.an('MathMLElement');
 	});
+
+	it('should support XHTML phrasing content within MathML token elements', () => {
+		render(
+			<div>
+				<math>
+					<mrow>
+						<mi><ins>123</ins></mi>
+					</mrow>
+				</math>
+			</div>,
+			scratch
+		);
+
+		expect(scratch.querySelector('mi').namespaceURI).to.equal(
+			MATH_NAMESPACE
+		);
+		expect(scratch.querySelector('ins').namespaceURI).to.equal(
+			XHTML_NAMESPACE
+		);
+	});
 });