Fix user select all handling (#1436)

* Update how style restoration is performed

* Refactor

* Add workaround for Firefox issue with user-select: all

* Add infinite loop prevention
This commit is contained in:
toasted-nutbread 2021-02-23 17:43:52 -05:00 committed by GitHub
parent 7abb8a6056
commit 28585e6ec6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -479,21 +479,87 @@ class DocumentUtil {
return null; return null;
} }
const range = document.createRange(); let offset = 0;
const offset = (node.nodeType === Node.TEXT_NODE ? position.offset : 0); const {nodeType} = node;
switch (nodeType) {
case Node.TEXT_NODE:
offset = position.offset;
break;
case Node.ELEMENT_NODE:
// Elements with user-select: all will return the element
// instead of a text point inside the element.
if (this._isElementUserSelectAll(node)) {
return this._caretPositionFromPointNormalizeStyles(x, y, node);
}
break;
}
try { try {
const range = document.createRange();
range.setStart(node, offset); range.setStart(node, offset);
range.setEnd(node, offset); range.setEnd(node, offset);
return range;
} catch (e) { } catch (e) {
// Firefox throws new DOMException("The operation is insecure.") // Firefox throws new DOMException("The operation is insecure.")
// when trying to select a node from within a ShadowRoot. // when trying to select a node from within a ShadowRoot.
return null; return null;
} }
return range; }
_caretPositionFromPointNormalizeStyles(x, y, nextElement) {
const previousStyles = new Map();
try {
while (true) {
this._recordPreviousStyle(previousStyles, nextElement);
nextElement.style.setProperty('user-select', 'text', 'important');
const position = document.caretPositionFromPoint(x, y);
if (position === null) {
return null;
}
const node = position.offsetNode;
if (node === null) {
return null;
}
let offset = 0;
const {nodeType} = node;
switch (nodeType) {
case Node.TEXT_NODE:
offset = position.offset;
break;
case Node.ELEMENT_NODE:
// Elements with user-select: all will return the element
// instead of a text point inside the element.
if (this._isElementUserSelectAll(node)) {
if (previousStyles.has(node)) {
// Recursive
return null;
}
nextElement = node;
continue;
}
break;
}
try {
const range = document.createRange();
range.setStart(node, offset);
range.setEnd(node, offset);
return range;
} catch (e) {
// Firefox throws new DOMException("The operation is insecure.")
// when trying to select a node from within a ShadowRoot.
return null;
}
}
} finally {
this._revertStyles(previousStyles);
}
} }
_caretRangeFromPointExt(x, y, elements) { _caretRangeFromPointExt(x, y, elements) {
const modifications = []; let previousStyles = null;
try { try {
let i = 0; let i = 0;
let startContinerPre = null; let startContinerPre = null;
@ -511,19 +577,20 @@ class DocumentUtil {
startContinerPre = startContainer; startContinerPre = startContainer;
} }
i = this._disableTransparentElement(elements, i, modifications); previousStyles = new Map();
i = this._disableTransparentElement(elements, i, previousStyles);
if (i < 0) { if (i < 0) {
return null; return null;
} }
} }
} finally { } finally {
if (modifications.length > 0) { if (previousStyles !== null && previousStyles.size > 0) {
this._restoreElementStyleModifications(modifications); this._revertStyles(previousStyles);
} }
} }
} }
_disableTransparentElement(elements, i, modifications) { _disableTransparentElement(elements, i, previousStyles) {
while (true) { while (true) {
if (i >= elements.length) { if (i >= elements.length) {
return -1; return -1;
@ -531,16 +598,21 @@ class DocumentUtil {
const element = elements[i++]; const element = elements[i++];
if (this._isElementTransparent(element)) { if (this._isElementTransparent(element)) {
const style = element.hasAttribute('style') ? element.getAttribute('style') : null; this._recordPreviousStyle(previousStyles, element);
modifications.push({element, style});
element.style.setProperty('pointer-events', 'none', 'important'); element.style.setProperty('pointer-events', 'none', 'important');
return i; return i;
} }
} }
} }
_restoreElementStyleModifications(modifications) { _recordPreviousStyle(previousStyles, element) {
for (const {element, style} of modifications) { if (previousStyles.has(element)) { return; }
const style = element.hasAttribute('style') ? element.getAttribute('style') : null;
previousStyles.set(element, style);
}
_revertStyles(previousStyles) {
for (const [element, style] of previousStyles.entries()) {
if (style === null) { if (style === null) {
element.removeAttribute('style'); element.removeAttribute('style');
} else { } else {
@ -567,4 +639,8 @@ class DocumentUtil {
_isColorTransparent(cssColor) { _isColorTransparent(cssColor) {
return this._transparentColorPattern.test(cssColor); return this._transparentColorPattern.test(cssColor);
} }
_isElementUserSelectAll(element) {
return getComputedStyle(element).userSelect === 'all';
}
} }