Skip to content

Commit

Permalink
Fix #2271: Editor infinite loop pasting HTML (#3099)
Browse files Browse the repository at this point in the history
  • Loading branch information
melloware authored Jul 23, 2022
1 parent 3e978bf commit 106494b
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 2 deletions.
15 changes: 13 additions & 2 deletions components/lib/editor/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,23 @@ export const Editor = React.memo(React.forwardRef((props, ref) => {
quill.current.setContents(quill.current.clipboard.convert(props.value));
}

quill.current.on('text-change', (delta, source) => {
quill.current.on('text-change', (delta, oldContents, source) => {
let firstChild = contentRef.current.children[0];
let html = firstChild ? firstChild.innerHTML : null;
let text = quill.current.getText();
if (html === '<p><br></p>') {
html = null;
html = null;
}

// GitHub #2271 prevent infinite loop on clipboard paste of HTML
if (source === "api") {
const htmlValue = contentRef.current.children[0];
const editorValue = document.createElement("div");
editorValue.innerHTML = props.value || "";
// this is necessary because Quill rearranged style elements
if (DomHandler.isEqualElement(htmlValue, editorValue)) {
return;
}
}

if (props.onTextChange) {
Expand Down
82 changes: 82 additions & 0 deletions components/lib/utils/DomHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -953,4 +953,86 @@ export default class DomHandler {
return ((element && element.nodeType === 9) || this.isExist(element)) ? element : null;
}
}

/**
* Get the attribute names for an element and sorts them alpha for comparison
*/
static getAttributeNames(node) {
let index, rv, attrs;

rv = [];
attrs = node.attributes;
for (index = 0; index < attrs.length; ++index) {
rv.push(attrs[index].nodeName);
}
rv.sort();
return rv;
}

/**
* Compare two elements for equality. Even will compare if the style element
* is out of order for example:
*
* elem1 = style="color: red; font-size: 28px"
* elem2 = style="font-size: 28px; color: red"
*/
static isEqualElement(elm1, elm2) {
let attrs1, attrs2, name, node1, node2;

// Compare attributes without order sensitivity
attrs1 = DomHandler.getAttributeNames(elm1);
attrs2 = DomHandler.getAttributeNames(elm2);
if (attrs1.join(",") !== attrs2.join(",")) {
// console.log("Found nodes with different sets of attributes; not equiv");
return false;
}

// ...and values
// unless you want to compare DOM0 event handlers
// (onclick="...")
for (let index = 0; index < attrs1.length; ++index) {
name = attrs1[index];
if (name === 'style') {
const astyle = elm1.style;
const bstyle = elm2.style;
const rexDigitsOnly = /^\d+$/;
for (const key of Object.keys(astyle)) {
if (!rexDigitsOnly.test(key) && astyle[key] !== bstyle[key]) {
// Not equivalent, stop
//console.log("Found nodes with mis-matched values for attribute '" + name + "'; not equiv");
return false;
}
}
} else {
if (elm1.getAttribute(name) !== elm2.getAttribute(name)) {
// console.log("Found nodes with mis-matched values for attribute '" + name + "'; not equiv");
return false;
}
}
}

// Walk the children
for (node1 = elm1.firstChild, node2 = elm2.firstChild; node1 && node2; node1 = node1.nextSibling, node2 = node2.nextSibling) {
if (node1.nodeType !== node2.nodeType) {
// display("Found nodes of different types; not equiv");
return false;
}
if (node1.nodeType === 1) { // Element
if (!DomHandler.isEqualElement(node1, node2)) {
return false;
}
} else if (node1.nodeValue !== node2.nodeValue) {
// console.log("Found nodes with mis-matched nodeValues; not equiv");
return false;
}
}
if (node1 || node2) {
// One of the elements had more nodes than the other
// console.log("Found more children of one element than the other; not equivalent");
return false;
}

// Seem the same
return true;
}
}

0 comments on commit 106494b

Please sign in to comment.