From 480869c3d1d820b344d23989d2deae64a594869e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 20 Sep 2022 21:06:39 -0400 Subject: 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 --- ext/js/dom/document-util.js | 87 +++++++++++++++++++++++++++++---------- ext/js/dom/text-source-element.js | 5 ++- ext/js/dom/text-source-range.js | 4 +- 3 files changed, 70 insertions(+), 26 deletions(-) (limited to 'ext/js/dom') 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; diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js index fe3fe083..b5fc1683 100644 --- a/ext/js/dom/text-source-element.js +++ b/ext/js/dom/text-source-element.js @@ -16,6 +16,7 @@ */ /* global + * DocumentUtil * StringUtil */ @@ -95,11 +96,11 @@ class TextSourceElement { } getRect() { - return this._element.getBoundingClientRect(); + return DocumentUtil.convertRectZoomCoordinates(this._element.getBoundingClientRect(), this._element); } getRects() { - return this._element.getClientRects(); + return DocumentUtil.convertMultipleRectZoomCoordinates(this._element.getClientRects(), this._element); } getWritingMode() { diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index 5e3e814c..6c35c4cb 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -91,11 +91,11 @@ class TextSourceRange { } getRect() { - return this._range.getBoundingClientRect(); + return DocumentUtil.convertRectZoomCoordinates(this._range.getBoundingClientRect(), this._range.startContainer); } getRects() { - return this._range.getClientRects(); + return DocumentUtil.convertMultipleRectZoomCoordinates(this._range.getClientRects(), this._range.startContainer); } getWritingMode() { -- cgit v1.2.3