aboutsummaryrefslogtreecommitdiff
path: root/ext/js
diff options
context:
space:
mode:
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);