summaryrefslogtreecommitdiffstats
path: root/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs')
-rw-r--r--toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs207
1 files changed, 207 insertions, 0 deletions
diff --git a/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs b/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs
new file mode 100644
index 0000000000..a4ab8e2640
--- /dev/null
+++ b/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs
@@ -0,0 +1,207 @@
+/* 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.defineLazyGetter(lazy, "console", () => {
+ return console.createInstance({
+ maxLogLevelPref: "browser.translations.logLevel",
+ prefix: "Translations",
+ });
+});
+
+/**
+ * The engine child is responsible for exposing privileged code to the un-privileged
+ * space the engine runs in.
+ */
+export class TranslationsEngineChild extends JSWindowActorChild {
+ /**
+ * The resolve function for the Promise returned by the
+ * "TranslationsEngine:ForceShutdown" message.
+ * @type {null | () => {}}
+ */
+ #resolveForceShutdown = null;
+
+ actorCreated() {
+ this.#exportFunctions();
+ }
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "DOMContentLoaded":
+ this.sendAsyncMessage("TranslationsEngine:Ready");
+ break;
+ }
+ }
+
+ // eslint-disable-next-line consistent-return
+ async receiveMessage({ name, data }) {
+ switch (name) {
+ case "TranslationsEngine:StartTranslation": {
+ const { fromLanguage, toLanguage, innerWindowId, port } = data;
+ const transferables = [port];
+ const message = {
+ type: "StartTranslation",
+ fromLanguage,
+ toLanguage,
+ innerWindowId,
+ port,
+ };
+ this.contentWindow.postMessage(message, "*", transferables);
+ break;
+ }
+ case "TranslationsEngine:DiscardTranslations": {
+ const { innerWindowId } = data;
+ this.contentWindow.postMessage({
+ type: "DiscardTranslations",
+ innerWindowId,
+ });
+ break;
+ }
+ case "TranslationsEngine:ForceShutdown": {
+ this.contentWindow.postMessage({
+ type: "ForceShutdown",
+ });
+ return new Promise(resolve => {
+ this.#resolveForceShutdown = resolve;
+ });
+ }
+ default:
+ console.error("Unknown message received", name);
+ }
+ }
+
+ /**
+ * Export any of the child functions that start with "TE_" to the unprivileged content
+ * page. This restricts the security capabilities of the content page.
+ */
+ #exportFunctions() {
+ const fns = [
+ "TE_addProfilerMarker",
+ "TE_getLogLevel",
+ "TE_log",
+ "TE_logError",
+ "TE_requestEnginePayload",
+ "TE_reportEngineStatus",
+ "TE_resolveForceShutdown",
+ "TE_destroyEngineProcess",
+ ];
+ for (const defineAs of fns) {
+ Cu.exportFunction(this[defineAs].bind(this), this.contentWindow, {
+ defineAs,
+ });
+ }
+ }
+
+ /**
+ * A privileged promise can't be used in the content page, so convert a privileged
+ * promise into a content one.
+ *
+ * @param {Promise<any>} promise
+ * @returns {Promise<any>}
+ */
+ #convertToContentPromise(promise) {
+ return new this.contentWindow.Promise((resolve, reject) =>
+ promise.then(resolve, error => {
+ let contentWindow;
+ try {
+ contentWindow = this.contentWindow;
+ } catch (error) {
+ // The content window is no longer available.
+ reject();
+ return;
+ }
+ // Create an error in the content window, if the content window is still around.
+ let message = "An error occured in the TranslationsEngine actor.";
+ if (typeof error === "string") {
+ message = error;
+ }
+ if (typeof error?.message === "string") {
+ message = error.message;
+ }
+ if (typeof error?.stack === "string") {
+ message += `\n\nOriginal stack:\n\n${error.stack}\n`;
+ }
+
+ reject(new contentWindow.Error(message));
+ })
+ );
+ }
+
+ /**
+ * @param {Object} options
+ * @param {number?} options.startTime
+ * @param {string} options.message
+ */
+ TE_addProfilerMarker({ startTime, message, innerWindowId }) {
+ ChromeUtils.addProfilerMarker(
+ "TranslationsEngine",
+ { startTime, innerWindowId },
+ message
+ );
+ }
+
+ /**
+ * Pass the message from content that the engines were shut down.
+ */
+ TE_resolveForceShutdown() {
+ this.#resolveForceShutdown();
+ }
+
+ /**
+ * @returns {string}
+ */
+ TE_getLogLevel() {
+ return Services.prefs.getCharPref("browser.translations.logLevel");
+ }
+
+ /**
+ * Log messages if "browser.translations.logLevel" is set to "All".
+ *
+ * @param {...any} args
+ */
+ TE_log(...args) {
+ lazy.console.log(...args);
+ }
+
+ /**
+ * Report an error to the console.
+ *
+ * @param {...any} args
+ */
+ TE_logError(...args) {
+ lazy.console.error(...args);
+ }
+
+ /**
+ * @param {string} fromLanguage
+ * @param {string} toLanguage
+ */
+ TE_requestEnginePayload(fromLanguage, toLanguage) {
+ return this.#convertToContentPromise(
+ this.sendQuery("TranslationsEngine:RequestEnginePayload", {
+ fromLanguage,
+ toLanguage,
+ })
+ );
+ }
+
+ /**
+ * @param {number} innerWindowId
+ * @param {"ready" | "error"} status
+ */
+ TE_reportEngineStatus(innerWindowId, status) {
+ this.sendAsyncMessage("TranslationsEngine:ReportEngineStatus", {
+ innerWindowId,
+ status,
+ });
+ }
+
+ /**
+ * No engines are still alive, destroy the process.
+ */
+ TE_destroyEngineProcess() {
+ this.sendAsyncMessage("TranslationsEngine:DestroyEngineProcess");
+ }
+}