From 9ef7f9d383561831ab1556f2679593235053a08e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Oct 2022 22:43:12 -0400 Subject: [PATCH] Clipboard updates (#2254) * Rename * Rename vars * Refactor paste target * Prevent most CSS url() properties from loading * Add helper function to clear rich function * Add useRichText argument * Update condition for using readText * Fix indent * Update CSS --- ext/background.html | 3 +- ext/css/background.css | 29 +++++++++++ ext/js/background/backend.js | 6 +-- ext/js/comm/clipboard-monitor.js | 2 +- ext/js/comm/clipboard-reader.js | 83 +++++++++++++++++++------------- 5 files changed, 84 insertions(+), 39 deletions(-) create mode 100644 ext/css/background.css diff --git a/ext/background.html b/ext/background.html index b3f85240..71990295 100644 --- a/ext/background.html +++ b/ext/background.html @@ -11,6 +11,7 @@ + @@ -61,7 +62,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1603985 --> -
+
diff --git a/ext/css/background.css b/ext/css/background.css new file mode 100644 index 00000000..b2aca1d6 --- /dev/null +++ b/ext/css/background.css @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 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 . + */ + +/* stylelint-disable declaration-no-important */ +#clipboard-rich-content-paste-target * { + background-image: none !important; + list-style-image: none !important; + content: none !important; + cursor: auto !important; + border-image-source: none !important; + offset-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; +} +/* stylelint-enable declaration-no-important */ diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 197734b1..2154b32a 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -53,7 +53,7 @@ class Backend { // eslint-disable-next-line no-undef document: (typeof document === 'object' && document !== null ? document : null), pasteTargetSelector: '#clipboard-paste-target', - imagePasteTargetSelector: '#clipboard-image-paste-target' + richContentPasteTargetSelector: '#clipboard-rich-content-paste-target' }); this._clipboardMonitor = new ClipboardMonitor({ japaneseUtil: this._japaneseUtil, @@ -596,7 +596,7 @@ class Backend { } async _onApiClipboardGet() { - return this._clipboardReader.getText(); + return this._clipboardReader.getText(false); } async _onApiGetDisplayTemplatesHtml() { @@ -1773,7 +1773,7 @@ class Backend { try { if (clipboardDetails !== null && clipboardDetails.text) { - clipboardText = await this._clipboardReader.getText(); + clipboardText = await this._clipboardReader.getText(false); } } catch (e) { errors.push(serializeError(e)); diff --git a/ext/js/comm/clipboard-monitor.js b/ext/js/comm/clipboard-monitor.js index 7a678461..f5be04e7 100644 --- a/ext/js/comm/clipboard-monitor.js +++ b/ext/js/comm/clipboard-monitor.js @@ -39,7 +39,7 @@ class ClipboardMonitor extends EventDispatcher { let text = null; try { - text = await this._clipboardReader.getText(); + text = await this._clipboardReader.getText(false); } catch (e) { // NOP } diff --git a/ext/js/comm/clipboard-reader.js b/ext/js/comm/clipboard-reader.js index aaf4dcd8..9e9a6116 100644 --- a/ext/js/comm/clipboard-reader.js +++ b/ext/js/comm/clipboard-reader.js @@ -28,15 +28,15 @@ class ClipboardReader { * @param {object} details Details about how to set up the instance. * @param {?Document} details.document The Document object to be used, or null for no support. * @param {?string} details.pasteTargetSelector The selector for the paste target element. - * @param {?string} details.imagePasteTargetSelector The selector for the image paste target element. + * @param {?string} details.richContentPasteTargetSelector The selector for the rich content paste target element. */ - constructor({document=null, pasteTargetSelector=null, imagePasteTargetSelector=null}) { + constructor({document=null, pasteTargetSelector=null, richContentPasteTargetSelector=null}) { this._document = document; this._browser = null; this._pasteTarget = null; this._pasteTargetSelector = pasteTargetSelector; - this._imagePasteTarget = null; - this._imagePasteTargetSelector = imagePasteTargetSelector; + this._richContentPasteTarget = null; + this._richContentPasteTargetSelector = richContentPasteTargetSelector; } /** @@ -56,13 +56,14 @@ class ClipboardReader { /** * Gets the text in the clipboard. + * @param {boolean} useRichText Whether or not to use rich text for pasting, when possible. * @returns {string} A string containing the clipboard text. * @throws {Error} Error if not supported. */ - async getText() { + async getText(useRichText) { /* Notes: - document.execCommand('paste') doesn't work on Firefox. + document.execCommand('paste') sometimes doesn't work on Firefox. See: https://bugzilla.mozilla.org/show_bug.cgi?id=1603985 Therefore, navigator.clipboard.readText() is used on Firefox. @@ -72,7 +73,7 @@ class ClipboardReader { being an extension with clipboard permissions. It effectively asks for the non-extension permission for clipboard access. */ - if (this._isFirefox()) { + if (this._isFirefox() && !useRichText) { try { return await navigator.clipboard.readText(); } catch (e) { @@ -86,21 +87,22 @@ class ClipboardReader { throw new Error('Clipboard reading not supported in this context'); } - 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; + if (useRichText) { + const target = this._getRichContentPasteTarget(); + target.focus(); + document.execCommand('paste'); + const result = target.textContent; + this._clearRichContent(target); + return result; + } else { + const target = this._getPasteTarget(); + target.value = ''; + target.focus(); + document.execCommand('paste'); + const result = target.value; + target.value = ''; + return (typeof result === 'string' ? result : ''); } - - target.value = ''; - target.focus(); - document.execCommand('paste'); - const result = target.value; - target.value = ''; - return (typeof result === 'string' ? result : ''); } /** @@ -143,23 +145,12 @@ class ClipboardReader { throw new Error('Clipboard reading not supported in this context'); } - 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; - } - + const target = this._getRichContentPasteTarget(); target.focus(); document.execCommand('paste'); const image = target.querySelector('img[src^="data:"]'); const result = (image !== null ? image.getAttribute('src') : null); - for (const image2 of target.querySelectorAll('img')) { - image2.removeAttribute('src'); - } - target.textContent = ''; + this._clearRichContent(target); return result; } @@ -177,4 +168,28 @@ class ClipboardReader { reader.readAsDataURL(file); }); } + + _getPasteTarget() { + if (this._pasteTarget === null) { this._pasteTarget = this._findPasteTarget(this._pasteTargetSelector); } + return this._pasteTarget; + } + + _getRichContentPasteTarget() { + if (this._richContentPasteTarget === null) { this._richContentPasteTarget = this._findPasteTarget(this._richContentPasteTargetSelector); } + return this._richContentPasteTarget; + } + + _findPasteTarget(selector) { + const target = this._document.querySelector(selector); + if (target === null) { throw new Error('Clipboard paste target does not exist'); } + return target; + } + + _clearRichContent(element) { + for (const image of element.querySelectorAll('img')) { + image.removeAttribute('src'); + image.removeAttribute('srcset'); + } + element.textContent = ''; + } }