Skip to content

Commit 229163d

Browse files
committed
[WIP] attempt to use OffscreenCanvas
1 parent 7553db1 commit 229163d

23 files changed

+359
-236
lines changed

.eslintrc.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"no-shadow": ["error", {"builtinGlobals": false}],
8787
"no-shadow-restricted-names": "error",
8888
"no-template-curly-in-string": "error",
89-
"no-undef": "error",
89+
"no-undef": "off",
9090
"no-undefined": "error",
9191
"no-underscore-dangle": ["error", {"allowAfterThis": true, "allowAfterSuper": false, "allowAfterThisConstructor": false}],
9292
"no-unexpected-multiline": "error",
@@ -581,7 +581,6 @@
581581
"ext/js/core/to-error.js",
582582
"ext/js/core/utilities.js",
583583
"ext/js/data/database.js",
584-
"ext/js/dictionary/dictionary-database.js",
585584
"ext/js/dictionary/dictionary-importer.js",
586585
"ext/js/dictionary/dictionary-worker-handler.js",
587586
"ext/js/dictionary/dictionary-worker-main.js",
@@ -623,7 +622,6 @@
623622
"ext/js/data/json-schema.js",
624623
"ext/js/data/options-util.js",
625624
"ext/js/data/permissions-util.js",
626-
"ext/js/dictionary/dictionary-database.js",
627625
"ext/js/dom/native-simple-dom-parser.js",
628626
"ext/js/dom/simple-dom-parser.js",
629627
"ext/js/extension/environment.js",

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
"eslint.format.enable": true,
1818
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
1919
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
20+
"javascript.format.enable": false,
21+
"typescript.format.enable": false,
2022
"javascript.preferences.importModuleSpecifierEnding": "js",
2123
"editor.tabSize": 4,
2224
"editor.insertSpaces": true,

ext/css/display.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,8 @@ button.action-button:active {
827827
.entry {
828828
padding: var(--entry-vertical-padding) var(--entry-horizontal-padding);
829829
position: relative;
830+
content-visibility: auto;
831+
contain-intrinsic-height: auto 500px;
830832
}
831833
.entry+.entry {
832834
border-top: var(--thin-border-size) solid var(--light-border-color);

ext/css/structured-content.css

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,6 @@
7575
outline: none;
7676
width: 100%;
7777
}
78-
.gloss-image:not([src]) {
79-
display: none;
80-
}
8178
.gloss-image-link[data-image-rendering=pixelated] .gloss-image {
8279
image-rendering: auto;
8380
image-rendering: -moz-crisp-edges;

ext/js/background/backend.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ export class Backend {
7272
this._translator = new Translator(this._dictionaryDatabase);
7373
/** @type {ClipboardReader|ClipboardReaderProxy} */
7474
this._clipboardReader = new ClipboardReader(
75-
// eslint-disable-next-line no-undef
7675
(typeof document === 'object' && document !== null ? document : null),
7776
'#clipboard-paste-target',
7877
'#clipboard-rich-content-paste-target',
@@ -172,7 +171,7 @@ export class Backend {
172171
['getDictionaryInfo', this._onApiGetDictionaryInfo.bind(this)],
173172
['purgeDatabase', this._onApiPurgeDatabase.bind(this)],
174173
['getMedia', this._onApiGetMedia.bind(this)],
175-
['getMediaObjects', this._onApiGetMediaObjects.bind(this)],
174+
['drawMedia', this._onApiDrawMedia.bind(this)],
176175
['logGenericErrorBackend', this._onApiLogGenericErrorBackend.bind(this)],
177176
['logIndicatorClear', this._onApiLogIndicatorClear.bind(this)],
178177
['modifySettings', this._onApiModifySettings.bind(this)],
@@ -242,6 +241,15 @@ export class Backend {
242241
const onMessage = this._onMessageWrapper.bind(this);
243242
chrome.runtime.onMessage.addListener(onMessage);
244243

244+
// This is for receiving messages sent with navigator.serviceWorker, which has the benefit of being able to transfer objects, but doesn't accept callbacks
245+
addEventListener('message', (event) => {
246+
if (event.data.action === 'drawMedia') {
247+
this._dictionaryDatabase.drawMedia(event.data.params);
248+
} else if (event.data.action === 'registerOffscreenPort' && this._offscreen !== null) {
249+
this._offscreen.registerOffscreenPort(event.ports[0]);
250+
}
251+
});
252+
245253
if (this._canObservePermissionsChanges()) {
246254
const onPermissionsChanged = this._onWebExtensionEventWrapper(this._onPermissionsChanged.bind(this));
247255
chrome.permissions.onAdded.addListener(onPermissionsChanged);
@@ -796,9 +804,10 @@ export class Backend {
796804
return await this._getNormalizedDictionaryDatabaseMedia(targets);
797805
}
798806

799-
/** @type {import('api').ApiHandler<'getMediaObjects'>} */
800-
async _onApiGetMediaObjects({targets}) {
801-
return await this._dictionaryDatabase.getMediaObjects(targets);
807+
/** @type {import('api').ApiHandler<'drawMedia'>} */
808+
async _onApiDrawMedia({targets}) {
809+
console.log('_onApiDrawMedia', targets);
810+
await this._dictionaryDatabase.drawMedia(targets);
802811
}
803812

804813
/** @type {import('api').ApiHandler<'logGenericErrorBackend'>} */

ext/js/background/offscreen-proxy.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ export class OffscreenProxy {
5858
this._webExtension = webExtension;
5959
/** @type {?Promise<void>} */
6060
this._creatingOffscreen = null;
61+
62+
/** @type {?MessagePort} */
63+
this._currentOffscreenPort = null;
6164
}
6265

6366
/**
@@ -136,6 +139,24 @@ export class OffscreenProxy {
136139
}
137140
return response.result;
138141
}
142+
143+
/**
144+
* @param {MessagePort} port
145+
*/
146+
async registerOffscreenPort(port) {
147+
if (this._currentOffscreenPort) {
148+
this._currentOffscreenPort.close();
149+
}
150+
this._currentOffscreenPort = port;
151+
}
152+
153+
/**
154+
* @param {any} message
155+
* @param {Transferable[]} transfers
156+
*/
157+
sendMessageViaPort(message, transfers) {
158+
this._currentOffscreenPort?.postMessage(message, transfers);
159+
}
139160
}
140161

141162
export class DictionaryDatabaseProxy {
@@ -178,12 +199,11 @@ export class DictionaryDatabaseProxy {
178199
}
179200

180201
/**
181-
* @param {import('dictionary-database').MediaRequest[]} targets
182-
* @returns {Promise<import('dictionary-database').MediaObject[]>}
202+
* @param {import('dictionary-database').DrawMediaRequest[]} targets
203+
* @returns {Promise<void>}
183204
*/
184-
async getMediaObjects(targets) {
185-
console.log('offscreen getMediaObjects', targets);
186-
return await this._offscreen.sendMessagePromise({action: 'databaseGetMediaObjectsOffscreen', params: {targets}});
205+
async drawMedia(targets) {
206+
this._offscreen.sendMessageViaPort({action: 'drawMedia', params: targets}, targets.map((t) => t.canvas));
187207
}
188208
}
189209

ext/js/background/offscreen.js

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,19 @@ export class Offscreen {
4646
/* eslint-disable @stylistic/no-multi-spaces */
4747
/** @type {import('offscreen').ApiMap} */
4848
this._apiMap = createApiMap([
49-
['clipboardGetTextOffscreen', this._getTextHandler.bind(this)],
50-
['clipboardGetImageOffscreen', this._getImageHandler.bind(this)],
49+
['clipboardGetTextOffscreen', this._getTextHandler.bind(this)],
50+
['clipboardGetImageOffscreen', this._getImageHandler.bind(this)],
5151
['clipboardSetBrowserOffscreen', this._setClipboardBrowser.bind(this)],
52-
['databasePrepareOffscreen', this._prepareDatabaseHandler.bind(this)],
53-
['getDictionaryInfoOffscreen', this._getDictionaryInfoHandler.bind(this)],
54-
['databasePurgeOffscreen', this._purgeDatabaseHandler.bind(this)],
55-
['databaseGetMediaOffscreen', this._getMediaHandler.bind(this)],
56-
['databaseGetMediaObjectsOffscreen', this._getMediaObjectsHandler.bind(this)],
57-
['translatorPrepareOffscreen', this._prepareTranslatorHandler.bind(this)],
58-
['findKanjiOffscreen', this._findKanjiHandler.bind(this)],
59-
['findTermsOffscreen', this._findTermsHandler.bind(this)],
60-
['getTermFrequenciesOffscreen', this._getTermFrequenciesHandler.bind(this)],
61-
['clearDatabaseCachesOffscreen', this._clearDatabaseCachesHandler.bind(this)],
52+
['databasePrepareOffscreen', this._prepareDatabaseHandler.bind(this)],
53+
['getDictionaryInfoOffscreen', this._getDictionaryInfoHandler.bind(this)],
54+
['databasePurgeOffscreen', this._purgeDatabaseHandler.bind(this)],
55+
['databaseGetMediaOffscreen', this._getMediaHandler.bind(this)],
56+
['databaseDrawMediaOffscreen', this._drawMediaHandler.bind(this)],
57+
['translatorPrepareOffscreen', this._prepareTranslatorHandler.bind(this)],
58+
['findKanjiOffscreen', this._findKanjiHandler.bind(this)],
59+
['findTermsOffscreen', this._findTermsHandler.bind(this)],
60+
['getTermFrequenciesOffscreen', this._getTermFrequenciesHandler.bind(this)],
61+
['clearDatabaseCachesOffscreen', this._clearDatabaseCachesHandler.bind(this)]
6262
]);
6363
/* eslint-enable @stylistic/no-multi-spaces */
6464

@@ -69,6 +69,18 @@ export class Offscreen {
6969
/** */
7070
prepare() {
7171
chrome.runtime.onMessage.addListener(this._onMessage.bind(this));
72+
73+
const registerPort = () => {
74+
const mc = new MessageChannel();
75+
mc.port1.onmessage = (e) => {
76+
this._onSWMessage(e.data);
77+
};
78+
void navigator.serviceWorker.ready.then((swr) => {
79+
swr.active?.postMessage({action: 'registerOffscreenPort'}, [mc.port2]);
80+
});
81+
};
82+
navigator.serviceWorker.addEventListener("controllerchange", registerPort);
83+
registerPort();
7284
}
7385

7486
/** @type {import('offscreen').ApiHandler<'clipboardGetTextOffscreen'>} */
@@ -111,9 +123,9 @@ export class Offscreen {
111123
return media.map((m) => ({...m, content: arrayBufferToBase64(m.content)}));
112124
}
113125

114-
/** @type {import('offscreen').ApiHandler<'databaseGetMediaObjectsOffscreen'>} */
115-
async _getMediaObjectsHandler({targets}) {
116-
return await this._dictionaryDatabase.getMediaObjects(targets);
126+
/** @type {import('offscreen').ApiHandler<'databaseDrawMediaOffscreen'>} */
127+
async _drawMediaHandler({targets}) {
128+
await this._dictionaryDatabase.drawMedia(targets);
117129
}
118130

119131
/** @type {import('offscreen').ApiHandler<'translatorPrepareOffscreen'>} */
@@ -136,11 +148,11 @@ export class Offscreen {
136148
const enabledDictionaryMap = new Map(options.enabledDictionaryMap);
137149
const excludeDictionaryDefinitions = (
138150
options.excludeDictionaryDefinitions !== null ?
139-
new Set(options.excludeDictionaryDefinitions) :
140-
null
151+
new Set(options.excludeDictionaryDefinitions) :
152+
null
141153
);
142154
const textReplacements = options.textReplacements.map((group) => {
143-
if (group === null) { return null; }
155+
if (group === null) {return null;}
144156
return group.map((opt) => {
145157
// https://stackoverflow.com/a/33642463
146158
const match = opt.pattern.match(/\/(.*?)\/([a-z]*)?$/i);
@@ -172,4 +184,11 @@ export class Offscreen {
172184
_onMessage({action, params}, _sender, callback) {
173185
return invokeApiMapHandler(this._apiMap, action, params, [], callback);
174186
}
187+
188+
/** @param {{action: string, params: any}} obj */
189+
_onSWMessage({action, params}) {
190+
if (action === 'drawMedia') {
191+
this._dictionaryDatabase.drawMedia(params);
192+
}
193+
}
175194
}

ext/js/comm/api.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -255,12 +255,12 @@ export class API {
255255
}
256256

257257
/**
258-
* @param {import('api').ApiParam<'getMediaObjects', 'targets'>} targets
259-
* @returns {Promise<import('api').ApiReturn<'getMediaObjects'>>}
258+
* @param {import('api').ApiParam<'drawMedia', 'targets'>} targets
259+
* @returns {Promise<import('api').ApiReturn<'drawMedia'>>}
260260
*/
261-
getMediaObjects(targets) {
262-
console.log('getMediaObjects', targets);
263-
return this._invoke('getMediaObjects', {targets});
261+
drawMedia(targets) {
262+
console.log('drawMedia', targets);
263+
return this._invoke('drawMedia', {targets});
264264
}
265265

266266
/**
@@ -399,10 +399,10 @@ export class API {
399399
if (response !== null && typeof response === 'object') {
400400
const {error} = /** @type {import('core').UnknownObject} */ (response);
401401
if (typeof error !== 'undefined') {
402-
reject(ExtensionError.deserialize(/** @type {import('core').SerializedError} */ (error)));
402+
reject(ExtensionError.deserialize(/** @type {import('core').SerializedError} */(error)));
403403
} else {
404404
const {result} = /** @type {import('core').UnknownObject} */ (response);
405-
resolve(/** @type {import('api').ApiReturn<TAction>} */ (result));
405+
resolve(/** @type {import('api').ApiReturn<TAction>} */(result));
406406
}
407407
} else {
408408
const message = response === null ? 'Unexpected null response. You may need to refresh the page.' : `Unexpected response of type ${typeof response}. You may need to refresh the page.`;

ext/js/comm/frame-client.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ export class FrameClient {
8585
return new Promise((resolve, reject) => {
8686
/** @type {Map<string, string>} */
8787
const tokenMap = new Map();
88+
/** @type {MessagePort | null} */
89+
let messagePort = null;
8890
/** @type {?import('core').Timeout} */
8991
let timer = null;
9092
const deferPromiseDetails = /** @type {import('core').DeferredPromiseDetails<void>} */ (deferPromise());
@@ -95,9 +97,10 @@ export class FrameClient {
9597
/**
9698
* @param {string} action
9799
* @param {import('core').SerializableObject} params
100+
* @param {Transferable[]} transfers
98101
* @throws {Error}
99102
*/
100-
const postMessage = (action, params) => {
103+
const postMessage = (action, params, transfers) => {
101104
const contentWindow = frame.contentWindow;
102105
if (contentWindow === null) { throw new Error('Frame missing content window'); }
103106

@@ -109,7 +112,7 @@ export class FrameClient {
109112
}
110113
if (!validOrigin) { throw new Error('Unexpected frame origin'); }
111114

112-
contentWindow.postMessage({action, params}, targetOrigin);
115+
contentWindow.postMessage({action, params}, targetOrigin, transfers);
113116
};
114117

115118
/** @type {import('extension').ChromeRuntimeOnMessageCallback<import('application').ApiMessageAny>} */
@@ -132,10 +135,12 @@ export class FrameClient {
132135
switch (action) {
133136
case 'frameEndpointReady':
134137
{
138+
const mc = new MessageChannel();
135139
const {secret} = params;
136140
const token = generateId(16);
137141
tokenMap.set(secret, token);
138-
postMessage('frameEndpointConnect', {secret, token, hostFrameId});
142+
messagePort = mc.port1;
143+
postMessage('frameEndpointConnect', {secret, token, hostFrameId}, [mc.port2]);
139144
}
140145
break;
141146
case 'frameEndpointConnected':
@@ -176,6 +181,9 @@ export class FrameClient {
176181
if (timer === null) { return; } // Done
177182
clearTimeout(timer);
178183
timer = null;
184+
if (messagePort !== null) {
185+
messagePort.close();
186+
}
179187

180188
frameLoadedResolve = null;
181189
if (frameLoadedReject !== null) {

ext/js/comm/frame-endpoint.js

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818

1919
import {EventListenerCollection} from '../core/event-listener-collection.js';
20+
import {log} from '../core/log.js';
2021
import {generateId} from '../core/utilities.js';
2122

2223
export class FrameEndpoint {
@@ -68,21 +69,44 @@ export class FrameEndpoint {
6869
_onMessage(event) {
6970
if (this._token !== null) { return; } // Already initialized
7071

71-
const {data} = event;
72-
if (typeof data !== 'object' || data === null) { return; } // Invalid message
72+
const {data, ports} = event;
73+
if (typeof data !== 'object' || data === null) {
74+
log.error('Invalid message');
75+
return;
76+
}
7377

7478
const {action} = /** @type {import('core').SerializableObject} */ (data);
75-
if (action !== 'frameEndpointConnect') { return; } // Invalid message
79+
if (action !== 'frameEndpointConnect') {
80+
log.error('Invalid action');
81+
return;
82+
}
7683

7784
const {params} = /** @type {import('core').SerializableObject} */ (data);
78-
if (typeof params !== 'object' || params === null) { return; } // Invalid data
85+
if (typeof params !== 'object' || params === null) {
86+
log.error('Invalid data');
87+
return;
88+
}
7989

8090
const {secret} = /** @type {import('core').SerializableObject} */ (params);
81-
if (secret !== this._secret) { return; } // Invalid authentication
91+
if (secret !== this._secret) {
92+
log.error('Invalid authentication');
93+
return;
94+
}
8295

8396
const {token, hostFrameId} = /** @type {import('core').SerializableObject} */ (params);
84-
if (typeof token !== 'string' || typeof hostFrameId !== 'number') { return; } // Invalid target
97+
if (typeof token !== 'string' || typeof hostFrameId !== 'number') {
98+
log.error('Invalid target');
99+
return;
100+
}
101+
102+
if (typeof ports !== 'object' || ports.length !== 1) {
103+
log.error('Invalid transfer');
104+
return;
105+
}
85106

107+
void navigator.serviceWorker.ready.then((swr) => {
108+
swr.active?.postMessage('mcp', [ports[0]]);
109+
});
86110
this._token = token;
87111

88112
this._eventListeners.removeAllEventListeners();

0 commit comments

Comments
 (0)