diff options
Diffstat (limited to 'ext/js/background/offscreen-proxy.js')
-rw-r--r-- | ext/js/background/offscreen-proxy.js | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js new file mode 100644 index 00000000..c01f523d --- /dev/null +++ b/ext/js/background/offscreen-proxy.js @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2016-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 <https://www.gnu.org/licenses/>. + */ + +import {deserializeError, isObject} from '../core.js'; +import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; + +export class OffscreenProxy { + constructor() { + this._creatingOffscreen = null; + } + + // https://developer.chrome.com/docs/extensions/reference/offscreen/ + async prepare() { + if (await this._hasOffscreenDocument()) { + return; + } + if (this._creatingOffscreen) { + await this._creatingOffscreen; + return; + } + + this._creatingOffscreen = chrome.offscreen.createDocument({ + url: 'offscreen.html', + reasons: ['CLIPBOARD'], + justification: 'Access to the clipboard' + }); + await this._creatingOffscreen; + this._creatingOffscreen = null; + } + + async _hasOffscreenDocument() { + const offscreenUrl = chrome.runtime.getURL('offscreen.html'); + if (!chrome.runtime.getContexts) { // chrome version <116 + const matchedClients = await clients.matchAll(); + return await matchedClients.some((client) => client.url === offscreenUrl); + } + + const contexts = await chrome.runtime.getContexts({ + contextTypes: ['OFFSCREEN_DOCUMENT'], + documentUrls: [offscreenUrl] + }); + return !!contexts.length; + } + + sendMessagePromise(...args) { + return new Promise((resolve, reject) => { + const callback = (response) => { + try { + resolve(this._getMessageResponseResult(response)); + } catch (error) { + reject(error); + } + }; + + chrome.runtime.sendMessage(...args, callback); + }); + } + + _getMessageResponseResult(response) { + let error = chrome.runtime.lastError; + if (error) { + throw new Error(error.message); + } + if (!isObject(response)) { + throw new Error('Offscreen document did not respond'); + } + error = response.error; + if (error) { + throw deserializeError(error); + } + return response.result; + } +} + +export class DictionaryDatabaseProxy { + constructor(offscreen) { + this._offscreen = offscreen; + } + + prepare() { + return this._offscreen.sendMessagePromise({action: 'databasePrepareOffscreen'}); + } + + getDictionaryInfo() { + return this._offscreen.sendMessagePromise({action: 'getDictionaryInfoOffscreen'}); + } + + purge() { + return this._offscreen.sendMessagePromise({action: 'databasePurgeOffscreen'}); + } + + async getMedia(targets) { + const serializedMedia = await this._offscreen.sendMessagePromise({action: 'databaseGetMediaOffscreen', params: {targets}}); + const media = serializedMedia.map((m) => ({...m, content: ArrayBufferUtil.base64ToArrayBuffer(m.content)})); + return media; + } +} + +export class TranslatorProxy { + constructor(offscreen) { + this._offscreen = offscreen; + } + + prepare(deinflectionReasons) { + return this._offscreen.sendMessagePromise({action: 'translatorPrepareOffscreen', params: {deinflectionReasons}}); + } + + async findKanji(text, findKanjiOptions) { + const enabledDictionaryMapList = [...findKanjiOptions.enabledDictionaryMap]; + const modifiedKanjiOptions = { + ...findKanjiOptions, + enabledDictionaryMap: enabledDictionaryMapList + }; + return this._offscreen.sendMessagePromise({action: 'findKanjiOffscreen', params: {text, findKanjiOptions: modifiedKanjiOptions}}); + } + + async findTerms(mode, text, findTermsOptions) { + const {enabledDictionaryMap, excludeDictionaryDefinitions, textReplacements} = findTermsOptions; + const enabledDictionaryMapList = [...enabledDictionaryMap]; + const excludeDictionaryDefinitionsList = excludeDictionaryDefinitions ? [...excludeDictionaryDefinitions] : null; + const textReplacementsSerialized = textReplacements.map((group) => { + if (!group) { + return group; + } + return group.map((opt) => ({...opt, pattern: opt.pattern.toString()})); + }); + const modifiedFindTermsOptions = { + ...findTermsOptions, + enabledDictionaryMap: enabledDictionaryMapList, + excludeDictionaryDefinitions: excludeDictionaryDefinitionsList, + textReplacements: textReplacementsSerialized + }; + return this._offscreen.sendMessagePromise({action: 'findTermsOffscreen', params: {mode, text, findTermsOptions: modifiedFindTermsOptions}}); + } + + async getTermFrequencies(termReadingList, dictionaries) { + return this._offscreen.sendMessagePromise({action: 'getTermFrequenciesOffscreen', params: {termReadingList, dictionaries}}); + } + + clearDatabaseCaches() { + return this._offscreen.sendMessagePromise({action: 'clearDatabaseCachesOffscreen'}); + } +} + +export class ClipboardReaderProxy { + constructor(offscreen) { + this._offscreen = offscreen; + } + + async getText(useRichText) { + return this._offscreen.sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}}); + } + + async getImage() { + return this._offscreen.sendMessagePromise({action: 'clipboardGetImageOffscreen'}); + } +} |