diff options
| -rw-r--r-- | ext/bg/background.html | 1 | ||||
| -rw-r--r-- | ext/bg/js/backend.js | 19 | ||||
| -rw-r--r-- | ext/bg/js/clipboard-reader.js | 23 | ||||
| -rw-r--r-- | ext/bg/js/dictionary-importer.js | 30 | ||||
| -rw-r--r-- | ext/bg/js/media-utility.js | 63 | 
5 files changed, 84 insertions, 52 deletions
| diff --git a/ext/bg/background.html b/ext/bg/background.html index ba0710e6..f42a411d 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -35,6 +35,7 @@          <script src="/bg/js/dictionary-database.js"></script>          <script src="/bg/js/json-schema.js"></script>          <script src="/bg/js/mecab.js"></script> +        <script src="/bg/js/media-utility.js"></script>          <script src="/bg/js/options.js"></script>          <script src="/bg/js/profile-conditions.js"></script>          <script src="/bg/js/request-builder.js"></script> diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index b9d85b84..2a90e8e1 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -24,6 +24,7 @@   * Environment   * JsonSchemaValidator   * Mecab + * MediaUtility   * ObjectPropertyAccessor   * OptionsUtil   * ProfileConditions @@ -39,10 +40,12 @@ class Backend {          this._translator = new Translator(this._dictionaryDatabase);          this._anki = new AnkiConnect();          this._mecab = new Mecab(); +        this._mediaUtility = new MediaUtility();          this._clipboardReader = new ClipboardReader({              document: (typeof document === 'object' && document !== null ? document : null),              pasteTargetSelector: '#clipboard-paste-target', -            imagePasteTargetSelector: '#clipboard-image-paste-target' +            imagePasteTargetSelector: '#clipboard-image-paste-target', +            mediaUtility: this._mediaUtility          });          this._clipboardMonitor = new ClipboardMonitor({              clipboardReader: this._clipboardReader @@ -1570,7 +1573,8 @@ class Backend {              const dataUrl = await this._getScreenshot(windowId, tabId, ownerFrameId, format, quality);              const {mediaType, data} = this._getDataUrlInfo(dataUrl); -            const extension = this._getImageExtensionFromMediaType(mediaType); +            const extension = this._mediaUtility.getFileExtensionFromImageMediaType(mediaType); +            if (extension === null) { throw new Error('Unknown image media type'); }              let fileName = `yomichan_browser_screenshot_${reading}_${this._ankNoteDateToString(now)}.${extension}`;              fileName = this._replaceInvalidFileNameCharacters(fileName); @@ -1593,7 +1597,8 @@ class Backend {              }              const {mediaType, data} = this._getDataUrlInfo(dataUrl); -            const extension = this._getImageExtensionFromMediaType(mediaType); +            const extension = this._mediaUtility.getFileExtensionFromImageMediaType(mediaType); +            if (extension === null) { throw new Error('Unknown image media type'); }              let fileName = `yomichan_clipboard_image_${reading}_${this._ankNoteDateToString(now)}.${extension}`;              fileName = this._replaceInvalidFileNameCharacters(fileName); @@ -1636,14 +1641,6 @@ class Backend {          return {mediaType, data};      } -    _getImageExtensionFromMediaType(mediaType) { -        switch (mediaType.toLowerCase()) { -            case 'image/png': return 'png'; -            case 'image/jpeg': return 'jpeg'; -            default: throw new Error('Unknown image media type'); -        } -    } -      _triggerDatabaseUpdated(type, cause) {          this._translator.clearDatabaseCaches();          this._sendMessageAllTabs('databaseUpdated', {type, cause}); diff --git a/ext/bg/js/clipboard-reader.js b/ext/bg/js/clipboard-reader.js index 66cf0c25..d8c80c21 100644 --- a/ext/bg/js/clipboard-reader.js +++ b/ext/bg/js/clipboard-reader.js @@ -25,13 +25,14 @@ class ClipboardReader {       * @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}) { +    constructor({document=null, pasteTargetSelector=null, imagePasteTargetSelector=null, mediaUtility=null}) {          this._document = document;          this._browser = null;          this._pasteTarget = null;          this._pasteTargetSelector = pasteTargetSelector;          this._imagePasteTarget = null;          this._imagePasteTargetSelector = imagePasteTargetSelector; +        this._mediaUtility = mediaUtility;      }      /** @@ -99,14 +100,20 @@ class ClipboardReader {       */      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; +        if ( +            this._isFirefox() && +            this._mediaUtility !== null && +            typeof navigator.clipboard !== 'undefined' && +            typeof navigator.clipboard.read === 'function' +        ) { +            // This function is behind the Firefox flag: dom.events.asyncClipboard.dataTransfer +            const {files} = await navigator.clipboard.read(); +            for (const file of files) { +                if (this._mediaUtility.getFileExtensionFromImageMediaType(file.type) !== null) { +                    return await this._readFileAsDataURL(file); +                }              } +            return null;          }          const document = this._document; diff --git a/ext/bg/js/dictionary-importer.js b/ext/bg/js/dictionary-importer.js index 2ad2ebe4..b641de3a 100644 --- a/ext/bg/js/dictionary-importer.js +++ b/ext/bg/js/dictionary-importer.js @@ -18,13 +18,14 @@  /* global   * JSZip   * JsonSchemaValidator - * mediaUtility + * MediaUtility   */  class DictionaryImporter {      constructor() {          this._schemas = new Map();          this._jsonSchemaValidator = new JsonSchemaValidator(); +        this._mediaUtility = new MediaUtility();      }      async importDictionary(dictionaryDatabase, archiveSource, details, onProgress) { @@ -324,14 +325,14 @@ class DictionaryImporter {          }          const content = await file.async('base64'); -        const mediaType = mediaUtility.getImageMediaTypeFromFileName(path); +        const mediaType = this._mediaUtility.getImageMediaTypeFromFileName(path);          if (mediaType === null) {              throw new Error(`Could not determine media type for image at path ${JSON.stringify(path)} for ${errorSource}`);          }          let image;          try { -            image = await mediaUtility.loadImageBase64(mediaType, content); +            image = await this._loadImageBase64(mediaType, content);          } catch (e) {              throw new Error(`Could not load image at path ${JSON.stringify(path)} for ${errorSource}`);          } @@ -380,4 +381,27 @@ class DictionaryImporter {          }          return await response.json();      } + +    /** +     * Attempts to load an image using a base64 encoded content and a media type. +     * @param mediaType The media type for the image content. +     * @param content The binary content for the image, encoded in base64. +     * @returns A Promise which resolves with an HTMLImageElement instance on +     *   successful load, otherwise an error is thrown. +     */ +    _loadImageBase64(mediaType, content) { +        return new Promise((resolve, reject) => { +            const image = new Image(); +            const eventListeners = new EventListenerCollection(); +            eventListeners.addEventListener(image, 'load', () => { +                eventListeners.removeAllEventListeners(); +                resolve(image); +            }, false); +            eventListeners.addEventListener(image, 'error', () => { +                eventListeners.removeAllEventListeners(); +                reject(new Error('Image failed to load')); +            }, false); +            image.src = `data:${mediaType};base64,${content}`; +        }); +    }  } diff --git a/ext/bg/js/media-utility.js b/ext/bg/js/media-utility.js index 1f93b2b4..52e32113 100644 --- a/ext/bg/js/media-utility.js +++ b/ext/bg/js/media-utility.js @@ -16,9 +16,9 @@   */  /** - * mediaUtility is an object containing helper methods related to media processing. + * MediaUtility is a class containing helper methods related to media processing.   */ -const mediaUtility = (() => { +class MediaUtility {      /**       * Gets the file extension of a file path. URL search queries and hash       * fragments are not handled. @@ -26,7 +26,7 @@ const mediaUtility = (() => {       * @returns The file extension, including the '.', or an empty string       *   if there is no file extension.       */ -    function getFileNameExtension(path) { +    getFileNameExtension(path) {          const match = /\.[^./\\]*$/.exec(path);          return match !== null ? match[0] : '';      } @@ -37,8 +37,8 @@ const mediaUtility = (() => {       * @returns The media type string if it can be determined from the file path,       *   otherwise null.       */ -    function getImageMediaTypeFromFileName(path) { -        switch (getFileNameExtension(path).toLowerCase()) { +    getImageMediaTypeFromFileName(path) { +        switch (this.getFileNameExtension(path).toLowerCase()) {              case '.apng':                  return 'image/apng';              case '.bmp': @@ -69,30 +69,33 @@ const mediaUtility = (() => {      }      /** -     * Attempts to load an image using a base64 encoded content and a media type. -     * @param mediaType The media type for the image content. -     * @param content The binary content for the image, encoded in base64. -     * @returns A Promise which resolves with an HTMLImageElement instance on -     *   successful load, otherwise an error is thrown. +     * Gets the file extension for a corresponding media type. +     * @param mediaType The media type to use. +     * @returns A file extension including the dot for the media type, +     *   otherwise null.       */ -    function loadImageBase64(mediaType, content) { -        return new Promise((resolve, reject) => { -            const image = new Image(); -            const eventListeners = new EventListenerCollection(); -            eventListeners.addEventListener(image, 'load', () => { -                eventListeners.removeAllEventListeners(); -                resolve(image); -            }, false); -            eventListeners.addEventListener(image, 'error', () => { -                eventListeners.removeAllEventListeners(); -                reject(new Error('Image failed to load')); -            }, false); -            image.src = `data:${mediaType};base64,${content}`; -        }); +    getFileExtensionFromImageMediaType(mediaType) { +        switch (mediaType) { +            case 'image/apng': +                return '.apng'; +            case 'image/bmp': +                return '.bmp'; +            case 'image/gif': +                return '.gif'; +            case 'image/x-icon': +                return '.ico'; +            case 'image/jpeg': +                return '.jpeg'; +            case 'image/png': +                return '.png'; +            case 'image/svg+xml': +                return '.svg'; +            case 'image/tiff': +                return '.tiff'; +            case 'image/webp': +                return '.webp'; +            default: +                return null; +        }      } - -    return { -        getImageMediaTypeFromFileName, -        loadImageBase64 -    }; -})(); +} |