[FIX] web_editor: prevent editor santizing root's ancestors

When sanitizing the content of the editor, we sometimes move up too far
and sanitize nodes that are not in the editor. This adds the proper
boundaries required to contain the sanitization.

closes odoo/odoo#124803

Signed-off-by: David Monjoie (dmo) <dmo@odoo.com>
This commit is contained in:
Antoine Guenet
2023-06-13 12:50:03 +02:00
parent 54e40d100e
commit 55837e14d7
2 changed files with 20 additions and 13 deletions
@@ -752,7 +752,7 @@ export class OdooEditor extends EventTarget {
}
// sanitize and mark current position as sanitized
sanitize(target);
sanitize(target, this.editable);
this._pluginCall('sanitizeElement',
[target.parentElement || target]);
this.options.onPostSanitize(target);
@@ -3902,7 +3902,7 @@ export class OdooEditor extends EventTarget {
node.replaceChildren();
}
sanitize(element);
sanitize(element, this.editable);
// Remove contenteditable=false on elements
for (const el of element.querySelectorAll('[contenteditable="false"]')) {
@@ -154,17 +154,24 @@ function sanitizeNode(node, root) {
return node;
}
export function sanitize(root) {
const start = root.ownerDocument.getSelection()?.anchorNode;
const rootClosestBlock = closestBlock(root);
if (rootClosestBlock) {
/**
* Sanitize a node tree and return the sanitized node.
*
* @param {Node} nodeToSanitize the node to sanitize
* @param {Node} [root] the root of the tree to sanitize (will not sanitize nodes outside of this tree)
* @returns {Node} the sanitized node
*/
export function sanitize(nodeToSanitize, root = nodeToSanitize) {
const start = nodeToSanitize.ownerDocument.getSelection()?.anchorNode;
const block = closestBlock(nodeToSanitize);
if (block && root.contains(block)) {
// If the node is a list, start sanitization from its parent to ensure
// adjacent lists are merged when needed.
const isList = ['UL', 'OL'].includes(rootClosestBlock.nodeName);
let node = isList ? rootClosestBlock.parentElement : rootClosestBlock;
const isList = ['UL', 'OL'].includes(block.nodeName);
let node = isList ? block.parentElement : block;
// Sanitize the tree.
while (node?.isConnected) {
while (node?.isConnected && root.contains(node)) {
if (!isProtected(node)) {
node = sanitizeNode(node, root); // The node itself might be replaced during sanitization.
}
@@ -172,11 +179,11 @@ export function sanitize(root) {
}
// Ensure unique ids on checklists and stars.
const elementsWithId = [...rootClosestBlock.querySelectorAll('[id^=checkId-]')];
const elementsWithId = [...block.querySelectorAll('[id^=checkId-]')];
const maxId = Math.max(...[0, ...elementsWithId.map(node => +node.getAttribute('id').substring(8))]);
let nextId = maxId + 1;
const ids = [];
for (const node of rootClosestBlock.querySelectorAll('[id^=checkId-], .o_checklist > li, .o_stars')) {
for (const node of block.querySelectorAll('[id^=checkId-], .o_checklist > li, .o_stars')) {
if (
!node.classList.contains('o_stars') && (
!node.parentElement.classList.contains('o_checklist') ||
@@ -200,7 +207,7 @@ export function sanitize(root) {
// Update link URL if label is a new valid link.
const startEl = start && closestElement(start, 'a');
if (startEl) {
if (startEl && root.contains(startEl)) {
const linkLabel = startEl.innerText;
const urlInfo = getUrlsInfosInString(linkLabel);
if (urlInfo.length && urlInfo[0].label === linkLabel && !startEl.href.startsWith('mailto:')) {
@@ -208,5 +215,5 @@ export function sanitize(root) {
}
}
}
return root;
return nodeToSanitize;
}