diff options
Diffstat (limited to 'ext/js')
| -rw-r--r-- | ext/js/app/frontend.js | 1 | ||||
| -rw-r--r-- | ext/js/app/popup.js | 58 | ||||
| -rw-r--r-- | ext/js/data/options-util.js | 2 | ||||
| -rw-r--r-- | ext/js/display/display.js | 2 | ||||
| -rw-r--r-- | ext/js/dom/document-util.js | 87 | ||||
| -rw-r--r-- | ext/js/dom/text-source-element.js | 5 | ||||
| -rw-r--r-- | ext/js/dom/text-source-range.js | 4 | ||||
| -rw-r--r-- | ext/js/language/text-scanner.js | 10 | 
8 files changed, 133 insertions, 36 deletions
| diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index f3b925c3..de3eb7fd 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -395,6 +395,7 @@ class Frontend {          this._textScanner.setOptions({              inputs: scanningOptions.inputs,              deepContentScan: scanningOptions.deepDomScan, +            normalizeCssZoom: scanningOptions.normalizeCssZoom,              selectText: scanningOptions.selectText,              delay: scanningOptions.delay,              touchInputEnabled: scanningOptions.touchInputEnabled, diff --git a/ext/js/app/popup.js b/ext/js/app/popup.js index 9ca2165d..8b62b92a 100644 --- a/ext/js/app/popup.js +++ b/ext/js/app/popup.js @@ -368,7 +368,7 @@ class Popup extends EventDispatcher {       *   `valid` is `false` for `PopupProxy`, since the DOM node is hosted in a different frame.       */      getFrameRect() { -        const {left, top, right, bottom} = this._frame.getBoundingClientRect(); +        const {left, top, right, bottom} = this._getFrameBoundingClientRect();          return {left, top, right, bottom, valid: true};      } @@ -377,7 +377,7 @@ class Popup extends EventDispatcher {       * @returns {Promise<{width: number, height: number, valid: boolean}>} The size and whether or not it is valid.       */      async getFrameSize() { -        const {width, height} = this._frame.getBoundingClientRect(); +        const {width, height} = this._getFrameBoundingClientRect();          return {width, height, valid: true};      } @@ -680,12 +680,13 @@ class Popup extends EventDispatcher {       * @returns {SizeRect} The calculated rectangle for where to position the popup.       */      _getPosition(sourceRects, writingMode, viewport) { -        const scale = this._contentScale; -        const scaleRatio = this._frameSizeContentScale === null ? 1.0 : scale / this._frameSizeContentScale; -        this._frameSizeContentScale = scale; +        sourceRects = this._convertSourceRectsCoordinateSpace(sourceRects); +        const contentScale = this._contentScale; +        const scaleRatio = this._frameSizeContentScale === null ? 1.0 : contentScale / this._frameSizeContentScale; +        this._frameSizeContentScale = contentScale;          const frameRect = this._frame.getBoundingClientRect(); -        const frameWidth = Math.max(frameRect.width * scaleRatio, this._initialWidth * scale); -        const frameHeight = Math.max(frameRect.height * scaleRatio, this._initialHeight * scale); +        const frameWidth = Math.max(frameRect.width * scaleRatio, this._initialWidth * contentScale); +        const frameHeight = Math.max(frameRect.height * scaleRatio, this._initialHeight * contentScale);          const horizontal = (writingMode === 'horizontal-tb' || this._verticalTextPosition === 'default');          let preferAfter; @@ -700,8 +701,8 @@ class Popup extends EventDispatcher {              horizontalOffset = this._horizontalOffset2;              verticalOffset = this._verticalOffset2;          } -        horizontalOffset *= scale; -        verticalOffset *= scale; +        horizontalOffset *= contentScale; +        verticalOffset *= contentScale;          let best = null;          const sourceRectsLength = sourceRects.length; @@ -955,4 +956,43 @@ class Popup extends EventDispatcher {          }          return false;      } + +    /** +     * Gets the bounding client rect for the frame element, with a coordinate conversion applied. +     * @returns {DOMRect} The rectangle of the frame. +     */ +    _getFrameBoundingClientRect() { +        return DocumentUtil.convertRectZoomCoordinates(this._frame.getBoundingClientRect(), this._container); +    } + +    /** +     * Converts the coordinate space of source rectangles. +     * @param {Rect[]} sourceRects The list of rectangles to convert. +     * @returns {Rect[]} Either an updated list of rectangles, or `sourceRects` if no change is required. +     */ +    _convertSourceRectsCoordinateSpace(sourceRects) { +        let scale = DocumentUtil.computeZoomScale(this._container); +        if (scale === 1) { return sourceRects; } +        scale = 1 / scale; +        const sourceRects2 = []; +        for (const rect of sourceRects) { +            sourceRects2.push(this._createScaledRect(rect, scale)); +        } +        return sourceRects2; +    } + +    /** +     * Creates a scaled rectangle. +     * @param {Rect} rect The rectangle to scale. +     * @param {number} scale The scale factor. +     * @returns {Rect} A new rectangle which has been scaled. +     */ +    _createScaledRect(rect, scale) { +        return { +            left: rect.left * scale, +            top: rect.top * scale, +            right: rect.right * scale, +            bottom: rect.bottom * scale +        }; +    }  } diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index d2835adb..442007d0 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -980,10 +980,12 @@ class OptionsUtil {      _updateVersion20(options) {          // Version 20 changes:          //  Added anki.downloadTimeout. +        //  Added scanning.normalizeCssZoom.          //  Fixed general.popupTheme invalid default.          //  Fixed general.popupOuterTheme invalid default.          for (const profile of options.profiles) {              profile.options.anki.downloadTimeout = 0; +            profile.options.scanning.normalizeCssZoom = true;              const {general} = profile.options;              if (general.popupTheme === 'default') {                  general.popupTheme = 'light'; diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 8905d304..d286bd5e 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -367,6 +367,7 @@ class Display extends EventDispatcher {              scanning: {                  inputs: scanningOptions.inputs,                  deepContentScan: scanningOptions.deepDomScan, +                normalizeCssZoom: scanningOptions.normalizeCssZoom,                  selectText: scanningOptions.selectText,                  delay: scanningOptions.delay,                  touchInputEnabled: scanningOptions.touchInputEnabled, @@ -1532,6 +1533,7 @@ class Display extends EventDispatcher {                  }              }],              deepContentScan: scanningOptions.deepDomScan, +            normalizeCssZoom: scanningOptions.normalizeCssZoom,              selectText: false,              delay: scanningOptions.delay,              touchInputEnabled: false, diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index b974387e..41f44afe 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -24,10 +24,9 @@  class DocumentUtil {      constructor() {          this._transparentColorPattern = /rgba\s*\([^)]*,\s*0(?:\.0+)?\s*\)/; -        this._cssZoomSupported = (typeof document.createElement('div').style.zoom === 'string');      } -    getRangeFromPoint(x, y, deepContentScan) { +    getRangeFromPoint(x, y, {deepContentScan, normalizeCssZoom}) {          const elements = this._getElementsFromPoint(x, y, deepContentScan);          let imposter = null;          let imposterContainer = null; @@ -52,7 +51,7 @@ class DocumentUtil {              }          } -        const range = this._caretRangeFromPointExt(x, y, deepContentScan ? elements : []); +        const range = this._caretRangeFromPointExt(x, y, deepContentScan ? elements : [], normalizeCssZoom);          if (range !== null) {              if (imposter !== null) {                  this._setImposterStyle(imposterContainer.style, 'z-index', '-2147483646'); @@ -175,6 +174,60 @@ class DocumentUtil {          };      } +    /** +     * Computes the scaling adjustment that is necessary for client space coordinates based on the +     * CSS zoom level. +     * @param {Node} node A node in the document. +     * @returns {number} The scaling factor. +     */ +    static computeZoomScale(node) { +        if (this._cssZoomSupported === null) { +            this._cssZoomSupported = (typeof document.createElement('div').style.zoom === 'string'); +        } +        if (!this._cssZoomSupported) { return 1; } +        // documentElement must be excluded because the computer style of its zoom property is inconsistent. +        // * If CSS `:root{zoom:X;}` is specified, the computed zoom will always report `X`. +        // * If CSS `:root{zoom:X;}` is not specified, the computed zoom report the browser's zoom level. +        // Therefor, if CSS root zoom is specified as a value other than 1, the adjusted {x, y} values +        // would be incorrect, which is not new behaviour. +        let scale = 1; +        const {ELEMENT_NODE, DOCUMENT_FRAGMENT_NODE} = Node; +        const {documentElement} = document; +        for (; node !== null && node !== documentElement; node = node.parentNode) { +            const {nodeType} = node; +            if (nodeType === DOCUMENT_FRAGMENT_NODE) { +                const {host} = node; +                if (typeof host !== 'undefined') { +                    node = host; +                } +                continue; +            } else if (nodeType !== ELEMENT_NODE) { +                continue; +            } +            let {zoom} = getComputedStyle(node); +            if (typeof zoom !== 'string') { continue; } +            zoom = Number.parseFloat(zoom); +            if (!Number.isFinite(zoom) || zoom === 0) { continue; } +            scale *= zoom; +        } +        return scale; +    } + +    static convertRectZoomCoordinates(rect, node) { +        const scale = this.computeZoomScale(node); +        return (scale === 1 ? rect : new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale)); +    } + +    static convertMultipleRectZoomCoordinates(rects, node) { +        const scale = this.computeZoomScale(node); +        if (scale === 1) { return rects; } +        const results = []; +        for (const rect of rects) { +            results.push(new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale)); +        } +        return results; +    } +      static isPointInRect(x, y, rect) {          return (              x >= rect.left && x < rect.right && @@ -435,7 +488,7 @@ class DocumentUtil {          return e !== null ? [e] : [];      } -    _isPointInRange(x, y, range) { +    _isPointInRange(x, y, range, normalizeCssZoom) {          // Require a text node to start          const {startContainer} = range;          if (startContainer.nodeType !== Node.TEXT_NODE) { @@ -443,8 +496,10 @@ class DocumentUtil {          }          // Convert CSS zoom coordinates -        if (this._cssZoomSupported) { -            ({x, y} = this._convertCssZoomCoordinates(x, y, startContainer)); +        if (normalizeCssZoom) { +            const scale = DocumentUtil.computeZoomScale(startContainer); +            x /= scale; +            y /= scale;          }          // Scan forward @@ -583,7 +638,7 @@ class DocumentUtil {          }      } -    _caretRangeFromPointExt(x, y, elements) { +    _caretRangeFromPointExt(x, y, elements, normalizeCssZoom) {          let previousStyles = null;          try {              let i = 0; @@ -596,7 +651,7 @@ class DocumentUtil {                  const startContainer = range.startContainer;                  if (startContinerPre !== startContainer) { -                    if (this._isPointInRange(x, y, range)) { +                    if (this._isPointInRange(x, y, range, normalizeCssZoom)) {                          return range;                      }                      startContinerPre = startContainer; @@ -668,18 +723,6 @@ class DocumentUtil {      _isElementUserSelectAll(element) {          return getComputedStyle(element).userSelect === 'all';      } - -    _convertCssZoomCoordinates(x, y, node) { -        const ELEMENT_NODE = Node.ELEMENT_NODE; -        for (; node !== null; node = node.parentNode) { -            if (node.nodeType !== ELEMENT_NODE) { continue; } -            let {zoom} = getComputedStyle(node); -            if (typeof zoom !== 'string') { continue; } -            zoom = Number.parseFloat(zoom); -            if (!Number.isFinite(zoom) || zoom === 0) { continue; } -            x /= zoom; -            y /= zoom; -        } -        return {x, y}; -    }  } +// eslint-disable-next-line no-underscore-dangle +DocumentUtil._cssZoomSupported = null; diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js index fe3fe083..b5fc1683 100644 --- a/ext/js/dom/text-source-element.js +++ b/ext/js/dom/text-source-element.js @@ -16,6 +16,7 @@   */  /* global + * DocumentUtil   * StringUtil   */ @@ -95,11 +96,11 @@ class TextSourceElement {      }      getRect() { -        return this._element.getBoundingClientRect(); +        return DocumentUtil.convertRectZoomCoordinates(this._element.getBoundingClientRect(), this._element);      }      getRects() { -        return this._element.getClientRects(); +        return DocumentUtil.convertMultipleRectZoomCoordinates(this._element.getClientRects(), this._element);      }      getWritingMode() { diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index 5e3e814c..6c35c4cb 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -91,11 +91,11 @@ class TextSourceRange {      }      getRect() { -        return this._range.getBoundingClientRect(); +        return DocumentUtil.convertRectZoomCoordinates(this._range.getBoundingClientRect(), this._range.startContainer);      }      getRects() { -        return this._range.getClientRects(); +        return DocumentUtil.convertMultipleRectZoomCoordinates(this._range.getClientRects(), this._range.startContainer);      }      getWritingMode() { diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index 93de4dd4..3b8a8b47 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -54,6 +54,7 @@ class TextScanner extends EventDispatcher {          this._selectionRestoreInfo = null;          this._deepContentScan = false; +        this._normalizeCssZoom = true;          this._selectText = false;          this._delay = 0;          this._touchInputEnabled = false; @@ -151,6 +152,7 @@ class TextScanner extends EventDispatcher {      setOptions({          inputs,          deepContentScan, +        normalizeCssZoom,          selectText,          delay,          touchInputEnabled, @@ -167,6 +169,9 @@ class TextScanner extends EventDispatcher {          if (typeof deepContentScan === 'boolean') {              this._deepContentScan = deepContentScan;          } +        if (typeof normalizeCssZoom === 'boolean') { +            this._normalizeCssZoom = normalizeCssZoom; +        }          if (typeof selectText === 'boolean') {              this._selectText = selectText;          } @@ -932,7 +937,10 @@ class TextScanner extends EventDispatcher {                  return;              } -            const textSource = this._documentUtil.getRangeFromPoint(x, y, this._deepContentScan); +            const textSource = this._documentUtil.getRangeFromPoint(x, y, { +                deepContentScan: this._deepContentScan, +                normalizeCssZoom: this._normalizeCssZoom +            });              try {                  await this._search(textSource, searchTerms, searchKanji, inputInfo);              } finally { |