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:
parent
7abb8a6056
commit
28585e6ec6
@ -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';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user