diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2020-09-20 15:10:57 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-09-20 15:10:57 -0400 | 
| commit | d395a2a6bfe33feef467f0e0886a089afccd8438 (patch) | |
| tree | 5aebeeb3d130960caac29061ffd9dca285f711aa | |
| parent | 29890f7625e7a9d0760acb163a9d7181e3d0f08f (diff) | |
ClipboardReader class (#854)
* Create ClipboardReader class
* Use ClipboardReader in Backend
* Update ClipboardMonitor to use ClipboardReader
* Replace _onApiClipboardImageGet call
* Assign clipboard reader browser
| -rw-r--r-- | ext/bg/background.html | 1 | ||||
| -rw-r--r-- | ext/bg/js/backend.js | 90 | ||||
| -rw-r--r-- | ext/bg/js/clipboard-monitor.js | 10 | ||||
| -rw-r--r-- | ext/bg/js/clipboard-reader.js | 148 | ||||
| -rw-r--r-- | ext/bg/js/search.js | 4 | 
5 files changed, 171 insertions, 82 deletions
| diff --git a/ext/bg/background.html b/ext/bg/background.html index a3bf44e7..6750ea69 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -30,6 +30,7 @@          <script src="/bg/js/anki.js"></script>          <script src="/bg/js/audio-uri-builder.js"></script>          <script src="/bg/js/clipboard-monitor.js"></script> +        <script src="/bg/js/clipboard-reader.js"></script>          <script src="/bg/js/database.js"></script>          <script src="/bg/js/deinflector.js"></script>          <script src="/bg/js/dictionary-database.js"></script> diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index f656adbd..91188fdc 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -20,6 +20,7 @@   * AudioSystem   * AudioUriBuilder   * ClipboardMonitor + * ClipboardReader   * DictionaryDatabase   * Environment   * JsonSchemaValidator @@ -39,7 +40,14 @@ class Backend {          this._translator = new Translator(this._dictionaryDatabase);          this._anki = new AnkiConnect();          this._mecab = new Mecab(); -        this._clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)}); +        this._clipboardReader = new ClipboardReader({ +            document: (typeof document === 'object' && document !== null ? document : null), +            pasteTargetSelector: '#clipboard-paste-target', +            imagePasteTargetSelector: '#clipboard-image-paste-target' +        }); +        this._clipboardMonitor = new ClipboardMonitor({ +            clipboardReader: this._clipboardReader +        });          this._options = null;          this._profileConditionsSchemaValidator = new JsonSchemaValidator();          this._profileConditionsSchemaCache = []; @@ -56,11 +64,6 @@ class Backend {          });          this._optionsUtil = new OptionsUtil(); -        this._clipboardPasteTarget = null; -        this._clipboardPasteTargetInitialized = false; -        this._clipboardImagePasteTarget = null; -        this._clipboardImagePasteTargetInitialized = false; -          this._searchPopupTabId = null;          this._searchPopupTabCreatePromise = null; @@ -179,6 +182,8 @@ class Backend {              yomichan.on('log', this._onLog.bind(this));              await this._environment.prepare(); +            this._clipboardReader.browser = this._environment.getInfo().browser; +              try {                  await this._dictionaryDatabase.prepare();              } catch (e) { @@ -577,78 +582,11 @@ class Backend {      }      async _onApiClipboardGet() { -        /* -        Notes: -            document.execCommand('paste') doesn't work on Firefox. -            This may be a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1603985 -            Therefore, navigator.clipboard.readText() is used on Firefox. - -            navigator.clipboard.readText() can't be used in Chrome for two reasons: -            * Requires page to be focused, else it rejects with an exception. -            * When the page is focused, Chrome will request clipboard permission, despite already -              being an extension with clipboard permissions. It effectively asks for the -              non-extension permission for clipboard access. -        */ -        const {browser} = this._environment.getInfo(); -        if (browser === 'firefox' || browser === 'firefox-mobile') { -            return await navigator.clipboard.readText(); -        } - -        if (!this._environmentHasDocument()) { -            throw new Error('Reading the clipboard is not supported in this context'); -        } - -        if (!this._clipboardPasteTargetInitialized) { -            this._clipboardPasteTarget = document.querySelector('#clipboard-paste-target'); -            this._clipboardPasteTargetInitialized = true; -        } - -        const target = this._clipboardPasteTarget; -        if (target === null) { -            throw new Error('Clipboard paste target does not exist'); -        } - -        target.value = ''; -        target.focus(); -        this._executePasteCommand(); -        const result = target.value; -        target.value = ''; -        return result; +        return this._clipboardReader.getText();      }      async _onApiClipboardImageGet() { -        // See browser-specific notes in _onApiClipboardGet -        const {browser} = this._environment.getInfo(); -        if (browser === 'firefox' || browser === 'firefox-mobile') { -            if (typeof navigator.clipboard !== 'undefined' && typeof navigator.clipboard.read === 'function') { -                // This function is behind the flag: dom.events.asyncClipboard.dataTransfer -                const {files} = await navigator.clipboard.read(); -                if (files.length === 0) { return null; } -                const result = await this._readFileAsDataURL(files[0]); -                return result; -            } -        } - -        if (!this._environmentHasDocument()) { -            throw new Error('Reading the clipboard is not supported in this context'); -        } - -        if (!this._clipboardImagePasteTargetInitialized) { -            this._clipboardImagePasteTarget = document.querySelector('#clipboard-image-paste-target'); -            this._clipboardImagePasteTargetInitialized = true; -        } - -        const target = this._clipboardImagePasteTarget; -        if (target === null) { -            throw new Error('Clipboard paste target does not exist'); -        } - -        target.focus(); -        this._executePasteCommand(); -        const image = target.querySelector('img[src^="data:"]'); -        const result = (image !== null ? image.getAttribute('src') : null); -        target.textContent = ''; -        return result; +        return this._clipboardReader.getImage();      }      async _onApiGetDisplayTemplatesHtml() { @@ -1655,7 +1593,7 @@ class Backend {          try {              const now = new Date(timestamp); -            const dataUrl = await this._onApiClipboardImageGet(); +            const dataUrl = await this._clipboardReader.getImage();              if (dataUrl === null) {                  throw new Error('No clipboard image');              } diff --git a/ext/bg/js/clipboard-monitor.js b/ext/bg/js/clipboard-monitor.js index e7e7378c..4d980e11 100644 --- a/ext/bg/js/clipboard-monitor.js +++ b/ext/bg/js/clipboard-monitor.js @@ -20,28 +20,28 @@   */  class ClipboardMonitor extends EventDispatcher { -    constructor({getClipboard}) { +    constructor({clipboardReader}) {          super();          this._timerId = null;          this._timerToken = null;          this._interval = 250;          this._previousText = null; -        this._getClipboard = getClipboard; +        this._clipboardReader = clipboardReader;      }      start() {          this.stop();          // The token below is used as a unique identifier to ensure that a new clipboard monitor -        // hasn't been started during the await call. The check below the await this._getClipboard() -        // call will exit early if the reference has changed. +        // hasn't been started during the await call. The check below the await call +        // will exit early if the reference has changed.          const token = {};          const intervalCallback = async () => {              this._timerId = null;              let text = null;              try { -                text = await this._getClipboard(); +                text = await this._clipboardReader.getText();              } catch (e) {                  // NOP              } diff --git a/ext/bg/js/clipboard-reader.js b/ext/bg/js/clipboard-reader.js new file mode 100644 index 00000000..66cf0c25 --- /dev/null +++ b/ext/bg/js/clipboard-reader.js @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2020  Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +/** + * Class which can read text and images from the clipboard. + */ +class ClipboardReader { +    /** +     * Creates a new instances of a clipboard reader. +     * @param document The Document object to be used, or null for no support. +     * @param pasteTargetSelector The selector for the paste target element. +     * @param imagePasteTargetSelector The selector for the image paste target element. +     */ +    constructor({document=null, pasteTargetSelector=null, imagePasteTargetSelector=null}) { +        this._document = document; +        this._browser = null; +        this._pasteTarget = null; +        this._pasteTargetSelector = pasteTargetSelector; +        this._imagePasteTarget = null; +        this._imagePasteTargetSelector = imagePasteTargetSelector; +    } + +    /** +     * Gets the browser being used. +     */ +    get browser() { +        return this._browser; +    } + +    /** +     * Assigns the browser being used. +     */ +    set browser(value) { +        this._browser = value; +    } + +    /** +     * Gets the text in the clipboard. +     * @returns A string containing the clipboard text. +     * @throws Error if not supported. +     */ +    async getText() { +        /* +        Notes: +            document.execCommand('paste') doesn't work on Firefox. +            This may be a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1603985 +            Therefore, navigator.clipboard.readText() is used on Firefox. + +            navigator.clipboard.readText() can't be used in Chrome for two reasons: +            * Requires page to be focused, else it rejects with an exception. +            * When the page is focused, Chrome will request clipboard permission, despite already +              being an extension with clipboard permissions. It effectively asks for the +              non-extension permission for clipboard access. +        */ +        if (this._isFirefox()) { +            return await navigator.clipboard.readText(); +        } + +        const document = this._document; +        if (document === null) { +            throw new Error('Not supported'); +        } + +        let target = this._pasteTarget; +        if (target === null) { +            target = document.querySelector(this._pasteTargetSelector); +            if (target === null) { +                throw new Error('Clipboard paste target does not exist'); +            } +            this._pasteTarget = target; +        } + +        target.value = ''; +        target.focus(); +        document.execCommand('paste'); +        const result = target.value; +        target.value = ''; +        return (typeof result === 'string' ? result : ''); +    } + +    /** +     * Gets the first image in the clipboard. +     * @returns A string containing a data URL of the image file, or null if no image was found. +     * @throws Error if not supported. +     */ +    async getImage() { +        // See browser-specific notes in getText +        if (this._isFirefox()) { +            if (typeof navigator.clipboard !== 'undefined' && typeof navigator.clipboard.read === 'function') { +                // This function is behind the flag: dom.events.asyncClipboard.dataTransfer +                const {files} = await navigator.clipboard.read(); +                if (files.length === 0) { return null; } +                const result = await this._readFileAsDataURL(files[0]); +                return result; +            } +        } + +        const document = this._document; +        if (document === null) { +            throw new Error('Not supported'); +        } + +        let target = this._imagePasteTarget; +        if (target === null) { +            target = document.querySelector(this._imagePasteTargetSelector); +            if (target === null) { +                throw new Error('Clipboard paste target does not exist'); +            } +            this._imagePasteTarget = target; +        } + +        target.focus(); +        document.execCommand('paste'); +        const image = target.querySelector('img[src^="data:"]'); +        const result = (image !== null ? image.getAttribute('src') : null); +        target.textContent = ''; +        return result; +    } + +    // Private + +    _isFirefox() { +        return (this._browser === 'firefox' || this._browser === 'firefox-mobile'); +    } + +    _readFileAsDataURL(file) { +        return new Promise((resolve, reject) => { +            const reader = new FileReader(); +            reader.onload = () => resolve(reader.result); +            reader.onerror = () => reject(reader.error); +            reader.readAsDataURL(file); +        }); +    } +} diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 2aa6d249..f5b4a671 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -36,7 +36,9 @@ class DisplaySearch extends Display {          this._introAnimationTimer = null;          this._clipboardMonitorEnabled = false;          this._clipboardMonitor = new ClipboardMonitor({ -            getClipboard: api.clipboardGet.bind(api) +            clipboardReader: { +                getText: async () => (await api.clipboardGet()) +            }          });          this._onKeyDownIgnoreKeys = new Map([              ['ANY_MOD', new Set([ |