Skip to content

Commit

Permalink
[WIP] attempt to use OffscreenCanvas
Browse files Browse the repository at this point in the history
  • Loading branch information
djahandarie committed Oct 13, 2024
1 parent 7553db1 commit 343925d
Show file tree
Hide file tree
Showing 23 changed files with 398 additions and 271 deletions.
4 changes: 1 addition & 3 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"no-shadow": ["error", {"builtinGlobals": false}],
"no-shadow-restricted-names": "error",
"no-template-curly-in-string": "error",
"no-undef": "error",
"no-undef": "off",
"no-undefined": "error",
"no-underscore-dangle": ["error", {"allowAfterThis": true, "allowAfterSuper": false, "allowAfterThisConstructor": false}],
"no-unexpected-multiline": "error",
Expand Down Expand Up @@ -581,7 +581,6 @@
"ext/js/core/to-error.js",
"ext/js/core/utilities.js",
"ext/js/data/database.js",
"ext/js/dictionary/dictionary-database.js",
"ext/js/dictionary/dictionary-importer.js",
"ext/js/dictionary/dictionary-worker-handler.js",
"ext/js/dictionary/dictionary-worker-main.js",
Expand Down Expand Up @@ -623,7 +622,6 @@
"ext/js/data/json-schema.js",
"ext/js/data/options-util.js",
"ext/js/data/permissions-util.js",
"ext/js/dictionary/dictionary-database.js",
"ext/js/dom/native-simple-dom-parser.js",
"ext/js/dom/simple-dom-parser.js",
"ext/js/extension/environment.js",
Expand Down
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"eslint.format.enable": true,
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
"javascript.format.enable": false,
"typescript.format.enable": false,
"javascript.preferences.importModuleSpecifierEnding": "js",
"editor.tabSize": 4,
"editor.insertSpaces": true,
Expand Down
2 changes: 2 additions & 0 deletions ext/css/display.css
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,8 @@ button.action-button:active {
.entry {
padding: var(--entry-vertical-padding) var(--entry-horizontal-padding);
position: relative;
content-visibility: auto;
contain-intrinsic-height: auto 500px;
}
.entry+.entry {
border-top: var(--thin-border-size) solid var(--light-border-color);
Expand Down
3 changes: 0 additions & 3 deletions ext/css/structured-content.css
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@
outline: none;
width: 100%;
}
.gloss-image:not([src]) {
display: none;
}
.gloss-image-link[data-image-rendering=pixelated] .gloss-image {
image-rendering: auto;
image-rendering: -moz-crisp-edges;
Expand Down
19 changes: 14 additions & 5 deletions ext/js/background/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ export class Backend {
this._translator = new Translator(this._dictionaryDatabase);
/** @type {ClipboardReader|ClipboardReaderProxy} */
this._clipboardReader = new ClipboardReader(
// eslint-disable-next-line no-undef
(typeof document === 'object' && document !== null ? document : null),
'#clipboard-paste-target',
'#clipboard-rich-content-paste-target',
Expand Down Expand Up @@ -172,7 +171,7 @@ export class Backend {
['getDictionaryInfo', this._onApiGetDictionaryInfo.bind(this)],
['purgeDatabase', this._onApiPurgeDatabase.bind(this)],
['getMedia', this._onApiGetMedia.bind(this)],
['getMediaObjects', this._onApiGetMediaObjects.bind(this)],
['drawMedia', this._onApiDrawMedia.bind(this)],
['logGenericErrorBackend', this._onApiLogGenericErrorBackend.bind(this)],
['logIndicatorClear', this._onApiLogIndicatorClear.bind(this)],
['modifySettings', this._onApiModifySettings.bind(this)],
Expand Down Expand Up @@ -242,6 +241,15 @@ export class Backend {
const onMessage = this._onMessageWrapper.bind(this);
chrome.runtime.onMessage.addListener(onMessage);

// This is for receiving messages sent with navigator.serviceWorker, which has the benefit of being able to transfer objects, but doesn't accept callbacks
addEventListener('message', (event) => {
if (event.data.action === 'drawMedia') {
this._dictionaryDatabase.drawMedia(event.data.params);
} else if (event.data.action === 'registerOffscreenPort' && this._offscreen !== null) {
this._offscreen.registerOffscreenPort(event.ports[0]);
}
});

if (this._canObservePermissionsChanges()) {
const onPermissionsChanged = this._onWebExtensionEventWrapper(this._onPermissionsChanged.bind(this));
chrome.permissions.onAdded.addListener(onPermissionsChanged);
Expand Down Expand Up @@ -796,9 +804,10 @@ export class Backend {
return await this._getNormalizedDictionaryDatabaseMedia(targets);
}

/** @type {import('api').ApiHandler<'getMediaObjects'>} */
async _onApiGetMediaObjects({targets}) {
return await this._dictionaryDatabase.getMediaObjects(targets);
/** @type {import('api').ApiHandler<'drawMedia'>} */
async _onApiDrawMedia({targets}) {
console.log('_onApiDrawMedia', targets);
await this._dictionaryDatabase.drawMedia(targets);
}

/** @type {import('api').ApiHandler<'logGenericErrorBackend'>} */
Expand Down
30 changes: 25 additions & 5 deletions ext/js/background/offscreen-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export class OffscreenProxy {
this._webExtension = webExtension;
/** @type {?Promise<void>} */
this._creatingOffscreen = null;

/** @type {?MessagePort} */
this._currentOffscreenPort = null;
}

/**
Expand Down Expand Up @@ -136,6 +139,24 @@ export class OffscreenProxy {
}
return response.result;
}

/**
* @param {MessagePort} port
*/
async registerOffscreenPort(port) {
if (this._currentOffscreenPort) {
this._currentOffscreenPort.close();
}
this._currentOffscreenPort = port;
}

/**
* @param {any} message
* @param {Transferable[]} transfers
*/
sendMessageViaPort(message, transfers) {
this._currentOffscreenPort?.postMessage(message, transfers);
}
}

export class DictionaryDatabaseProxy {
Expand Down Expand Up @@ -178,12 +199,11 @@ export class DictionaryDatabaseProxy {
}

/**
* @param {import('dictionary-database').MediaRequest[]} targets
* @returns {Promise<import('dictionary-database').MediaObject[]>}
* @param {import('dictionary-database').DrawMediaRequest[]} targets
* @returns {Promise<void>}
*/
async getMediaObjects(targets) {
console.log('offscreen getMediaObjects', targets);
return await this._offscreen.sendMessagePromise({action: 'databaseGetMediaObjectsOffscreen', params: {targets}});
async drawMedia(targets) {
this._offscreen.sendMessageViaPort({action: 'drawMedia', params: targets}, targets.map((t) => t.canvas));
}
}

Expand Down
55 changes: 37 additions & 18 deletions ext/js/background/offscreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,19 @@ export class Offscreen {
/* eslint-disable @stylistic/no-multi-spaces */
/** @type {import('offscreen').ApiMap} */
this._apiMap = createApiMap([
['clipboardGetTextOffscreen', this._getTextHandler.bind(this)],
['clipboardGetImageOffscreen', this._getImageHandler.bind(this)],
['clipboardGetTextOffscreen', this._getTextHandler.bind(this)],
['clipboardGetImageOffscreen', this._getImageHandler.bind(this)],
['clipboardSetBrowserOffscreen', this._setClipboardBrowser.bind(this)],
['databasePrepareOffscreen', this._prepareDatabaseHandler.bind(this)],
['getDictionaryInfoOffscreen', this._getDictionaryInfoHandler.bind(this)],
['databasePurgeOffscreen', this._purgeDatabaseHandler.bind(this)],
['databaseGetMediaOffscreen', this._getMediaHandler.bind(this)],
['databaseGetMediaObjectsOffscreen', this._getMediaObjectsHandler.bind(this)],
['translatorPrepareOffscreen', this._prepareTranslatorHandler.bind(this)],
['findKanjiOffscreen', this._findKanjiHandler.bind(this)],
['findTermsOffscreen', this._findTermsHandler.bind(this)],
['getTermFrequenciesOffscreen', this._getTermFrequenciesHandler.bind(this)],
['clearDatabaseCachesOffscreen', this._clearDatabaseCachesHandler.bind(this)],
['databasePrepareOffscreen', this._prepareDatabaseHandler.bind(this)],
['getDictionaryInfoOffscreen', this._getDictionaryInfoHandler.bind(this)],
['databasePurgeOffscreen', this._purgeDatabaseHandler.bind(this)],
['databaseGetMediaOffscreen', this._getMediaHandler.bind(this)],
['databaseDrawMediaOffscreen', this._drawMediaHandler.bind(this)],
['translatorPrepareOffscreen', this._prepareTranslatorHandler.bind(this)],
['findKanjiOffscreen', this._findKanjiHandler.bind(this)],
['findTermsOffscreen', this._findTermsHandler.bind(this)],
['getTermFrequenciesOffscreen', this._getTermFrequenciesHandler.bind(this)],
['clearDatabaseCachesOffscreen', this._clearDatabaseCachesHandler.bind(this)]
]);
/* eslint-enable @stylistic/no-multi-spaces */

Expand All @@ -69,6 +69,18 @@ export class Offscreen {
/** */
prepare() {
chrome.runtime.onMessage.addListener(this._onMessage.bind(this));

const registerPort = () => {
const mc = new MessageChannel();
mc.port1.onmessage = (e) => {
this._onSWMessage(e.data);
};
void navigator.serviceWorker.ready.then((swr) => {
swr.active?.postMessage({action: 'registerOffscreenPort'}, [mc.port2]);
});
};
navigator.serviceWorker.addEventListener("controllerchange", registerPort);
registerPort();
}

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

/** @type {import('offscreen').ApiHandler<'databaseGetMediaObjectsOffscreen'>} */
async _getMediaObjectsHandler({targets}) {
return await this._dictionaryDatabase.getMediaObjects(targets);
/** @type {import('offscreen').ApiHandler<'databaseDrawMediaOffscreen'>} */
async _drawMediaHandler({targets}) {
await this._dictionaryDatabase.drawMedia(targets);
}

/** @type {import('offscreen').ApiHandler<'translatorPrepareOffscreen'>} */
Expand All @@ -136,11 +148,11 @@ export class Offscreen {
const enabledDictionaryMap = new Map(options.enabledDictionaryMap);
const excludeDictionaryDefinitions = (
options.excludeDictionaryDefinitions !== null ?
new Set(options.excludeDictionaryDefinitions) :
null
new Set(options.excludeDictionaryDefinitions) :
null
);
const textReplacements = options.textReplacements.map((group) => {
if (group === null) { return null; }
if (group === null) {return null;}
return group.map((opt) => {
// https://stackoverflow.com/a/33642463
const match = opt.pattern.match(/\/(.*?)\/([a-z]*)?$/i);
Expand Down Expand Up @@ -172,4 +184,11 @@ export class Offscreen {
_onMessage({action, params}, _sender, callback) {
return invokeApiMapHandler(this._apiMap, action, params, [], callback);
}

/** @param {{action: string, params: any}} obj */
_onSWMessage({action, params}) {
if (action === 'drawMedia') {
this._dictionaryDatabase.drawMedia(params);
}
}
}
14 changes: 7 additions & 7 deletions ext/js/comm/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,12 @@ export class API {
}

/**
* @param {import('api').ApiParam<'getMediaObjects', 'targets'>} targets
* @returns {Promise<import('api').ApiReturn<'getMediaObjects'>>}
* @param {import('api').ApiParam<'drawMedia', 'targets'>} targets
* @returns {Promise<import('api').ApiReturn<'drawMedia'>>}
*/
getMediaObjects(targets) {
console.log('getMediaObjects', targets);
return this._invoke('getMediaObjects', {targets});
drawMedia(targets) {
console.log('drawMedia', targets);
return this._invoke('drawMedia', {targets});
}

/**
Expand Down Expand Up @@ -399,10 +399,10 @@ export class API {
if (response !== null && typeof response === 'object') {
const {error} = /** @type {import('core').UnknownObject} */ (response);
if (typeof error !== 'undefined') {
reject(ExtensionError.deserialize(/** @type {import('core').SerializedError} */ (error)));
reject(ExtensionError.deserialize(/** @type {import('core').SerializedError} */(error)));
} else {
const {result} = /** @type {import('core').UnknownObject} */ (response);
resolve(/** @type {import('api').ApiReturn<TAction>} */ (result));
resolve(/** @type {import('api').ApiReturn<TAction>} */(result));
}
} else {
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.`;
Expand Down
14 changes: 11 additions & 3 deletions ext/js/comm/frame-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export class FrameClient {
return new Promise((resolve, reject) => {
/** @type {Map<string, string>} */
const tokenMap = new Map();
/** @type {MessagePort | null} */
let messagePort = null;
/** @type {?import('core').Timeout} */
let timer = null;
const deferPromiseDetails = /** @type {import('core').DeferredPromiseDetails<void>} */ (deferPromise());
Expand All @@ -95,9 +97,10 @@ export class FrameClient {
/**
* @param {string} action
* @param {import('core').SerializableObject} params
* @param {Transferable[]} transfers
* @throws {Error}
*/
const postMessage = (action, params) => {
const postMessage = (action, params, transfers) => {
const contentWindow = frame.contentWindow;
if (contentWindow === null) { throw new Error('Frame missing content window'); }

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

contentWindow.postMessage({action, params}, targetOrigin);
contentWindow.postMessage({action, params}, targetOrigin, transfers);
};

/** @type {import('extension').ChromeRuntimeOnMessageCallback<import('application').ApiMessageAny>} */
Expand All @@ -132,10 +135,12 @@ export class FrameClient {
switch (action) {
case 'frameEndpointReady':
{
const mc = new MessageChannel();
const {secret} = params;
const token = generateId(16);
tokenMap.set(secret, token);
postMessage('frameEndpointConnect', {secret, token, hostFrameId});
messagePort = mc.port1;
postMessage('frameEndpointConnect', {secret, token, hostFrameId}, [mc.port2]);
}
break;
case 'frameEndpointConnected':
Expand Down Expand Up @@ -176,6 +181,9 @@ export class FrameClient {
if (timer === null) { return; } // Done
clearTimeout(timer);
timer = null;
if (messagePort !== null) {
messagePort.close();
}

frameLoadedResolve = null;
if (frameLoadedReject !== null) {
Expand Down
36 changes: 30 additions & 6 deletions ext/js/comm/frame-endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

import {EventListenerCollection} from '../core/event-listener-collection.js';
import {log} from '../core/log.js';
import {generateId} from '../core/utilities.js';

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

const {data} = event;
if (typeof data !== 'object' || data === null) { return; } // Invalid message
const {data, ports} = event;
if (typeof data !== 'object' || data === null) {
log.error('Invalid message');
return;
}

const {action} = /** @type {import('core').SerializableObject} */ (data);
if (action !== 'frameEndpointConnect') { return; } // Invalid message
if (action !== 'frameEndpointConnect') {
log.error('Invalid action');
return;
}

const {params} = /** @type {import('core').SerializableObject} */ (data);
if (typeof params !== 'object' || params === null) { return; } // Invalid data
if (typeof params !== 'object' || params === null) {
log.error('Invalid data');
return;
}

const {secret} = /** @type {import('core').SerializableObject} */ (params);
if (secret !== this._secret) { return; } // Invalid authentication
if (secret !== this._secret) {
log.error('Invalid authentication');
return;
}

const {token, hostFrameId} = /** @type {import('core').SerializableObject} */ (params);
if (typeof token !== 'string' || typeof hostFrameId !== 'number') { return; } // Invalid target
if (typeof token !== 'string' || typeof hostFrameId !== 'number') {
log.error('Invalid target');
return;
}

if (typeof ports !== 'object' || ports.length !== 1) {
log.error('Invalid transfer');
return;
}

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

this._eventListeners.removeAllEventListeners();
Expand Down
Loading

0 comments on commit 343925d

Please sign in to comment.