Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #2271: Editor infinite loop pasting HTML #3099

Merged
merged 1 commit into from
Jul 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}