diff --git a/ext/bg/background.html b/ext/bg/background.html index ba8c3863..3b889fb8 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -46,5 +46,12 @@ + + +
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index a9ef4ce4..047044df 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -64,11 +64,10 @@ class Backend { }); this._templateRenderer = new TemplateRenderer(); - this._clipboardPasteTarget = ( - typeof document === 'object' && document !== null ? - document.querySelector('#clipboard-paste-target') : - null - ); + this._clipboardPasteTarget = null; + this._clipboardPasteTargetInitialized = false; + this._clipboardImagePasteTarget = null; + this._clipboardImagePasteTargetInitialized = false; this._searchPopupTabId = null; this._searchPopupTabCreatePromise = null; @@ -108,6 +107,7 @@ class Backend { ['getStylesheetContent', {async: true, contentScript: true, handler: this._onApiGetStylesheetContent.bind(this)}], ['getEnvironmentInfo', {async: false, contentScript: true, handler: this._onApiGetEnvironmentInfo.bind(this)}], ['clipboardGet', {async: true, contentScript: true, handler: this._onApiClipboardGet.bind(this)}], + ['clipboardGetImage', {async: true, contentScript: true, handler: this._onApiClipboardImageGet.bind(this)}], ['getDisplayTemplatesHtml', {async: true, contentScript: true, handler: this._onApiGetDisplayTemplatesHtml.bind(this)}], ['getQueryParserTemplatesHtml', {async: true, contentScript: true, handler: this._onApiGetQueryParserTemplatesHtml.bind(this)}], ['getZoom', {async: true, contentScript: true, handler: this._onApiGetZoom.bind(this)}], @@ -645,18 +645,63 @@ class Backend { const {browser} = this._environment.getInfo(); if (browser === 'firefox' || browser === 'firefox-mobile') { return await navigator.clipboard.readText(); - } else { - const clipboardPasteTarget = this._clipboardPasteTarget; - if (clipboardPasteTarget === null) { - throw new Error('Reading the clipboard is not supported in this context'); - } - clipboardPasteTarget.value = ''; - clipboardPasteTarget.focus(); - document.execCommand('paste'); - const result = clipboardPasteTarget.value; - clipboardPasteTarget.value = ''; - return result; } + + 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; + } + + 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; } async _onApiGetDisplayTemplatesHtml() { @@ -1539,4 +1584,21 @@ class Backend { const isValidTab = urlPredicate(url); return isValidTab ? tab : null; } + + _environmentHasDocument() { + return (typeof document === 'object' && document !== null); + } + + _executePasteCommand() { + document.execCommand('paste'); + } + + _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/mixed/js/api.js b/ext/mixed/js/api.js index a6ac227e..63b3a3c0 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -133,6 +133,10 @@ const api = (() => { return this._invoke('clipboardGet'); } + clipboardGetImage() { + return this._invoke('clipboardGetImage'); + } + getDisplayTemplatesHtml() { return this._invoke('getDisplayTemplatesHtml'); }