diff --git a/ext/js/accessibility/google-docs-util.js b/ext/js/accessibility/google-docs-util.js index c915487a27..3d52236585 100644 --- a/ext/js/accessibility/google-docs-util.js +++ b/ext/js/accessibility/google-docs-util.js @@ -137,7 +137,7 @@ export class GoogleDocsUtil { const mid = Math.floor((start + end) / 2); range.setStart(textNode, mid); range.setEnd(textNode, end); - if (isPointInAnyRect(x, y, range.getClientRects())) { + if (isPointInAnyRect(x, y, range.getClientRects(), null)) { start = mid; } else { end = mid; diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index f8dba8781f..2dd3c2bc73 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -22,6 +22,9 @@ */ let cssZoomSupported = null; +/** @type {Set} */ +const FIREFOX_RECT_EXCLUDED_LANGUAGES = new Set(['th']); + /** * Computes the scaling adjustment that is necessary for client space coordinates based on the * CSS zoom level. @@ -107,9 +110,18 @@ export function isPointInRect(x, y, rect) { * @param {number} x The horizontal coordinate. * @param {number} y The vertical coordinate. * @param {DOMRect[]|DOMRectList} rects The rect to check. + * @param {?string} language * @returns {boolean} `true` if the point is inside any of the rects, `false` otherwise. */ -export function isPointInAnyRect(x, y, rects) { +export function isPointInAnyRect(x, y, rects, language) { + // Always return true for Firefox due to inconsistencies with Range.getClientRects() implementation from unclear W3C spec + // https://drafts.csswg.org/cssom-view/#dom-range-getclientrects + // https://bugzilla.mozilla.org/show_bug.cgi?id=816238 + // Firefox returns only the first level nodes, Chromium returns every text node + // This only affects specific languages + if (typeof browser !== 'undefined' && FIREFOX_RECT_EXCLUDED_LANGUAGES.has(language)) { + return true; + } for (const rect of rects) { if (isPointInRect(x, y, rect)) { return true; @@ -123,12 +135,13 @@ export function isPointInAnyRect(x, y, rects) { * @param {number} x The horizontal coordinate. * @param {number} y The vertical coordinate. * @param {Selection} selection The selection to check. + * @param {string} language * @returns {boolean} `true` if the point is inside the selection, `false` otherwise. */ -export function isPointInSelection(x, y, selection) { +export function isPointInSelection(x, y, selection, language) { for (let i = 0; i < selection.rangeCount; ++i) { const range = selection.getRangeAt(i); - if (isPointInAnyRect(x, y, range.getClientRects())) { + if (isPointInAnyRect(x, y, range.getClientRects(), language)) { return true; } } diff --git a/ext/js/dom/text-source-generator.js b/ext/js/dom/text-source-generator.js index 279c472517..2413b0bc3b 100644 --- a/ext/js/dom/text-source-generator.js +++ b/ext/js/dom/text-source-generator.js @@ -187,7 +187,7 @@ export class TextSourceGenerator { * @returns {?import('text-source').TextSource} A range for the hovered text or element, or `null` if no applicable content was found. */ _getRangeFromPointInternal(x, y, options) { - const {deepContentScan, normalizeCssZoom} = options; + const {deepContentScan, normalizeCssZoom, language} = options; const elements = this._getElementsFromPoint(x, y, deepContentScan); /** @type {?HTMLDivElement} */ @@ -216,7 +216,7 @@ export class TextSourceGenerator { } } - const range = this._caretRangeFromPointExt(x, y, deepContentScan ? elements : [], normalizeCssZoom); + const range = this._caretRangeFromPointExt(x, y, deepContentScan ? elements : [], normalizeCssZoom, language); if (range !== null) { if (imposter !== null) { this._setImposterStyle(/** @type {HTMLDivElement} */ (imposterContainer).style, 'z-index', '-2147483646'); @@ -347,9 +347,10 @@ export class TextSourceGenerator { * @param {number} y * @param {Range} range * @param {boolean} normalizeCssZoom + * @param {?string} language * @returns {boolean} */ - _isPointInRange(x, y, range, normalizeCssZoom) { + _isPointInRange(x, y, range, normalizeCssZoom, language) { // Require a text node to start const {startContainer} = range; if (startContainer.nodeType !== Node.TEXT_NODE) { @@ -370,7 +371,7 @@ export class TextSourceGenerator { const {node, offset, content} = new DOMTextScanner(nodePre, offsetPre, true, false).seek(1); range.setEnd(node, offset); - if (!this._isWhitespace(content) && isPointInAnyRect(x, y, range.getClientRects())) { + if (!this._isWhitespace(content) && isPointInAnyRect(x, y, range.getClientRects(), language)) { return true; } } finally { @@ -381,7 +382,7 @@ export class TextSourceGenerator { const {node, offset, content} = new DOMTextScanner(startContainer, range.startOffset, true, false).seek(-1); range.setStart(node, offset); - if (!this._isWhitespace(content) && isPointInAnyRect(x, y, range.getClientRects())) { + if (!this._isWhitespace(content) && isPointInAnyRect(x, y, range.getClientRects(), language)) { // This purposefully leaves the starting offset as modified and sets the range length to 0. range.setEnd(node, offset); return true; @@ -522,9 +523,10 @@ export class TextSourceGenerator { * @param {number} y * @param {Element[]} elements * @param {boolean} normalizeCssZoom + * @param {?string} language * @returns {?Range} */ - _caretRangeFromPointExt(x, y, elements, normalizeCssZoom) { + _caretRangeFromPointExt(x, y, elements, normalizeCssZoom, language) { /** @type {?Map} */ let previousStyles = null; try { @@ -538,7 +540,7 @@ export class TextSourceGenerator { const startContainer = range.startContainer; if (startContinerPre !== startContainer) { - if (this._isPointInRange(x, y, range, normalizeCssZoom)) { + if (this._isPointInRange(x, y, range, normalizeCssZoom, language)) { return range; } startContinerPre = startContainer; diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index 23853c09d5..f7a0e84970 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -731,8 +731,9 @@ export class TextScanner extends EventDispatcher { this._preventNextClick = false; this._touchTapValid = true; + const languageNotNull = this._language !== null ? this._language : ''; const selection = window.getSelection(); - if (selection !== null && isPointInSelection(x, y, selection)) { + if (selection !== null && isPointInSelection(x, y, selection, languageNotNull)) { return; } @@ -1356,6 +1357,7 @@ export class TextScanner extends EventDispatcher { const textSource = this._textSourceGenerator.getRangeFromPoint(x, y, { deepContentScan: this._deepContentScan, normalizeCssZoom: this._normalizeCssZoom, + language: this._language, }); if (textSource !== null) { try { diff --git a/test/document-util.test.js b/test/document-util.test.js index 8add2b97bf..b5b32eac2a 100644 --- a/test/document-util.test.js +++ b/test/document-util.test.js @@ -187,6 +187,7 @@ describe('Document utility tests', () => { const source = textSourceGenerator.getRangeFromPoint(0, 0, { deepContentScan: false, normalizeCssZoom: true, + language: null, }); switch (resultType) { case 'TextSourceRange': diff --git a/types/ext/document-util.d.ts b/types/ext/document-util.d.ts index ec86259699..a46d1f7bd0 100644 --- a/types/ext/document-util.d.ts +++ b/types/ext/document-util.d.ts @@ -30,6 +30,10 @@ export type GetRangeFromPointOptions = { * Whether or not zoom coordinates should be normalized. */ normalizeCssZoom: boolean; + /** + * ISO-639 code of the language. + */ + language: string | null; }; export type ToNumberConstraints = {