From feefc0d7938a52c239ee9a65b9697370dcef070a Mon Sep 17 00:00:00 2001 From: spocke Date: Mon, 3 Feb 2025 15:23:30 +0100 Subject: [PATCH] TINY-11755: Retain semantics if annotations are allowed (#10122) Related Ticket: TINY-11755 Description of Changes: * Fixed the issue with `semantics` being removed even when `annotation` elements was allowed * Also changed so that the `annotation` element is properly removed if it's not allowed so that it wouldn't get rendered. * The `semantics` element is unwrapped if it's not allowed that should work since the first one should be equation and we want to keep that. Pre-checks: * [x] Changelog entry added * [x] Tests have been added (if applicable) * [x] Branch prefixed with `feature/`, `hotfix/` or `spike/` Review: * [x] Milestone set * [x] Docs ticket created (if applicable) GitHub issues (if applicable): ## Summary by CodeRabbit - **Bug Fixes** - Resolved an issue with MathML element handling, specifically ensuring the `semantics` element is properly retained when annotation elements are allowed. - Improved parsing and sanitization of MathML annotations based on specified encoding rules. - **Tests** - Added new test cases to verify MathML annotation handling under different configuration scenarios. --- .../tinymce-TINY-11755-2025-01-29.yaml | 7 ++ .../src/core/main/ts/html/Sanitization.ts | 20 +++- .../ts/browser/content/EditorContentTest.ts | 106 ++++++++++++------ .../test/ts/browser/html/DomParserTest.ts | 7 ++ 4 files changed, 101 insertions(+), 39 deletions(-) create mode 100644 .changes/unreleased/tinymce-TINY-11755-2025-01-29.yaml diff --git a/.changes/unreleased/tinymce-TINY-11755-2025-01-29.yaml b/.changes/unreleased/tinymce-TINY-11755-2025-01-29.yaml new file mode 100644 index 0000000000..8e2a19a9d4 --- /dev/null +++ b/.changes/unreleased/tinymce-TINY-11755-2025-01-29.yaml @@ -0,0 +1,7 @@ +project: tinymce +kind: Fixed +body: The `semantics` element in MathML was not properly retained when `annotation` + elements was allowed. +time: 2025-01-29T08:43:41.042875+01:00 +custom: + Issue: TINY-11755 diff --git a/modules/tinymce/src/core/main/ts/html/Sanitization.ts b/modules/tinymce/src/core/main/ts/html/Sanitization.ts index 282497d4ce..025a02d798 100644 --- a/modules/tinymce/src/core/main/ts/html/Sanitization.ts +++ b/modules/tinymce/src/core/main/ts/html/Sanitization.ts @@ -232,15 +232,25 @@ const sanitizeMathmlElement = (node: Element, settings: DomParserSettings) => { }; const purify = createDompurify(); + const allowedEncodings = settings.allow_mathml_annotation_encodings; + const hasAllowedEncodings = Type.isArray(allowedEncodings) && allowedEncodings.length > 0; + const hasValidEncoding = (el: Element) => { + const encoding = el.getAttribute('encoding'); + return hasAllowedEncodings && Type.isString(encoding) && Arr.contains(allowedEncodings, encoding); + }; purify.addHook('uponSanitizeElement', (node, evt) => { const lcTagName = evt.tagName ?? node.nodeName.toLowerCase(); - const allowedEncodings = settings.allow_mathml_annotation_encodings; - if (lcTagName === 'annotation' && Type.isArray(allowedEncodings) && allowedEncodings.length > 0) { - const encoding = node.getAttribute('encoding'); - if (Type.isString(encoding) && Arr.contains(allowedEncodings, encoding)) { - evt.allowedTags[lcTagName] = true; + if (hasAllowedEncodings && lcTagName === 'semantics') { + evt.allowedTags[lcTagName] = true; + } + + if (lcTagName === 'annotation') { + const keepElement = hasValidEncoding(node); + evt.allowedTags[lcTagName] = keepElement; + if (!keepElement) { + node.remove(); } } }); diff --git a/modules/tinymce/src/core/test/ts/browser/content/EditorContentTest.ts b/modules/tinymce/src/core/test/ts/browser/content/EditorContentTest.ts index 70844a8655..e14ef24b26 100644 --- a/modules/tinymce/src/core/test/ts/browser/content/EditorContentTest.ts +++ b/modules/tinymce/src/core/test/ts/browser/content/EditorContentTest.ts @@ -724,45 +724,83 @@ describe('browser.tinymce.core.content.EditorContentTest', () => { }); context('math elements', () => { - const hook = TinyHooks.bddSetupLight({ - base_url: '/project/tinymce/js/tinymce', - custom_elements: 'math', - allow_mathml_annotation_encodings: [ - 'application/x-tex', - 'application/custom', - 'wiris' - ] - }, []); + context('annotations encodings defined', () => { + const hook = TinyHooks.bddSetupLight({ + base_url: '/project/tinymce/js/tinymce', + custom_elements: 'math', + allow_mathml_annotation_encodings: [ + 'application/x-tex', + 'application/custom', + 'wiris' + ] + }, []); - it('TINY-11166: allow_mathml_annotation_encodings should retain the specified annotation elements', () => { - const editor = hook.editor(); + it('TINY-11166: allow_mathml_annotation_encodings should retain the specified annotation elements and the parent semantics element', () => { + const editor = hook.editor(); - const input = [ - '
', - '\\frac{1}{2}', - 'custom', - 'custom with src', - '{"version":"1.1","math":"<math xmlns="http://www.w3.org/1998/Math/MathML"><mfrac><mn>1</mn><mn>2</mn></mfrac></math>"}', - 'html', - 'svg', - '
' - ].join(''); + const input = [ + '
', + '1\\frac{1}{2}', + '1custom', + '1custom with src', + '1{"version":"1.1","math":"<math xmlns="http://www.w3.org/1998/Math/MathML"><mn>1></mn></math>"}', + '1html', + '1svg', + '
' + ].join(''); - editor.setContent(input); + editor.setContent(input); - const expected = [ - '
', - '\\frac{1}{2}', - 'custom', - 'custom with src', - '{"version":"1.1","math":"<math xmlns="http://www.w3.org/1998/Math/MathML"><mfrac><mn>1</mn><mn>2</mn></mfrac></math>"}', - 'html', - 'svg', - '
' - ].join(''); + const expected = [ + '
', + '1\\frac{1}{2}', + '1custom', + '1custom with src', + '1{"version":"1.1","math":"<math xmlns="http://www.w3.org/1998/Math/MathML"><mn>1></mn></math>"}', + '1', + '1', + '
' + ].join(''); - TinyAssertions.assertContent(editor, expected); + TinyAssertions.assertContent(editor, expected); + }); + }); + + context('annotations encodings not defined', () => { + const hook = TinyHooks.bddSetupLight({ + base_url: '/project/tinymce/js/tinymce', + custom_elements: 'math' + }, []); + + it('TINY-11166: not setting allow_mathml_annotation_encodings should not retain the semantics or annotation elements', () => { + const editor = hook.editor(); + + const input = [ + '
', + '1\\frac{1}{2}', + '1custom', + '1custom with src', + '1{"version":"1.1","math":"<math xmlns="http://www.w3.org/1998/Math/MathML"><mn>1></mn></math>"}', + '1html', + '1svg', + '
' + ].join(''); + + editor.setContent(input); + + const expected = [ + '
', + '1', + '1', + '1', + '1', + '1', + '1', + '
' + ].join(''); + + TinyAssertions.assertContent(editor, expected); + }); }); }); - }); diff --git a/modules/tinymce/src/core/test/ts/browser/html/DomParserTest.ts b/modules/tinymce/src/core/test/ts/browser/html/DomParserTest.ts index e60a901206..d66a1d809e 100644 --- a/modules/tinymce/src/core/test/ts/browser/html/DomParserTest.ts +++ b/modules/tinymce/src/core/test/ts/browser/html/DomParserTest.ts @@ -1748,6 +1748,13 @@ describe('browser.tinymce.core.html.DomParserTest', () => { assert.equal(serializedHtml, '
'); }); + it('TINY-11755: Should retain semantics and annotations if allow_mathml_annotation_encodings is set', () => { + const schema = Schema(); + schema.addValidElements('math[*]'); + const input = 'annotation1annotation2'; + const serializedHtml = HtmlSerializer({}, schema).serialize(DomParser({ allow_mathml_annotation_encodings: [ '-x-custom-mime' ] }, schema).parse(input)); + assert.equal(serializedHtml, 'annotation1'); + }); }); context('Special elements', () => {