Skip to content
This repository has been archived by the owner on Feb 25, 2023. It is now read-only.

TextSource* API updates #2236

Merged
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
4 changes: 2 additions & 2 deletions ext/js/accessibility/google-docs-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -110,7 +110,7 @@ class GoogleDocsUtil {
}
}
range.setStart(textNode, start);
range.setEnd(textNode, end);
range.setEnd(textNode, start);
return range;
}

Expand Down
2 changes: 1 addition & 1 deletion ext/js/app/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
59 changes: 56 additions & 3 deletions ext/js/dom/document-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
29 changes: 8 additions & 21 deletions ext/js/dom/text-source-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
Expand All @@ -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) {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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':
Expand Down
109 changes: 51 additions & 58 deletions ext/js/dom/text-source-range.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -45,33 +47,39 @@ 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);
}
}

text() {
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;
Expand All @@ -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();
}
Expand Down Expand Up @@ -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
);
}
}
8 changes: 4 additions & 4 deletions ext/js/language/text-scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion ext/js/pages/settings/popup-preview-frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down