255 lines
7 KiB
JavaScript
255 lines
7 KiB
JavaScript
/* 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",
|
|
});
|
|
});
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
LanguageDetector:
|
|
"resource://gre/modules/translation/LanguageDetector.sys.mjs",
|
|
});
|
|
|
|
/**
|
|
* @typedef {import("./TranslationsChild.sys.mjs").TranslationsEngine} TranslationsEngine
|
|
* @typedef {import("./TranslationsChild.sys.mjs").SupportedLanguages} SupportedLanguages
|
|
*/
|
|
|
|
/**
|
|
* The AboutTranslationsChild is responsible for coordinating what privileged APIs
|
|
* are exposed to the un-privileged scope of the about:translations page.
|
|
*/
|
|
export class AboutTranslationsChild extends JSWindowActorChild {
|
|
/**
|
|
* The translations engine uses text translations by default in about:translations,
|
|
* but it can be changed to translate HTML by setting this pref to true. This is
|
|
* useful for manually testing HTML translation behavior, but is not useful to surface
|
|
* as a user-facing feature.
|
|
*
|
|
* @type {bool}
|
|
*/
|
|
#isHtmlTranslation = Services.prefs.getBoolPref(
|
|
"browser.translations.useHTML"
|
|
);
|
|
|
|
handleEvent(event) {
|
|
if (event.type === "DOMDocElementInserted") {
|
|
this.#exportFunctions();
|
|
}
|
|
|
|
if (
|
|
event.type === "DOMContentLoaded" &&
|
|
Services.prefs.getBoolPref("browser.translations.enable")
|
|
) {
|
|
this.#sendEventToContent({ type: "enable" });
|
|
}
|
|
}
|
|
|
|
receiveMessage({ name, data }) {
|
|
switch (name) {
|
|
case "AboutTranslations:SendTranslationsPort": {
|
|
const { fromLanguage, toLanguage, port } = data;
|
|
const transferables = [port];
|
|
this.contentWindow.postMessage(
|
|
{
|
|
type: "GetTranslationsPort",
|
|
fromLanguage,
|
|
toLanguage,
|
|
port,
|
|
},
|
|
"*",
|
|
transferables
|
|
);
|
|
break;
|
|
}
|
|
default:
|
|
throw new Error("Unknown AboutTranslations message: " + name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {object} detail
|
|
*/
|
|
#sendEventToContent(detail) {
|
|
this.contentWindow.dispatchEvent(
|
|
new this.contentWindow.CustomEvent("AboutTranslationsChromeToContent", {
|
|
detail: Cu.cloneInto(detail, this.contentWindow),
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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 AboutTranslations 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));
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Export any of the child functions that start with "AT_" to the unprivileged content
|
|
* page. This restricts the security capabilities of the the content page.
|
|
*/
|
|
#exportFunctions() {
|
|
const window = this.contentWindow;
|
|
|
|
const fns = [
|
|
"AT_log",
|
|
"AT_logError",
|
|
"AT_getAppLocale",
|
|
"AT_getSupportedLanguages",
|
|
"AT_isTranslationEngineSupported",
|
|
"AT_isHtmlTranslation",
|
|
"AT_createTranslationsPort",
|
|
"AT_identifyLanguage",
|
|
"AT_getScriptDirection",
|
|
];
|
|
for (const name of fns) {
|
|
Cu.exportFunction(this[name].bind(this), window, { defineAs: name });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log messages if "browser.translations.logLevel" is set to "All".
|
|
*
|
|
* @param {...any} args
|
|
*/
|
|
AT_log(...args) {
|
|
lazy.console.log(...args);
|
|
}
|
|
|
|
/**
|
|
* Report an error to the console.
|
|
*
|
|
* @param {...any} args
|
|
*/
|
|
AT_logError(...args) {
|
|
lazy.console.error(...args);
|
|
}
|
|
|
|
/**
|
|
* Returns the app's locale.
|
|
*
|
|
* @returns {Intl.Locale}
|
|
*/
|
|
AT_getAppLocale() {
|
|
return Services.locale.appLocaleAsBCP47;
|
|
}
|
|
|
|
/**
|
|
* Wire this function to the TranslationsChild.
|
|
*
|
|
* @returns {Promise<SupportedLanguages>}
|
|
*/
|
|
AT_getSupportedLanguages() {
|
|
return this.#convertToContentPromise(
|
|
this.sendQuery("AboutTranslations:GetSupportedLanguages").then(data =>
|
|
Cu.cloneInto(data, this.contentWindow)
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Does this device support the translation engine?
|
|
*
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
AT_isTranslationEngineSupported() {
|
|
return this.#convertToContentPromise(
|
|
this.sendQuery("AboutTranslations:IsTranslationsEngineSupported")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Expose the #isHtmlTranslation property.
|
|
*
|
|
* @returns {bool}
|
|
*/
|
|
AT_isHtmlTranslation() {
|
|
return this.#isHtmlTranslation;
|
|
}
|
|
|
|
/**
|
|
* Requests a port to the TranslationsEngine process. An engine will be created on
|
|
* the fly for translation requests through this port. This port is unique to its
|
|
* language pair. In order to translate a different language pair, a new port must be
|
|
* created for that pair. The lifecycle of the engine is managed by the
|
|
* TranslationsEngine.
|
|
*
|
|
* @param {string} fromLanguage
|
|
* @param {string} toLanguage
|
|
* @returns {void}
|
|
*/
|
|
AT_createTranslationsPort(fromLanguage, toLanguage) {
|
|
this.sendAsyncMessage("AboutTranslations:GetTranslationsPort", {
|
|
fromLanguage,
|
|
toLanguage,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Attempts to identify the human language in which the message is written.
|
|
*
|
|
* @param {string} message
|
|
* @returns {Promise<{ langTag: string, confidence: number }>}
|
|
*/
|
|
AT_identifyLanguage(message) {
|
|
return this.#convertToContentPromise(
|
|
lazy.LanguageDetector.detectLanguage(message).then(data =>
|
|
Cu.cloneInto(
|
|
// This language detector reports confidence as a boolean instead of
|
|
// a percentage, so we need to map the confidence to 0.0 or 1.0.
|
|
{ langTag: data.language, confidence: data.confident ? 1.0 : 0.0 },
|
|
this.contentWindow
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* TODO - Remove this when Intl.Locale.prototype.textInfo is available to
|
|
* content scripts.
|
|
*
|
|
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo
|
|
* https://bugzilla.mozilla.org/show_bug.cgi?id=1693576
|
|
*
|
|
* @param {string} locale
|
|
* @returns {string}
|
|
*/
|
|
AT_getScriptDirection(locale) {
|
|
return Services.intl.getScriptDirection(locale);
|
|
}
|
|
}
|