Source code
Revision control
Copy as Markdown
Other Tools
/* 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
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
* @param {number} options.innerWindowId
*/
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, signal that the process can be destroyed.
*/
TE_destroyEngineProcess() {
this.sendAsyncMessage("TranslationsEngine:DestroyEngineProcess");
}
}