diff options
Diffstat (limited to 'ext/js')
| -rw-r--r-- | ext/js/accessibility/google-docs-util.js | 4 | ||||
| -rw-r--r-- | ext/js/app/frontend.js | 2 | ||||
| -rw-r--r-- | ext/js/dom/document-util.js | 59 | ||||
| -rw-r--r-- | ext/js/dom/text-source-element.js | 29 | ||||
| -rw-r--r-- | ext/js/dom/text-source-range.js | 109 | ||||
| -rw-r--r-- | ext/js/language/text-scanner.js | 8 | ||||
| -rw-r--r-- | ext/js/pages/settings/popup-preview-frame.js | 2 | 
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); |