Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Firefox scanning when some combining characters are present #1214

Merged
merged 6 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ext/js/accessibility/google-docs-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 16 additions & 3 deletions ext/js/dom/document-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
*/
let cssZoomSupported = null;

/** @type {Set<?string>} */
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.
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
}
Expand Down
16 changes: 9 additions & 7 deletions ext/js/dom/text-source-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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} */
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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<Element, ?string>} */
let previousStyles = null;
try {
Expand All @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion ext/js/language/text-scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions test/document-util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ describe('Document utility tests', () => {
const source = textSourceGenerator.getRangeFromPoint(0, 0, {
deepContentScan: false,
normalizeCssZoom: true,
language: null,
});
switch (resultType) {
case 'TextSourceRange':
Expand Down
4 changes: 4 additions & 0 deletions types/ext/document-util.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down