aboutsummaryrefslogtreecommitdiff
path: root/ext/js
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2022-09-25 09:37:33 -0400
committerGitHub <noreply@github.com>2022-09-25 09:37:33 -0400
commit75d30594511a6593044565829ad55369fecaf4cd (patch)
tree7908a9ffc211f9d8b9342e98bd143a7dc09b500d /ext/js
parent8240482e9d0b6da7a996bc581c9b5304ebcf22aa (diff)
TextSource* API updates (#2236)
* Move TextSourceRange static functions to DocumentUtil getWritingMode is also simplified * Update Google Docs range to be empty to match other range sources * Rename imposterContainer to imposterElement * Add static creation functions * Add static creation function * Remove unused collapse function * Don't select imposter elements * Refactor setEndOffset * Adjust argument order for setEndOffset * Update TextSourceRange constructor * Remove unused isConnected * Cache rects * Fix test * Remove unused getRect * Revert "Fix test" * Remove cachedRect * Use the source element rect to handle scroll differences * Writing mode update * Remove _cachedRects update This shouldn't be necessary as the imposter is usually detached almost immediately after scanning, giving no time for the window to be resized or scrolled.
Diffstat (limited to 'ext/js')
-rw-r--r--ext/js/accessibility/google-docs-util.js4
-rw-r--r--ext/js/app/frontend.js2
-rw-r--r--ext/js/dom/document-util.js59
-rw-r--r--ext/js/dom/text-source-element.js29
-rw-r--r--ext/js/dom/text-source-range.js109
-rw-r--r--ext/js/language/text-scanner.js8
-rw-r--r--ext/js/pages/settings/popup-preview-frame.js2
7 files changed, 123 insertions, 90 deletions
diff --git a/ext/js/accessibility/google-docs-util.js b/ext/js/accessibility/google-docs-util.js
index e50ba652..12f2db24 100644
--- a/ext/js/accessibility/google-docs-util.js
+++ b/ext/js/accessibility/google-docs-util.js
@@ -87,7 +87,7 @@ class GoogleDocsUtil {
const range = this._getRangeWithPoint(content, x, y, normalizeCssZoom);
this._setImportantStyle(textStyle, 'pointer-events', 'none');
this._setImportantStyle(textStyle, 'opacity', '0');
- return new TextSourceRange(range, '', svgText, element);
+ return TextSourceRange.createFromImposter(range, svgText, element);
}
static _getRangeWithPoint(textNode, x, y, normalizeCssZoom) {
@@ -110,7 +110,7 @@ class GoogleDocsUtil {
}
}
range.setStart(textNode, start);
- range.setEnd(textNode, end);
+ range.setEnd(textNode, start);
return range;
}
diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js
index 3a262c65..229ec940 100644
--- a/ext/js/app/frontend.js
+++ b/ext/js/app/frontend.js
@@ -757,7 +757,7 @@ class Frontend {
async _scanSelectedText(allowEmptyRange) {
const range = this._getFirstSelectionRange(allowEmptyRange);
if (range === null) { return false; }
- const source = new TextSourceRange(range, range.toString(), null, null);
+ const source = TextSourceRange.create(range);
await this._textScanner.search(source, {focus: true, restoreSelection: true});
return true;
}
diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js
index 76f551c7..73525ff5 100644
--- a/ext/js/dom/document-util.js
+++ b/ext/js/dom/document-util.js
@@ -66,7 +66,7 @@ class DocumentUtil {
case 'IMG':
case 'BUTTON':
case 'SELECT':
- return new TextSourceElement(element);
+ return TextSourceElement.create(element);
case 'INPUT':
if (element.type === 'text') {
imposterSourceElement = element;
@@ -85,8 +85,9 @@ class DocumentUtil {
if (imposter !== null) {
this._setImposterStyle(imposterContainer.style, 'z-index', '-2147483646');
this._setImposterStyle(imposter.style, 'pointer-events', 'none');
+ return TextSourceRange.createFromImposter(range, imposterContainer, imposterSourceElement);
}
- return new TextSourceRange(range, '', imposterContainer, imposterSourceElement);
+ return TextSourceRange.create(range);
} else {
if (imposterContainer !== null) {
imposterContainer.parentNode.removeChild(imposterContainer);
@@ -131,7 +132,7 @@ class DocumentUtil {
// Scan text
source = source.clone();
const startLength = source.setStartOffset(extent, layoutAwareScan);
- const endLength = source.setEndOffset(extent * 2 - startLength, layoutAwareScan, true);
+ const endLength = source.setEndOffset(extent * 2 - startLength, true, layoutAwareScan);
const text = source.text();
const textLength = text.length;
const textEndAnchor = textLength - endLength;
@@ -418,6 +419,58 @@ class DocumentUtil {
}
}
+ /**
+ * Offsets an array of DOMRects by a given amount.
+ * @param {DOMRect[]} rects The DOMRects to offset.
+ * @param {number} x The horizontal offset amount.
+ * @param {number} y The vertical offset amount.
+ * @returns {DOMRect} The DOMRects with the offset applied.
+ */
+ static offsetDOMRects(rects, x, y) {
+ const results = [];
+ for (const rect of rects) {
+ results.push(new DOMRect(rect.left + x, rect.top + y, rect.width, rect.height));
+ }
+ return results;
+ }
+
+ /**
+ * Gets the parent writing mode of an element.
+ * See: https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode.
+ * @param {Element} element The HTML element to check.
+ * @returns {string} The writing mode.
+ */
+ static getElementWritingMode(element) {
+ if (element !== null) {
+ const {writingMode} = getComputedStyle(element);
+ if (typeof writingMode === 'string') {
+ return this.normalizeWritingMode(writingMode);
+ }
+ }
+ return 'horizontal-tb';
+ }
+
+ /**
+ * Normalizes a CSS writing mode value by converting non-standard and deprecated values
+ * into their corresponding standard vaules.
+ * @param {string} writingMode The writing mode to normalize.
+ * @returns {string} The normalized writing mode.
+ */
+ static normalizeWritingMode(writingMode) {
+ switch (writingMode) {
+ case 'lr':
+ case 'lr-tb':
+ case 'rl':
+ return 'horizontal-tb';
+ case 'tb':
+ return 'vertical-lr';
+ case 'tb-rl':
+ return 'vertical-rl';
+ default:
+ return writingMode;
+ }
+ }
+
// Private
static _getActiveButtons(event, array) {
diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js
index b5fc1683..13c5cd86 100644
--- a/ext/js/dom/text-source-element.js
+++ b/ext/js/dom/text-source-element.js
@@ -21,9 +21,9 @@
*/
class TextSourceElement {
- constructor(element, fullContent=null, startOffset=0, endOffset=0) {
+ constructor(element, fullContent, startOffset, endOffset) {
this._element = element;
- this._fullContent = (typeof fullContent === 'string' ? fullContent : TextSourceElement.getElementContent(element));
+ this._fullContent = fullContent;
this._startOffset = startOffset;
this._endOffset = endOffset;
this._content = this._fullContent.substring(this._startOffset, this._endOffset);
@@ -49,10 +49,6 @@ class TextSourceElement {
return this._endOffset;
}
- get isConnected() {
- return this._element.isConnected;
- }
-
clone() {
return new TextSourceElement(this._element, this._fullContent, this._startOffset, this._endOffset);
}
@@ -65,7 +61,7 @@ class TextSourceElement {
return this._content;
}
- setEndOffset(length, _layoutAwareScan, fromEnd) {
+ setEndOffset(length, fromEnd) {
const offset = fromEnd ? this._endOffset : this._startOffset;
length = Math.min(this._fullContent.length - offset, length);
if (length > 0) {
@@ -86,19 +82,6 @@ class TextSourceElement {
return length;
}
- collapse(toStart) {
- if (toStart) {
- this._endOffset = this._startOffset;
- } else {
- this._startOffset = this._endOffset;
- }
- this._content = '';
- }
-
- getRect() {
- return DocumentUtil.convertRectZoomCoordinates(this._element.getBoundingClientRect(), this._element);
- }
-
getRects() {
return DocumentUtil.convertMultipleRectZoomCoordinates(this._element.getClientRects(), this._element);
}
@@ -130,7 +113,11 @@ class TextSourceElement {
return [this._element];
}
- static getElementContent(element) {
+ static create(element) {
+ return new TextSourceElement(element, this._getElementContent(element), 0, 0);
+ }
+
+ static _getElementContent(element) {
let content;
switch (element.nodeName.toUpperCase()) {
case 'BUTTON':
diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js
index 6c35c4cb..e03783a5 100644
--- a/ext/js/dom/text-source-range.js
+++ b/ext/js/dom/text-source-range.js
@@ -21,12 +21,14 @@
*/
class TextSourceRange {
- constructor(range, content, imposterContainer, imposterSourceElement) {
+ constructor(range, rangeStartOffset, content, imposterElement, imposterSourceElement, cachedRects, cachedSourceRect) {
this._range = range;
- this._rangeStartOffset = range.startOffset;
+ this._rangeStartOffset = rangeStartOffset;
this._content = content;
- this._imposterContainer = imposterContainer;
+ this._imposterElement = imposterElement;
this._imposterSourceElement = imposterSourceElement;
+ this._cachedRects = cachedRects;
+ this._cachedSourceRect = cachedSourceRect;
}
get type() {
@@ -45,20 +47,21 @@ class TextSourceRange {
return this._imposterSourceElement;
}
- get isConnected() {
- return (
- this._range.startContainer.isConnected &&
- this._range.endContainer.isConnected
- );
- }
-
clone() {
- return new TextSourceRange(this._range.cloneRange(), this._content, this._imposterContainer, this._imposterSourceElement);
+ return new TextSourceRange(
+ this._range.cloneRange(),
+ this._rangeStartOffset,
+ this._content,
+ this._imposterElement,
+ this._imposterSourceElement,
+ this._cachedRects,
+ this._cachedSourceRect
+ );
}
cleanup() {
- if (this._imposterContainer !== null && this._imposterContainer.parentNode !== null) {
- this._imposterContainer.parentNode.removeChild(this._imposterContainer);
+ if (this._imposterElement !== null && this._imposterElement.parentNode !== null) {
+ this._imposterElement.parentNode.removeChild(this._imposterElement);
}
}
@@ -66,12 +69,17 @@ class TextSourceRange {
return this._content;
}
- setEndOffset(length, layoutAwareScan, fromEnd) {
- const state = (
- fromEnd ?
- new DOMTextScanner(this._range.endContainer, this._range.endOffset, !layoutAwareScan, layoutAwareScan).seek(length) :
- new DOMTextScanner(this._range.startContainer, this._range.startOffset, !layoutAwareScan, layoutAwareScan).seek(length)
- );
+ setEndOffset(length, fromEnd, layoutAwareScan) {
+ let node;
+ let offset;
+ if (fromEnd) {
+ node = this._range.endContainer;
+ offset = this._range.endOffset;
+ } else {
+ node = this._range.startContainer;
+ offset = this._range.startOffset;
+ }
+ const state = new DOMTextScanner(node, offset, !layoutAwareScan, layoutAwareScan).seek(length);
this._range.setEnd(state.node, state.offset);
this._content = (fromEnd ? this._content + state.content : state.content);
return length - state.remainder;
@@ -85,30 +93,25 @@ class TextSourceRange {
return length - state.remainder;
}
- collapse(toStart) {
- this._range.collapse(toStart);
- this._content = '';
- }
-
- getRect() {
- return DocumentUtil.convertRectZoomCoordinates(this._range.getBoundingClientRect(), this._range.startContainer);
- }
-
getRects() {
+ if (this._isImposterDisconnected()) { return this._getCachedRects(); }
return DocumentUtil.convertMultipleRectZoomCoordinates(this._range.getClientRects(), this._range.startContainer);
}
getWritingMode() {
- return TextSourceRange.getElementWritingMode(TextSourceRange.getParentElement(this._range.startContainer));
+ const node = this._isImposterDisconnected() ? this._imposterSourceElement : this._range.startContainer;
+ return DocumentUtil.getElementWritingMode(node !== null ? node.parentElement : null);
}
select() {
+ if (this._imposterElement !== null) { return; }
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(this._range);
}
deselect() {
+ if (this._imposterElement !== null) { return; }
const selection = window.getSelection();
selection.removeAllRanges();
}
@@ -143,36 +146,26 @@ class TextSourceRange {
return DocumentUtil.getNodesInRange(this._range);
}
- static getParentElement(node) {
- while (node !== null && node.nodeType !== Node.ELEMENT_NODE) {
- node = node.parentNode;
- }
- return node;
+ static create(range) {
+ return new TextSourceRange(range, range.startOffset, range.toString(), null, null, null, null);
}
- static getElementWritingMode(element) {
- if (element !== null) {
- const style = window.getComputedStyle(element);
- const writingMode = style.writingMode;
- if (typeof writingMode === 'string') {
- return TextSourceRange.normalizeWritingMode(writingMode);
- }
- }
- return 'horizontal-tb';
- }
-
- static normalizeWritingMode(writingMode) {
- switch (writingMode) {
- case 'lr':
- case 'lr-tb':
- case 'rl':
- return 'horizontal-tb';
- case 'tb':
- return 'vertical-lr';
- case 'tb-rl':
- return 'vertical-rl';
- default:
- return writingMode;
- }
+ static createFromImposter(range, imposterElement, imposterSourceElement) {
+ const cachedRects = DocumentUtil.convertMultipleRectZoomCoordinates(range.getClientRects(), range.startContainer);
+ const cachedSourceRect = DocumentUtil.convertRectZoomCoordinates(imposterSourceElement.getBoundingClientRect(), imposterSourceElement);
+ return new TextSourceRange(range, range.startOffset, range.toString(), imposterElement, imposterSourceElement, cachedRects, cachedSourceRect);
+ }
+
+ _isImposterDisconnected() {
+ return this._imposterElement !== null && !this._imposterElement.isConnected;
+ }
+
+ _getCachedRects() {
+ const sourceRect = DocumentUtil.convertRectZoomCoordinates(this._imposterSourceElement.getBoundingClientRect(), this._imposterSourceElement);
+ return DocumentUtil.offsetDOMRects(
+ this._cachedRects,
+ sourceRect.left - this._cachedSourceRect.left,
+ sourceRect.top - this._cachedSourceRect.top
+ );
}
}
diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js
index a5b35a26..826c8c5b 100644
--- a/ext/js/language/text-scanner.js
+++ b/ext/js/language/text-scanner.js
@@ -229,7 +229,7 @@ class TextScanner extends EventDispatcher {
getTextSourceContent(textSource, length, layoutAwareScan) {
const clonedTextSource = textSource.clone();
- clonedTextSource.setEndOffset(length, layoutAwareScan, false);
+ clonedTextSource.setEndOffset(length, false, layoutAwareScan);
const includeSelector = this._includeSelector;
const excludeSelector = this._excludeSelector;
@@ -875,7 +875,7 @@ class TextScanner extends EventDispatcher {
const {dictionaryEntries, originalTextLength} = await yomichan.api.termsFind(searchText, details, optionsContext);
if (dictionaryEntries.length === 0) { return null; }
- textSource.setEndOffset(originalTextLength, layoutAwareScan, false);
+ textSource.setEndOffset(originalTextLength, false, layoutAwareScan);
const sentence = DocumentUtil.extractSentence(
textSource,
layoutAwareScan,
@@ -902,7 +902,7 @@ class TextScanner extends EventDispatcher {
const dictionaryEntries = await yomichan.api.kanjiFind(searchText, optionsContext);
if (dictionaryEntries.length === 0) { return null; }
- textSource.setEndOffset(1, layoutAwareScan, false);
+ textSource.setEndOffset(1, false, layoutAwareScan);
const sentence = DocumentUtil.extractSentence(
textSource,
layoutAwareScan,
@@ -1127,7 +1127,7 @@ class TextScanner extends EventDispatcher {
(excludeSelector !== null && DocumentUtil.anyNodeMatchesSelector(nodes, excludeSelector))
) {
--length;
- textSource.setEndOffset(length, layoutAwareScan, false);
+ textSource.setEndOffset(length, false, layoutAwareScan);
} else {
break;
}
diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js
index 68ec7de0..86d8633a 100644
--- a/ext/js/pages/settings/popup-preview-frame.js
+++ b/ext/js/pages/settings/popup-preview-frame.js
@@ -212,7 +212,7 @@ class PopupPreviewFrame {
const range = document.createRange();
range.selectNodeContents(textNode);
- const source = new TextSourceRange(range, range.toString(), null, null);
+ const source = TextSourceRange.create(range);
try {
await this._frontend.setTextSource(source);