aboutsummaryrefslogtreecommitdiff
path: root/ext/js/background/offscreen-proxy.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/js/background/offscreen-proxy.js')
-rw-r--r--ext/js/background/offscreen-proxy.js162
1 files changed, 123 insertions, 39 deletions
diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js
index c01f523d..63f619fa 100644
--- a/ext/js/background/offscreen-proxy.js
+++ b/ext/js/background/offscreen-proxy.js
@@ -16,15 +16,19 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import {deserializeError, isObject} from '../core.js';
+import {isObject} from '../core.js';
+import {ExtensionError} from '../core/extension-error.js';
import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js';
export class OffscreenProxy {
constructor() {
+ /** @type {?Promise<void>} */
this._creatingOffscreen = null;
}
- // https://developer.chrome.com/docs/extensions/reference/offscreen/
+ /**
+ * @see https://developer.chrome.com/docs/extensions/reference/offscreen/
+ */
async prepare() {
if (await this._hasOffscreenDocument()) {
return;
@@ -36,20 +40,30 @@ export class OffscreenProxy {
this._creatingOffscreen = chrome.offscreen.createDocument({
url: 'offscreen.html',
- reasons: ['CLIPBOARD'],
+ reasons: [
+ /** @type {chrome.offscreen.Reason} */ ('CLIPBOARD')
+ ],
justification: 'Access to the clipboard'
});
await this._creatingOffscreen;
this._creatingOffscreen = null;
}
+ /**
+ * @returns {Promise<boolean>}
+ */
async _hasOffscreenDocument() {
const offscreenUrl = chrome.runtime.getURL('offscreen.html');
- if (!chrome.runtime.getContexts) { // chrome version <116
+ // @ts-expect-error - API not defined yet
+ if (!chrome.runtime.getContexts) { // chrome version below 116
+ // Clients: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/clients
+ // @ts-expect-error - Types not set up for service workers yet
const matchedClients = await clients.matchAll();
+ // @ts-expect-error - Types not set up for service workers yet
return await matchedClients.some((client) => client.url === offscreenUrl);
}
+ // @ts-expect-error - API not defined yet
const contexts = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT'],
documentUrls: [offscreenUrl]
@@ -57,116 +71,186 @@ export class OffscreenProxy {
return !!contexts.length;
}
- sendMessagePromise(...args) {
+ /**
+ * @template {import('offscreen').MessageType} TMessageType
+ * @param {import('offscreen').Message<TMessageType>} message
+ * @returns {Promise<import('offscreen').MessageReturn<TMessageType>>}
+ */
+ sendMessagePromise(message) {
return new Promise((resolve, reject) => {
- const callback = (response) => {
+ chrome.runtime.sendMessage(message, (response) => {
try {
resolve(this._getMessageResponseResult(response));
} catch (error) {
reject(error);
}
- };
-
- chrome.runtime.sendMessage(...args, callback);
+ });
});
}
+ /**
+ * @template [TReturn=unknown]
+ * @param {import('core').Response<TReturn>} response
+ * @returns {TReturn}
+ * @throws {Error}
+ */
_getMessageResponseResult(response) {
- let error = chrome.runtime.lastError;
+ const 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);
+ const error2 = response.error;
+ if (error2) {
+ throw ExtensionError.deserialize(error2);
}
return response.result;
}
}
export class DictionaryDatabaseProxy {
+ /**
+ * @param {OffscreenProxy} offscreen
+ */
constructor(offscreen) {
+ /** @type {OffscreenProxy} */
this._offscreen = offscreen;
}
- prepare() {
- return this._offscreen.sendMessagePromise({action: 'databasePrepareOffscreen'});
+ /**
+ * @returns {Promise<void>}
+ */
+ async prepare() {
+ await this._offscreen.sendMessagePromise({action: 'databasePrepareOffscreen'});
}
- getDictionaryInfo() {
+ /**
+ * @returns {Promise<import('dictionary-importer').Summary[]>}
+ */
+ async getDictionaryInfo() {
return this._offscreen.sendMessagePromise({action: 'getDictionaryInfoOffscreen'});
}
- purge() {
- return this._offscreen.sendMessagePromise({action: 'databasePurgeOffscreen'});
+ /**
+ * @returns {Promise<boolean>}
+ */
+ async purge() {
+ return await this._offscreen.sendMessagePromise({action: 'databasePurgeOffscreen'});
}
+ /**
+ * @param {import('dictionary-database').MediaRequest[]} targets
+ * @returns {Promise<import('dictionary-database').Media[]>}
+ */
async getMedia(targets) {
- const serializedMedia = await this._offscreen.sendMessagePromise({action: 'databaseGetMediaOffscreen', params: {targets}});
+ const serializedMedia = /** @type {import('dictionary-database').Media<string>[]} */ (await this._offscreen.sendMessagePromise({action: 'databaseGetMediaOffscreen', params: {targets}}));
const media = serializedMedia.map((m) => ({...m, content: ArrayBufferUtil.base64ToArrayBuffer(m.content)}));
return media;
}
}
export class TranslatorProxy {
+ /**
+ * @param {OffscreenProxy} offscreen
+ */
constructor(offscreen) {
+ /** @type {OffscreenProxy} */
this._offscreen = offscreen;
}
- prepare(deinflectionReasons) {
- return this._offscreen.sendMessagePromise({action: 'translatorPrepareOffscreen', params: {deinflectionReasons}});
+ /**
+ * @param {import('deinflector').ReasonsRaw} deinflectionReasons
+ */
+ async prepare(deinflectionReasons) {
+ await this._offscreen.sendMessagePromise({action: 'translatorPrepareOffscreen', params: {deinflectionReasons}});
}
- async findKanji(text, findKanjiOptions) {
- const enabledDictionaryMapList = [...findKanjiOptions.enabledDictionaryMap];
- const modifiedKanjiOptions = {
- ...findKanjiOptions,
+ /**
+ * @param {string} text
+ * @param {import('translation').FindKanjiOptions} options
+ * @returns {Promise<import('dictionary').KanjiDictionaryEntry[]>}
+ */
+ async findKanji(text, options) {
+ const enabledDictionaryMapList = [...options.enabledDictionaryMap];
+ /** @type {import('offscreen').FindKanjiOptionsOffscreen} */
+ const modifiedOptions = {
+ ...options,
enabledDictionaryMap: enabledDictionaryMapList
};
- return this._offscreen.sendMessagePromise({action: 'findKanjiOffscreen', params: {text, findKanjiOptions: modifiedKanjiOptions}});
+ return this._offscreen.sendMessagePromise({action: 'findKanjiOffscreen', params: {text, options: modifiedOptions}});
}
- async findTerms(mode, text, findTermsOptions) {
- const {enabledDictionaryMap, excludeDictionaryDefinitions, textReplacements} = findTermsOptions;
+ /**
+ * @param {import('translator').FindTermsMode} mode
+ * @param {string} text
+ * @param {import('translation').FindTermsOptions} options
+ * @returns {Promise<import('translator').FindTermsResult>}
+ */
+ async findTerms(mode, text, options) {
+ const {enabledDictionaryMap, excludeDictionaryDefinitions, textReplacements} = options;
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()}));
+ return group !== null ? group.map((opt) => ({...opt, pattern: opt.pattern.toString()})) : null;
});
- const modifiedFindTermsOptions = {
- ...findTermsOptions,
+ /** @type {import('offscreen').FindTermsOptionsOffscreen} */
+ const modifiedOptions = {
+ ...options,
enabledDictionaryMap: enabledDictionaryMapList,
excludeDictionaryDefinitions: excludeDictionaryDefinitionsList,
textReplacements: textReplacementsSerialized
};
- return this._offscreen.sendMessagePromise({action: 'findTermsOffscreen', params: {mode, text, findTermsOptions: modifiedFindTermsOptions}});
+ return this._offscreen.sendMessagePromise({action: 'findTermsOffscreen', params: {mode, text, options: modifiedOptions}});
}
+ /**
+ * @param {import('translator').TermReadingList} termReadingList
+ * @param {string[]} dictionaries
+ * @returns {Promise<import('translator').TermFrequencySimple[]>}
+ */
async getTermFrequencies(termReadingList, dictionaries) {
return this._offscreen.sendMessagePromise({action: 'getTermFrequenciesOffscreen', params: {termReadingList, dictionaries}});
}
- clearDatabaseCaches() {
- return this._offscreen.sendMessagePromise({action: 'clearDatabaseCachesOffscreen'});
+ /** */
+ async clearDatabaseCaches() {
+ await this._offscreen.sendMessagePromise({action: 'clearDatabaseCachesOffscreen'});
}
}
export class ClipboardReaderProxy {
+ /**
+ * @param {OffscreenProxy} offscreen
+ */
constructor(offscreen) {
+ /** @type {?import('environment').Browser} */
+ this._browser = null;
+ /** @type {OffscreenProxy} */
this._offscreen = offscreen;
}
+ /** @type {?import('environment').Browser} */
+ get browser() { return this._browser; }
+ set browser(value) {
+ if (this._browser === value) { return; }
+ this._browser = value;
+ this._offscreen.sendMessagePromise({action: 'clipboardSetBrowserOffscreen', params: {value}});
+ }
+
+ /**
+ * @param {boolean} useRichText
+ * @returns {Promise<string>}
+ */
async getText(useRichText) {
- return this._offscreen.sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}});
+ return await this._offscreen.sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}});
}
+ /**
+ * @returns {Promise<?string>}
+ */
async getImage() {
- return this._offscreen.sendMessagePromise({action: 'clipboardGetImageOffscreen'});
+ return await this._offscreen.sendMessagePromise({action: 'clipboardGetImageOffscreen'});
}
}