Skip to content

Commit 6ba1ffe

Browse files
WebExtension class (#551)
* Add WebExtension class * Use WebExtension class * Use WebExtension instance for all runtime message sending * Use getUrl * Add a sendMessage variant which ignores the response and error
1 parent ebdde1e commit 6ba1ffe

13 files changed

Lines changed: 179 additions & 124 deletions

File tree

ext/js/app/frontend.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export class Frontend {
226226
try {
227227
await this._updateOptionsInternal();
228228
} catch (e) {
229-
if (!yomitan.isExtensionUnloaded) {
229+
if (!yomitan.webExtension.unloaded) {
230230
throw e;
231231
}
232232
}
@@ -368,7 +368,7 @@ export class Frontend {
368368
const scanningOptions = /** @type {import('settings').ProfileOptions} */ (this._options).scanning;
369369

370370
if (error !== null) {
371-
if (yomitan.isExtensionUnloaded) {
371+
if (yomitan.webExtension.unloaded) {
372372
if (textSource !== null && !passive) {
373373
this._showExtensionUnloaded(textSource);
374374
}
@@ -655,7 +655,7 @@ export class Frontend {
655655
try {
656656
return this._popup !== null && await this._popup.containsPoint(x, y);
657657
} catch (e) {
658-
if (!yomitan.isExtensionUnloaded) {
658+
if (!yomitan.webExtension.unloaded) {
659659
throw e;
660660
}
661661
return false;
@@ -742,7 +742,7 @@ export class Frontend {
742742
Promise.resolve()
743743
);
744744
this._lastShowPromise.catch((error) => {
745-
if (yomitan.isExtensionUnloaded) { return; }
745+
if (yomitan.webExtension.unloaded) { return; }
746746
log.error(error);
747747
});
748748
return this._lastShowPromise;

ext/js/app/popup-proxy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ export class PopupProxy extends EventDispatcher {
320320
try {
321321
return await this._invoke(action, params);
322322
} catch (e) {
323-
if (!yomitan.isExtensionUnloaded) { throw e; }
323+
if (!yomitan.webExtension.unloaded) { throw e; }
324324
return defaultReturnValue;
325325
}
326326
}

ext/js/app/popup-window.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ export class PopupWindow extends EventDispatcher {
274274
* @returns {Promise<import('display').DirectApiReturn<TName>|undefined>}
275275
*/
276276
async _invoke(open, action, params) {
277-
if (yomitan.isExtensionUnloaded) {
277+
if (yomitan.webExtension.unloaded) {
278278
return void 0;
279279
}
280280

@@ -290,7 +290,7 @@ export class PopupWindow extends EventDispatcher {
290290
message
291291
));
292292
} catch (e) {
293-
if (yomitan.isExtensionUnloaded) {
293+
if (yomitan.webExtension.unloaded) {
294294
open = false;
295295
}
296296
}

ext/js/app/popup.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ export class Popup extends EventDispatcher {
714714
try {
715715
return await this._invoke(action, params);
716716
} catch (e) {
717-
if (!yomitan.isExtensionUnloaded) { throw e; }
717+
if (!yomitan.webExtension.unloaded) { throw e; }
718718
return void 0;
719719
}
720720
}

ext/js/background/backend.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ import {injectStylesheet} from './script-manager.js';
4949
*/
5050
export class Backend {
5151
/**
52-
* Creates a new instance.
52+
* @param {import('../extension/web-extension.js').WebExtension} webExtension
5353
*/
54-
constructor() {
54+
constructor(webExtension) {
55+
/** @type {import('../extension/web-extension.js').WebExtension} */
56+
this._webExtension = webExtension;
5557
/** @type {JapaneseUtil} */
5658
this._japaneseUtil = new JapaneseUtil(wanakana);
5759
/** @type {Environment} */
@@ -80,7 +82,7 @@ export class Backend {
8082
});
8183
} else {
8284
/** @type {?OffscreenProxy} */
83-
this._offscreen = new OffscreenProxy();
85+
this._offscreen = new OffscreenProxy(webExtension);
8486
/** @type {DictionaryDatabase|DictionaryDatabaseProxy} */
8587
this._dictionaryDatabase = new DictionaryDatabaseProxy(this._offscreen);
8688
/** @type {Translator|TranslatorProxy} */
@@ -1902,8 +1904,7 @@ export class Backend {
19021904
* @param {import('application').ApiMessage<TName>} message
19031905
*/
19041906
_sendMessageIgnoreResponse(message) {
1905-
const callback = () => this._checkLastError(chrome.runtime.lastError);
1906-
chrome.runtime.sendMessage(message, callback);
1907+
this._webExtension.sendMessageIgnoreResponse(message);
19071908
}
19081909

19091910
/**

ext/js/background/background-main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {Backend} from './backend.js';
2323
async function main() {
2424
yomitan.prepare(true);
2525

26-
const backend = new Backend();
26+
const backend = new Backend(yomitan.webExtension);
2727
await backend.prepare();
2828
}
2929

ext/js/background/offscreen-proxy.js

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@
1616
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1717
*/
1818

19-
import {isObject} from '../core/utilities.js';
2019
import {ExtensionError} from '../core/extension-error.js';
20+
import {isObject} from '../core/utilities.js';
2121
import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js';
2222

2323
export class OffscreenProxy {
24-
constructor() {
24+
/**
25+
* @param {import('../extension/web-extension.js').WebExtension} webExtension
26+
*/
27+
constructor(webExtension) {
28+
/** @type {import('../extension/web-extension.js').WebExtension} */
29+
this._webExtension = webExtension;
2530
/** @type {?Promise<void>} */
2631
this._creatingOffscreen = null;
2732
}
@@ -76,16 +81,9 @@ export class OffscreenProxy {
7681
* @param {import('offscreen').ApiMessage<TMessageType>} message
7782
* @returns {Promise<import('offscreen').ApiReturn<TMessageType>>}
7883
*/
79-
sendMessagePromise(message) {
80-
return new Promise((resolve, reject) => {
81-
chrome.runtime.sendMessage(message, (response) => {
82-
try {
83-
resolve(this._getMessageResponseResult(response));
84-
} catch (error) {
85-
reject(error);
86-
}
87-
});
88-
});
84+
async sendMessagePromise(message) {
85+
const response = await this._webExtension.sendMessagePromise(message);
86+
return this._getMessageResponseResult(/** @type {import('core').Response<import('offscreen').ApiReturn<TMessageType>>} */ (response));
8987
}
9088

9189
/**

ext/js/comm/api.js

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ import {ExtensionError} from '../core/extension-error.js';
2020

2121
export class API {
2222
/**
23-
* @param {import('../yomitan.js').Yomitan} yomitan
23+
* @param {import('../extension/web-extension.js').WebExtension} webExtension
2424
*/
25-
constructor(yomitan) {
26-
/** @type {import('../yomitan.js').Yomitan} */
27-
this._yomitan = yomitan;
25+
constructor(webExtension) {
26+
/** @type {import('../extension/web-extension.js').WebExtension} */
27+
this._webExtension = webExtension;
2828
}
2929

3030
/**
@@ -375,13 +375,15 @@ export class API {
375375
const data = {action, params};
376376
return new Promise((resolve, reject) => {
377377
try {
378-
this._yomitan.sendMessage(data, (response) => {
379-
this._checkLastError(chrome.runtime.lastError);
378+
this._webExtension.sendMessage(data, (response) => {
379+
this._webExtension.getLastError();
380380
if (response !== null && typeof response === 'object') {
381-
if (typeof response.error !== 'undefined') {
382-
reject(ExtensionError.deserialize(response.error));
381+
const {error} = /** @type {import('core').UnknownObject} */ (response);
382+
if (typeof error !== 'undefined') {
383+
reject(ExtensionError.deserialize(/** @type {import('core').SerializedError} */ (error)));
383384
} else {
384-
resolve(response.result);
385+
const {result} = /** @type {import('core').UnknownObject} */ (response);
386+
resolve(/** @type {import('api').ApiReturn<TAction>} */ (result));
385387
}
386388
} else {
387389
const message = response === null ? 'Unexpected null response' : `Unexpected response of type ${typeof response}`;
@@ -393,11 +395,4 @@ export class API {
393395
}
394396
});
395397
}
396-
397-
/**
398-
* @param {chrome.runtime.LastError|undefined} _ignore
399-
*/
400-
_checkLastError(_ignore) {
401-
// NOP
402-
}
403398
}

ext/js/display/display.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ export class Display extends EventDispatcher {
390390
* @param {Error} error
391391
*/
392392
onError(error) {
393-
if (yomitan.isExtensionUnloaded) { return; }
393+
if (yomitan.webExtension.unloaded) { return; }
394394
log.error(error);
395395
}
396396

@@ -727,8 +727,7 @@ export class Display extends EventDispatcher {
727727

728728
/** @type {import('display').WindowApiHandler<'displayExtensionUnloaded'>} */
729729
_onMessageExtensionUnloaded() {
730-
if (yomitan.isExtensionUnloaded) { return; }
731-
yomitan.triggerExtensionUnloaded();
730+
yomitan.webExtension.triggerUnloaded();
732731
}
733732

734733
// Private
@@ -1894,7 +1893,7 @@ export class Display extends EventDispatcher {
18941893
* @param {import('text-scanner').SearchedEventDetails} details
18951894
*/
18961895
_onContentTextScannerSearched({type, dictionaryEntries, sentence, textSource, optionsContext, error}) {
1897-
if (error !== null && !yomitan.isExtensionUnloaded) {
1896+
if (error !== null && !yomitan.webExtension.unloaded) {
18981897
log.error(error);
18991898
}
19001899

ext/js/extension/web-extension.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright (C) 2024 Yomitan Authors
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
import {EventDispatcher} from '../core/event-dispatcher.js';
19+
import {toError} from '../core/to-error.js';
20+
21+
/**
22+
* @augments EventDispatcher<import('web-extension').Events>
23+
*/
24+
export class WebExtension extends EventDispatcher {
25+
constructor() {
26+
super();
27+
/** @type {boolean} */
28+
this._unloaded = false;
29+
}
30+
31+
/** @type {boolean} */
32+
get unloaded() {
33+
return this._unloaded;
34+
}
35+
36+
/**
37+
* @param {string} path
38+
* @returns {string}
39+
*/
40+
getUrl(path) {
41+
return chrome.runtime.getURL(path);
42+
}
43+
44+
/**
45+
* @param {unknown} message
46+
* @param {(response: unknown) => void} responseCallback
47+
* @throws {Error}
48+
*/
49+
sendMessage(message, responseCallback) {
50+
try {
51+
chrome.runtime.sendMessage(message, responseCallback);
52+
} catch (error) {
53+
this.triggerUnloaded();
54+
throw toError(error);
55+
}
56+
}
57+
58+
/**
59+
* @param {unknown} message
60+
* @returns {Promise<unknown>}
61+
*/
62+
sendMessagePromise(message) {
63+
return new Promise((resolve, reject) => {
64+
try {
65+
this.sendMessage(message, (response) => {
66+
const error = this.getLastError();
67+
if (error !== null) {
68+
reject(error);
69+
} else {
70+
resolve(response);
71+
}
72+
});
73+
} catch (error) {
74+
reject(error);
75+
}
76+
});
77+
}
78+
79+
/**
80+
* @param {unknown} message
81+
*/
82+
sendMessageIgnoreResponse(message) {
83+
this.sendMessage(message, () => {
84+
// Clear the last error
85+
this.getLastError();
86+
});
87+
}
88+
89+
/**
90+
* @returns {?Error}
91+
*/
92+
getLastError() {
93+
const {lastError} = chrome.runtime;
94+
if (typeof lastError !== 'undefined') {
95+
const {message} = lastError;
96+
return new Error(typeof message === 'string' ? message : 'An unknown web extension error occured');
97+
}
98+
return null;
99+
}
100+
101+
/** */
102+
triggerUnloaded() {
103+
if (this._unloaded) { return; }
104+
this._unloaded = true;
105+
this.trigger('unloaded', {});
106+
}
107+
}

0 commit comments

Comments
 (0)