aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2021-02-23 17:43:52 -0500
committerGitHub <noreply@github.com>2021-02-23 17:43:52 -0500
commit28585e6ec6b612a1586039e4b74f61cc2a655467 (patch)
tree9c8165dbe9994f45174b7ed8ec8200fffc91a376
parent7abb8a6056f78bffb8906b733a102711aa7a3464 (diff)
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
-rw-r--r--ext/js/dom/document-util.js100
1 files changed, 88 insertions, 12 deletions
diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js
index 513a0c05..80081aca 100644
--- a/ext/js/dom/document-util.js
+++ b/ext/js/dom/document-util.js
@@ -479,21 +479,87 @@ class DocumentUtil {
return null;
}
- const range = document.createRange();
- const offset = (node.nodeType === Node.TEXT_NODE ? position.offset : 0);
+ 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)) {
+ return this._caretPositionFromPointNormalizeStyles(x, y, node);
+ }
+ 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;
}
- 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) {
- const modifications = [];
+ let previousStyles = null;
try {
let i = 0;
let startContinerPre = null;
@@ -511,19 +577,20 @@ class DocumentUtil {
startContinerPre = startContainer;
}
- i = this._disableTransparentElement(elements, i, modifications);
+ previousStyles = new Map();
+ i = this._disableTransparentElement(elements, i, previousStyles);
if (i < 0) {
return null;
}
}
} finally {
- if (modifications.length > 0) {
- this._restoreElementStyleModifications(modifications);
+ if (previousStyles !== null && previousStyles.size > 0) {
+ this._revertStyles(previousStyles);
}
}
}
- _disableTransparentElement(elements, i, modifications) {
+ _disableTransparentElement(elements, i, previousStyles) {
while (true) {
if (i >= elements.length) {
return -1;
@@ -531,16 +598,21 @@ class DocumentUtil {
const element = elements[i++];
if (this._isElementTransparent(element)) {
- const style = element.hasAttribute('style') ? element.getAttribute('style') : null;
- modifications.push({element, style});
+ this._recordPreviousStyle(previousStyles, element);
element.style.setProperty('pointer-events', 'none', 'important');
return i;
}
}
}
- _restoreElementStyleModifications(modifications) {
- for (const {element, style} of modifications) {
+ _recordPreviousStyle(previousStyles, element) {
+ 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) {
element.removeAttribute('style');
} else {
@@ -567,4 +639,8 @@ class DocumentUtil {
_isColorTransparent(cssColor) {
return this._transparentColorPattern.test(cssColor);
}
+
+ _isElementUserSelectAll(element) {
+ return getComputedStyle(element).userSelect === 'all';
+ }
}