607 lines
19 KiB
JavaScript
607 lines
19 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.defineESModuleGetters(lazy, {
|
|
TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
|
|
TranslationsUtils:
|
|
"chrome://global/content/translations/TranslationsUtils.mjs",
|
|
});
|
|
|
|
import { GeckoViewModule } from "resource://gre/modules/GeckoViewModule.sys.mjs";
|
|
|
|
export class GeckoViewTranslations extends GeckoViewModule {
|
|
onInit() {
|
|
debug`onInit`;
|
|
this.registerListener([
|
|
"GeckoView:Translations:Translate",
|
|
"GeckoView:Translations:RestorePage",
|
|
"GeckoView:Translations:GetNeverTranslateSite",
|
|
"GeckoView:Translations:SetNeverTranslateSite",
|
|
]);
|
|
}
|
|
|
|
onEnable() {
|
|
debug`onEnable`;
|
|
this.window.addEventListener("TranslationsParent:OfferTranslation", this);
|
|
this.window.addEventListener("TranslationsParent:LanguageState", this);
|
|
}
|
|
|
|
onDisable() {
|
|
debug`onDisable`;
|
|
this.window.removeEventListener(
|
|
"TranslationsParent:OfferTranslation",
|
|
this
|
|
);
|
|
this.window.removeEventListener("TranslationsParent:LanguageState", this);
|
|
}
|
|
|
|
onEvent(aEvent, aData, aCallback) {
|
|
debug`onEvent: event=${aEvent}, data=${aData}`;
|
|
switch (aEvent) {
|
|
case "GeckoView:Translations:Translate": {
|
|
try {
|
|
const { sourceLanguage, targetLanguage } = aData;
|
|
|
|
if (
|
|
lazy.TranslationsUtils.isLangTagValid(sourceLanguage) &&
|
|
lazy.TranslationsUtils.isLangTagValid(targetLanguage)
|
|
) {
|
|
this.getActor("Translations")
|
|
.translate(
|
|
{
|
|
sourceLanguage,
|
|
targetLanguage,
|
|
// Model variants are not currently supported. See Bug 1943444.
|
|
},
|
|
/* reportAsAutoTranslate */ false
|
|
)
|
|
.then(
|
|
() => aCallback.onSuccess(),
|
|
error => aCallback.onError(`Could not translate: ${error}`)
|
|
);
|
|
} else {
|
|
aCallback.onError(
|
|
`The language tag ${sourceLanguage} or ${targetLanguage} is not valid.`
|
|
);
|
|
}
|
|
} catch (error) {
|
|
aCallback.onError(`Could not translate: ${error}`);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case "GeckoView:Translations:RestorePage":
|
|
try {
|
|
this.getActor("Translations").restorePage();
|
|
aCallback.onSuccess();
|
|
} catch (error) {
|
|
aCallback.onError(`Could not restore page: ${error}`);
|
|
}
|
|
break;
|
|
|
|
case "GeckoView:Translations:GetNeverTranslateSite":
|
|
try {
|
|
const value =
|
|
this.getActor("Translations").shouldNeverTranslateSite();
|
|
aCallback.onSuccess(value);
|
|
} catch (error) {
|
|
aCallback.onError(`Could not set site setting: ${error}`);
|
|
}
|
|
break;
|
|
|
|
case "GeckoView:Translations:SetNeverTranslateSite":
|
|
try {
|
|
this.getActor("Translations").setNeverTranslateSitePermissions(
|
|
aData.neverTranslate
|
|
);
|
|
aCallback.onSuccess();
|
|
} catch (error) {
|
|
aCallback.onError(`Could not set site setting: ${error}`);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
handleEvent(aEvent) {
|
|
debug`handleEvent: ${aEvent.type}`;
|
|
switch (aEvent.type) {
|
|
case "TranslationsParent:OfferTranslation":
|
|
this.eventDispatcher.sendRequest({
|
|
type: "GeckoView:Translations:Offer",
|
|
});
|
|
break;
|
|
case "TranslationsParent:LanguageState": {
|
|
const {
|
|
detectedLanguages,
|
|
requestedLanguagePair,
|
|
hasVisibleChange,
|
|
error,
|
|
isEngineReady,
|
|
} = aEvent.detail.actor.languageState;
|
|
|
|
const data = {
|
|
detectedLanguages,
|
|
requestedLanguagePair,
|
|
hasVisibleChange,
|
|
error,
|
|
isEngineReady,
|
|
};
|
|
|
|
this.eventDispatcher.sendRequest({
|
|
type: "GeckoView:Translations:StateChange",
|
|
data,
|
|
});
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Runtime functionality
|
|
export const GeckoViewTranslationsSettings = {
|
|
// Helper method for retrieving language setting state and corresponding string name.
|
|
_getLanguageSettingName(langTag) {
|
|
const isAlways = lazy.TranslationsParent.shouldAlwaysTranslateLanguage({
|
|
docLangTag: langTag,
|
|
userLangTag: new Intl.Locale(Services.locale.appLocaleAsBCP47).language,
|
|
});
|
|
const isNever =
|
|
lazy.TranslationsParent.shouldNeverTranslateLanguage(langTag);
|
|
// Default setting is offer.
|
|
let setting = "offer";
|
|
|
|
if (isAlways & !isNever) {
|
|
setting = "always";
|
|
}
|
|
|
|
if (isNever & !isAlways) {
|
|
setting = "never";
|
|
}
|
|
return setting;
|
|
},
|
|
/* eslint-disable complexity */
|
|
async onEvent(aEvent, aData, aCallback) {
|
|
debug`onEvent ${aEvent} ${aData}`;
|
|
|
|
switch (aEvent) {
|
|
case "GeckoView:Translations:IsTranslationEngineSupported": {
|
|
try {
|
|
aCallback.onSuccess(
|
|
lazy.TranslationsParent.getIsTranslationsEngineSupported()
|
|
);
|
|
} catch (error) {
|
|
aCallback.onError(
|
|
`An issue occurred while checking the translations engine: ${error}`
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
case "GeckoView:Translations:PreferredLanguages": {
|
|
aCallback.onSuccess({
|
|
preferredLanguages: lazy.TranslationsParent.getPreferredLanguages(),
|
|
});
|
|
return;
|
|
}
|
|
case "GeckoView:Translations:ManageModel": {
|
|
const { language, operation, operationLevel } = aData;
|
|
if (operation === "delete") {
|
|
if (operationLevel === "all") {
|
|
lazy.TranslationsParent.deleteAllLanguageFiles().then(
|
|
function () {
|
|
aCallback.onSuccess();
|
|
},
|
|
function (error) {
|
|
aCallback.onError(
|
|
`COULD_NOT_DELETE - An issue occurred while deleting all language files: ${error}`
|
|
);
|
|
}
|
|
);
|
|
return;
|
|
}
|
|
if (operationLevel === "language") {
|
|
if (language === undefined) {
|
|
aCallback.onError(
|
|
`LANGUAGE_REQUIRED - A specified language is required language level operations.`
|
|
);
|
|
return;
|
|
}
|
|
lazy.TranslationsParent.deleteLanguageFiles(language).then(
|
|
function () {
|
|
aCallback.onSuccess();
|
|
},
|
|
function (error) {
|
|
aCallback.onError(
|
|
`COULD_NOT_DELETE - An issue occurred while deleting a language file: ${error}`
|
|
);
|
|
}
|
|
);
|
|
}
|
|
if (operationLevel === "cache") {
|
|
await lazy.TranslationsParent.deleteCachedLanguageFiles().then(
|
|
function () {
|
|
aCallback.onSuccess();
|
|
},
|
|
function (error) {
|
|
aCallback.onError(
|
|
`COULD_NOT_DELETE - An issue occurred while deleting the cache: ${error}`
|
|
);
|
|
}
|
|
);
|
|
}
|
|
} else if (operation === "download") {
|
|
if (operationLevel === "all") {
|
|
lazy.TranslationsParent.downloadAllFiles().then(
|
|
function () {
|
|
aCallback.onSuccess();
|
|
},
|
|
function (error) {
|
|
aCallback.onError(
|
|
`COULD_NOT_DOWNLOAD - An issue occurred while downloading all language files: ${error}`
|
|
);
|
|
}
|
|
);
|
|
return;
|
|
}
|
|
if (operationLevel === "language") {
|
|
if (language === undefined) {
|
|
aCallback.onError(
|
|
`LANGUAGE_REQUIRED - A specified language is required language level operations.`
|
|
);
|
|
return;
|
|
}
|
|
lazy.TranslationsParent.downloadLanguageFiles(language).then(
|
|
function () {
|
|
aCallback.onSuccess();
|
|
},
|
|
function (error) {
|
|
aCallback.onError(
|
|
`COULD_NOT_DOWNLOAD - An issue occurred while downloading a language files: ${error}`
|
|
);
|
|
}
|
|
);
|
|
}
|
|
if (operationLevel === "cache") {
|
|
aCallback.onError(
|
|
`COULD_NOT_DOWNLOAD - Downloading the cache is not a valid option. Please check the parameters and try again.
|
|
Language: ${language}, Operation: ${operation}, Operation Level: ${operationLevel}`
|
|
);
|
|
}
|
|
} else {
|
|
aCallback.onError(
|
|
`ERROR_UNKNOWN - The request to manage models appears to be malformed. Please check the parameters and try again.
|
|
Language: ${language}, Operation: ${operation}, Operation Level: ${operationLevel}`
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "GeckoView:Translations:TranslationInformation": {
|
|
if (
|
|
Cu.isInAutomation &&
|
|
Services.prefs.getBoolPref(
|
|
"browser.translations.geckoview.enableAllTestMocks",
|
|
false
|
|
)
|
|
) {
|
|
const mockResult = {
|
|
languagePairs: [
|
|
{ sourceLanguage: "en", targetLanguage: "es" },
|
|
{ sourceLanguage: "es", targetLanguage: "en" },
|
|
{ sourceLanguage: "en", targetLanguage: "es", variant: "base" },
|
|
],
|
|
sourceLanguages: [
|
|
{ langTag: "en", langTagKey: "en", displayName: "English" },
|
|
{ langTag: "es", langTagKey: "es", displayName: "Spanish" },
|
|
],
|
|
targetLanguages: [
|
|
{ langTag: "en", langTagKey: "en", displayName: "English" },
|
|
{ langTag: "es", langTagKey: "en", displayName: "Spanish" },
|
|
],
|
|
};
|
|
aCallback.onSuccess(mockResult);
|
|
return;
|
|
}
|
|
|
|
lazy.TranslationsParent.getSupportedLanguages().then(
|
|
function (value) {
|
|
aCallback.onSuccess(value);
|
|
},
|
|
function (error) {
|
|
aCallback.onError(
|
|
`Could not retrieve requested information: ${error}`
|
|
);
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
case "GeckoView:Translations:ModelInformation": {
|
|
if (
|
|
Cu.isInAutomation &&
|
|
Services.prefs.getBoolPref(
|
|
"browser.translations.geckoview.enableAllTestMocks",
|
|
false
|
|
)
|
|
) {
|
|
const mockResult = {
|
|
models: [
|
|
{
|
|
langTag: "es",
|
|
displayName: "Spanish",
|
|
isDownloaded: false,
|
|
size: 12345,
|
|
},
|
|
{
|
|
langTag: "de",
|
|
displayName: "German",
|
|
isDownloaded: false,
|
|
size: 12345,
|
|
},
|
|
],
|
|
};
|
|
aCallback.onSuccess(mockResult);
|
|
return;
|
|
}
|
|
|
|
// Helper function to process remote server records size and download state for GV use
|
|
async function _processLanguageModelData(language, remoteRecords) {
|
|
// Aggregate size of downloads, e.g., one language has many model binary files
|
|
let size = 0;
|
|
remoteRecords.forEach(item => {
|
|
size += parseInt(item.attachment.size);
|
|
});
|
|
// Check if required files are downloaded
|
|
const isDownloaded =
|
|
await lazy.TranslationsParent.hasAllFilesForLanguage(
|
|
language.langTag
|
|
);
|
|
const model = {
|
|
langTag: language.langTag,
|
|
displayName: language.displayName,
|
|
isDownloaded,
|
|
size,
|
|
};
|
|
return model;
|
|
}
|
|
|
|
// Main call to toolkit
|
|
lazy.TranslationsParent.getSupportedLanguages().then(
|
|
// Retrieve supported languages
|
|
async function (supportedLanguages) {
|
|
// Get language display information,
|
|
const languageList =
|
|
lazy.TranslationsParent.getLanguageList(supportedLanguages);
|
|
const results = [];
|
|
const pivotIsDownloaded =
|
|
await lazy.TranslationsParent.hasAllFilesForLanguage(
|
|
lazy.TranslationsParent.PIVOT_LANGUAGE
|
|
);
|
|
|
|
// For each language, process the related remote server model records
|
|
languageList.forEach(language => {
|
|
// No need to include the pivot in the download size, once it has been downloaded.
|
|
const recordsResult =
|
|
lazy.TranslationsParent.getRecordsForTranslatingToAndFromAppLanguage(
|
|
language.langTag,
|
|
/* includePivotRecords */ !pivotIsDownloaded
|
|
).then(
|
|
async function (records) {
|
|
return _processLanguageModelData(language, records);
|
|
},
|
|
function (recordError) {
|
|
aCallback.onError(
|
|
`An issue occurred while aggregating information: ${recordError}`
|
|
);
|
|
}
|
|
);
|
|
results.push(recordsResult);
|
|
});
|
|
// Aggregate records
|
|
Promise.all(results).then(models => {
|
|
const response = [];
|
|
models.forEach(item => {
|
|
// Ensures unactionable models do not appear in the list
|
|
if (parseInt(item.size) !== 0) {
|
|
response.push(item);
|
|
}
|
|
});
|
|
aCallback.onSuccess({ models: response });
|
|
});
|
|
},
|
|
function (languageError) {
|
|
aCallback.onError(
|
|
`An issue occurred while retrieving the supported languages: ${languageError}`
|
|
);
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
|
|
case "GeckoView:Translations:GetLanguageSetting": {
|
|
if (
|
|
Cu.isInAutomation &&
|
|
Services.prefs.getBoolPref(
|
|
"browser.translations.geckoview.enableAllTestMocks",
|
|
false
|
|
)
|
|
) {
|
|
aCallback.onSuccess("always");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const setting = this._getLanguageSettingName(aData.language);
|
|
aCallback.onSuccess(setting);
|
|
} catch (error) {
|
|
aCallback.onError(`Could not get language setting: ${error}`);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case "GeckoView:Translations:GetLanguageSettings": {
|
|
if (
|
|
Cu.isInAutomation &&
|
|
Services.prefs.getBoolPref(
|
|
"browser.translations.geckoview.enableAllTestMocks",
|
|
false
|
|
)
|
|
) {
|
|
const mockResult = {
|
|
settings: [
|
|
{ langTag: "fr", displayName: "French", setting: "always" },
|
|
{ langTag: "de", displayName: "German", setting: "offer" },
|
|
{ langTag: "es", displayName: "Spanish", setting: "never" },
|
|
],
|
|
};
|
|
aCallback.onSuccess(mockResult);
|
|
return;
|
|
}
|
|
|
|
lazy.TranslationsParent.getSupportedLanguages().then(
|
|
function (supportedLanguages) {
|
|
const languageList =
|
|
lazy.TranslationsParent.getLanguageList(supportedLanguages);
|
|
|
|
languageList.forEach(language => {
|
|
language.setting = this._getLanguageSettingName(language.langTag);
|
|
});
|
|
|
|
aCallback.onSuccess({ settings: languageList });
|
|
}.bind(this),
|
|
function (error) {
|
|
aCallback.onError(
|
|
`Could not retrieve language setting information: ${error}`
|
|
);
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
|
|
case "GeckoView:Translations:SetLanguageSettings": {
|
|
let { language, languageSetting } = aData;
|
|
languageSetting = languageSetting.toLowerCase();
|
|
|
|
if (!lazy.TranslationsUtils.isLangTagValid(aData.language)) {
|
|
aCallback.onError(`The language tag ${language} is not valid.`);
|
|
return;
|
|
}
|
|
|
|
const ALWAYS = lazy.TranslationsParent.ALWAYS_TRANSLATE_LANGS_PREF;
|
|
const NEVER = lazy.TranslationsParent.NEVER_TRANSLATE_LANGS_PREF;
|
|
|
|
switch (languageSetting) {
|
|
case "always": {
|
|
try {
|
|
lazy.TranslationsParent.removeLangTagFromPref(language, NEVER);
|
|
lazy.TranslationsParent.addLangTagToPref(language, ALWAYS);
|
|
aCallback.onSuccess();
|
|
} catch (error) {
|
|
aCallback.onError(
|
|
`Could not set language preference to always: ${error}`
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case "never": {
|
|
try {
|
|
lazy.TranslationsParent.removeLangTagFromPref(language, ALWAYS);
|
|
lazy.TranslationsParent.addLangTagToPref(language, NEVER);
|
|
aCallback.onSuccess();
|
|
} catch (error) {
|
|
aCallback.onError(
|
|
`Could not set language preference to never: ${error}`
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case "offer": {
|
|
try {
|
|
// Reverting to default settings, so ensure nothing is set.
|
|
lazy.TranslationsParent.removeLangTagFromPref(language, NEVER);
|
|
lazy.TranslationsParent.removeLangTagFromPref(language, ALWAYS);
|
|
aCallback.onSuccess();
|
|
} catch (error) {
|
|
aCallback.onError(
|
|
`Could not set language preference to offer: ${error}`
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case "GeckoView:Translations:GetNeverTranslateSpecifiedSites":
|
|
try {
|
|
const neverTranslateList =
|
|
lazy.TranslationsParent.listNeverTranslateSites();
|
|
aCallback.onSuccess({ sites: neverTranslateList });
|
|
} catch (error) {
|
|
aCallback.onError(
|
|
`Could not get list of never translate sites: ${error}`
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "GeckoView:Translations:SetNeverTranslateSpecifiedSite":
|
|
try {
|
|
lazy.TranslationsParent.setNeverTranslateSiteByOrigin(
|
|
aData.neverTranslate,
|
|
aData.origin
|
|
);
|
|
aCallback.onSuccess();
|
|
} catch (error) {
|
|
aCallback.onError(
|
|
`Could not set never translate site setting: ${error}`
|
|
);
|
|
}
|
|
break;
|
|
case "GeckoView:Translations:GetTranslateDownloadSize": {
|
|
if (
|
|
Cu.isInAutomation &&
|
|
Services.prefs.getBoolPref(
|
|
"browser.translations.geckoview.enableAllTestMocks",
|
|
false
|
|
)
|
|
) {
|
|
aCallback.onSuccess({ bytes: 1234567 });
|
|
return;
|
|
}
|
|
|
|
const fromLangValid = lazy.TranslationsUtils.isLangTagValid(
|
|
aData.fromLanguage
|
|
);
|
|
const toLangValid = lazy.TranslationsUtils.isLangTagValid(
|
|
aData.toLanguage
|
|
);
|
|
if (!fromLangValid || !toLangValid) {
|
|
aCallback.onError(
|
|
`The language tag ${aData.fromLanguage} or ${aData.toLanguage} is not valid.`
|
|
);
|
|
return;
|
|
}
|
|
|
|
lazy.TranslationsParent.getExpectedTranslationDownloadSize(
|
|
aData.fromLanguage,
|
|
aData.toLanguage
|
|
).then(
|
|
function (bytes) {
|
|
aCallback.onSuccess({ bytes });
|
|
},
|
|
function (error) {
|
|
aCallback.onError(`Could not get the download size: ${error}`);
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
const { debug, warn } = GeckoViewTranslations.initLogging(
|
|
"GeckoViewTranslations"
|
|
);
|