From 40a355a42d4a9444dc753c04c6608dade2f06a23 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:13:27 +0200 Subject: Adding upstream version 125.0.1. Signed-off-by: Daniel Baumann --- .../components/ml/content/EngineProcess.sys.mjs | 241 +++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 toolkit/components/ml/content/EngineProcess.sys.mjs (limited to 'toolkit/components/ml/content/EngineProcess.sys.mjs') diff --git a/toolkit/components/ml/content/EngineProcess.sys.mjs b/toolkit/components/ml/content/EngineProcess.sys.mjs new file mode 100644 index 0000000000..36a9381192 --- /dev/null +++ b/toolkit/components/ml/content/EngineProcess.sys.mjs @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + HiddenFrame: "resource://gre/modules/HiddenFrame.sys.mjs", +}); + +/** + * @typedef {import("../actors/MLEngineParent.sys.mjs").MLEngineParent} MLEngineParent + */ + +/** + * @typedef {import("../../translations/actors/TranslationsEngineParent.sys.mjs").TranslationsEngineParent} TranslationsEngineParent + */ + +/** + * This class controls the life cycle of the engine process used both in the + * Translations engine and the MLEngine component. + */ +export class EngineProcess { + /** + * @type {Promise<{ hiddenFrame: HiddenFrame, actor: TranslationsEngineParent }> | null} + */ + + /** @type {Promise | null} */ + static #hiddenFrame = null; + /** @type {Promise | null} */ + static translationsEngineParent = null; + /** @type {Promise | null} */ + static mlEngineParent = null; + + /** @type {((actor: TranslationsEngineParent) => void) | null} */ + resolveTranslationsEngineParent = null; + + /** @type {((actor: MLEngineParent) => void) | null} */ + resolveMLEngineParent = null; + + /** + * See if all engines are terminated. This is useful for testing. + * + * @returns {boolean} + */ + static areAllEnginesTerminated() { + return ( + !EngineProcess.#hiddenFrame && + !EngineProcess.translationsEngineParent && + !EngineProcess.mlEngineParent + ); + } + + /** + * @returns {Promise} + */ + static async getTranslationsEngineParent() { + if (!this.translationsEngineParent) { + this.translationsEngineParent = this.#attachBrowser({ + id: "translations-engine-browser", + url: "chrome://global/content/translations/translations-engine.html", + resolverName: "resolveTranslationsEngineParent", + }); + } + return this.translationsEngineParent; + } + + /** + * @returns {Promise} + */ + static async getMLEngineParent() { + if (!this.mlEngineParent) { + this.mlEngineParent = this.#attachBrowser({ + id: "ml-engine-browser", + url: "chrome://global/content/ml/MLEngine.html", + resolverName: "resolveMLEngineParent", + }); + } + return this.mlEngineParent; + } + + /** + * @param {object} config + * @param {string} config.url + * @param {string} config.id + * @param {string} config.resolverName + * @returns {Promise} + */ + static async #attachBrowser({ url, id, resolverName }) { + const hiddenFrame = await this.#getHiddenFrame(); + const chromeWindow = await hiddenFrame.get(); + const doc = chromeWindow.document; + + if (doc.getElementById(id)) { + throw new Error( + "Attempting to append the translations-engine.html when one " + + "already exists." + ); + } + + const browser = doc.createXULElement("browser"); + browser.setAttribute("id", id); + browser.setAttribute("remote", "true"); + browser.setAttribute("remoteType", "web"); + browser.setAttribute("disableglobalhistory", "true"); + browser.setAttribute("type", "content"); + browser.setAttribute("src", url); + + ChromeUtils.addProfilerMarker( + "EngineProcess", + {}, + `Creating the "${id}" process` + ); + doc.documentElement.appendChild(browser); + + const { promise, resolve } = Promise.withResolvers(); + + // The engine parents must resolve themselves when they are ready. + this[resolverName] = resolve; + + return promise; + } + + /** + * @returns {HiddenFrame} + */ + static async #getHiddenFrame() { + if (!EngineProcess.#hiddenFrame) { + EngineProcess.#hiddenFrame = new lazy.HiddenFrame(); + } + return EngineProcess.#hiddenFrame; + } + + /** + * Destroy the translations engine, and remove the hidden frame if no other + * engines exist. + */ + static destroyTranslationsEngine() { + return this.#destroyEngine({ + id: "translations-engine-browser", + keyName: "translationsEngineParent", + }); + } + + /** + * Destroy the ML engine, and remove the hidden frame if no other engines exist. + */ + static destroyMLEngine() { + return this.#destroyEngine({ + id: "ml-engine-browser", + keyName: "mlEngineParent", + }); + } + + /** + * Destroy the specified engine and maybe the entire hidden frame as well if no engines + * are remaining. + */ + static #destroyEngine({ id, keyName }) { + ChromeUtils.addProfilerMarker( + "EngineProcess", + {}, + `Destroying the "${id}" engine` + ); + + const actorShutdown = this.forceActorShutdown(id, keyName).catch( + error => void console.error(error) + ); + + this[keyName] = null; + + const hiddenFrame = EngineProcess.#hiddenFrame; + if (hiddenFrame && !this.translationsEngineParent && !this.mlEngineParent) { + EngineProcess.#hiddenFrame = null; + + // Both actors are destroyed, also destroy the hidden frame. + actorShutdown.then(() => { + // Double check a race condition that no new actors have been created during + // shutdown. + if (this.translationsEngineParent && this.mlEngineParent) { + return; + } + if (!hiddenFrame) { + return; + } + hiddenFrame.destroy(); + ChromeUtils.addProfilerMarker( + "EngineProcess", + {}, + `Removing the hidden frame` + ); + }); + } + + // Infallibly resolve the promise even if there are errors. + return Promise.resolve(); + } + + /** + * Shut down an actor and remove its element. + * + * @param {string} id + * @param {string} keyName + */ + static async forceActorShutdown(id, keyName) { + const actorPromise = this[keyName]; + if (!actorPromise) { + return; + } + + let actor; + try { + actor = await actorPromise; + } catch { + // The actor failed to initialize, so it doesn't need to be shut down. + return; + } + + // Shut down the actor. + try { + await actor.forceShutdown(); + } catch (error) { + console.error("Failed to shut down the actor " + id, error); + return; + } + + if (!EngineProcess.#hiddenFrame) { + // The hidden frame was already removed. + return; + } + + // Remove the element. + const chromeWindow = EngineProcess.#hiddenFrame.getWindow(); + const doc = chromeWindow.document; + const element = doc.getElementById(id); + if (!element) { + console.error("Could not find the element for " + id); + return; + } + element.remove(); + } +} -- cgit v1.2.3