aboutsummaryrefslogtreecommitdiff
path: root/ext/js/dom/document-util.js
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2022-09-20 21:06:39 -0400
committerGitHub <noreply@github.com>2022-09-20 21:06:39 -0400
commit480869c3d1d820b344d23989d2deae64a594869e (patch)
treebc1d0c5143b71e312322e3dc851cb2c98050b8c6 /ext/js/dom/document-util.js
parentac373a67944a3241f96091b2bcd94f1b337a3940 (diff)
Exclude documentElement from zoom calculation (#2227)
* Exclude documentElement from zoom calculation * Add an option * Refactor zoom coordinate conversion functions * Convert zoom coordinates for text sources * Rename variable * Convert rect coordinate spaces * Handle shadow DOM
Diffstat (limited to 'ext/js/dom/document-util.js')
-rw-r--r--ext/js/dom/document-util.js87
1 files changed, 65 insertions, 22 deletions
diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js
index b974387e..41f44afe 100644
--- a/ext/js/dom/document-util.js
+++ b/ext/js/dom/document-util.js
@@ -24,10 +24,9 @@
class DocumentUtil {
constructor() {
this._transparentColorPattern = /rgba\s*\([^)]*,\s*0(?:\.0+)?\s*\)/;
- this._cssZoomSupported = (typeof document.createElement('div').style.zoom === 'string');
}
- getRangeFromPoint(x, y, deepContentScan) {
+ getRangeFromPoint(x, y, {deepContentScan, normalizeCssZoom}) {
const elements = this._getElementsFromPoint(x, y, deepContentScan);
let imposter = null;
let imposterContainer = null;
@@ -52,7 +51,7 @@ class DocumentUtil {
}
}
- const range = this._caretRangeFromPointExt(x, y, deepContentScan ? elements : []);
+ const range = this._caretRangeFromPointExt(x, y, deepContentScan ? elements : [], normalizeCssZoom);
if (range !== null) {
if (imposter !== null) {
this._setImposterStyle(imposterContainer.style, 'z-index', '-2147483646');
@@ -175,6 +174,60 @@ class DocumentUtil {
};
}
+ /**
+ * Computes the scaling adjustment that is necessary for client space coordinates based on the
+ * CSS zoom level.
+ * @param {Node} node A node in the document.
+ * @returns {number} The scaling factor.
+ */
+ static computeZoomScale(node) {
+ if (this._cssZoomSupported === null) {
+ this._cssZoomSupported = (typeof document.createElement('div').style.zoom === 'string');
+ }
+ if (!this._cssZoomSupported) { return 1; }
+ // documentElement must be excluded because the computer style of its zoom property is inconsistent.
+ // * If CSS `:root{zoom:X;}` is specified, the computed zoom will always report `X`.
+ // * If CSS `:root{zoom:X;}` is not specified, the computed zoom report the browser's zoom level.
+ // Therefor, if CSS root zoom is specified as a value other than 1, the adjusted {x, y} values
+ // would be incorrect, which is not new behaviour.
+ let scale = 1;
+ const {ELEMENT_NODE, DOCUMENT_FRAGMENT_NODE} = Node;
+ const {documentElement} = document;
+ for (; node !== null && node !== documentElement; node = node.parentNode) {
+ const {nodeType} = node;
+ if (nodeType === DOCUMENT_FRAGMENT_NODE) {
+ const {host} = node;
+ if (typeof host !== 'undefined') {
+ node = host;
+ }
+ continue;
+ } else if (nodeType !== ELEMENT_NODE) {
+ continue;
+ }
+ let {zoom} = getComputedStyle(node);
+ if (typeof zoom !== 'string') { continue; }
+ zoom = Number.parseFloat(zoom);
+ if (!Number.isFinite(zoom) || zoom === 0) { continue; }
+ scale *= zoom;
+ }
+ return scale;
+ }
+
+ static convertRectZoomCoordinates(rect, node) {
+ const scale = this.computeZoomScale(node);
+ return (scale === 1 ? rect : new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale));
+ }
+
+ static convertMultipleRectZoomCoordinates(rects, node) {
+ const scale = this.computeZoomScale(node);
+ if (scale === 1) { return rects; }
+ const results = [];
+ for (const rect of rects) {
+ results.push(new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale));
+ }
+ return results;
+ }
+
static isPointInRect(x, y, rect) {
return (
x >= rect.left && x < rect.right &&
@@ -435,7 +488,7 @@ class DocumentUtil {
return e !== null ? [e] : [];
}
- _isPointInRange(x, y, range) {
+ _isPointInRange(x, y, range, normalizeCssZoom) {
// Require a text node to start
const {startContainer} = range;
if (startContainer.nodeType !== Node.TEXT_NODE) {
@@ -443,8 +496,10 @@ class DocumentUtil {
}
// Convert CSS zoom coordinates
- if (this._cssZoomSupported) {
- ({x, y} = this._convertCssZoomCoordinates(x, y, startContainer));
+ if (normalizeCssZoom) {
+ const scale = DocumentUtil.computeZoomScale(startContainer);
+ x /= scale;
+ y /= scale;
}
// Scan forward
@@ -583,7 +638,7 @@ class DocumentUtil {
}
}
- _caretRangeFromPointExt(x, y, elements) {
+ _caretRangeFromPointExt(x, y, elements, normalizeCssZoom) {
let previousStyles = null;
try {
let i = 0;
@@ -596,7 +651,7 @@ class DocumentUtil {
const startContainer = range.startContainer;
if (startContinerPre !== startContainer) {
- if (this._isPointInRange(x, y, range)) {
+ if (this._isPointInRange(x, y, range, normalizeCssZoom)) {
return range;
}
startContinerPre = startContainer;
@@ -668,18 +723,6 @@ class DocumentUtil {
_isElementUserSelectAll(element) {
return getComputedStyle(element).userSelect === 'all';
}
-
- _convertCssZoomCoordinates(x, y, node) {
- const ELEMENT_NODE = Node.ELEMENT_NODE;
- for (; node !== null; node = node.parentNode) {
- if (node.nodeType !== ELEMENT_NODE) { continue; }
- let {zoom} = getComputedStyle(node);
- if (typeof zoom !== 'string') { continue; }
- zoom = Number.parseFloat(zoom);
- if (!Number.isFinite(zoom) || zoom === 0) { continue; }
- x /= zoom;
- y /= zoom;
- }
- return {x, y};
- }
}
+// eslint-disable-next-line no-underscore-dangle
+DocumentUtil._cssZoomSupported = null;