forked from mirrors/gecko-dev
Bundle Firefox Translation as a builtin pref'd off addon in Nightly only Differential Revision: https://phabricator.services.mozilla.com/D114810
322 lines
9.6 KiB
JavaScript
322 lines
9.6 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/. */
|
|
|
|
/* global TranslationBrowserChromeUiNotificationManager */
|
|
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "(TranslationBrowserChromeUi)" }]*/
|
|
|
|
const { setTimeout, clearTimeout } = ChromeUtils.import(
|
|
"resource://gre/modules/Timer.jsm",
|
|
{},
|
|
);
|
|
|
|
const TranslationInfoBarStates = {
|
|
STATE_OFFER: 0,
|
|
STATE_TRANSLATING: 1,
|
|
STATE_TRANSLATED: 2,
|
|
STATE_ERROR: 3,
|
|
STATE_UNAVAILABLE: 4,
|
|
};
|
|
|
|
class TranslationBrowserChromeUi {
|
|
constructor(Services, browser, context, apiEventEmitter, tabId) {
|
|
this.Services = Services;
|
|
this.uiState = null;
|
|
this.browser = browser;
|
|
this.context = context;
|
|
this.translationInfoBarShown = false;
|
|
this.shouldShowTranslationProgressTimer = undefined;
|
|
this.importTranslationNotification();
|
|
|
|
// The manager instance is injected into the translation notification bar and handles events from therein
|
|
this.translationBrowserChromeUiNotificationManager = new TranslationBrowserChromeUiNotificationManager(
|
|
browser,
|
|
apiEventEmitter,
|
|
tabId,
|
|
TranslationInfoBarStates,
|
|
);
|
|
}
|
|
|
|
get notificationBox() {
|
|
return this.browser.ownerGlobal.gBrowser.getNotificationBox(this.browser);
|
|
}
|
|
|
|
importTranslationNotification() {
|
|
const chromeWin = this.browser.ownerGlobal;
|
|
|
|
// As a workaround to be able to load updates for the translation notification on extension reload
|
|
// we use the current unix timestamp as part of the element id.
|
|
// TODO: Restrict use of Date.now() as cachebuster to development mode only
|
|
chromeWin.now = Date.now();
|
|
|
|
try {
|
|
chromeWin.customElements.setElementCreationCallback(
|
|
`translation-notification-${chromeWin.now}`,
|
|
() => {
|
|
this.Services.scriptloader.loadSubScript(
|
|
this.context.extension.getURL(
|
|
"experiment-apis/translateUi/content/translation-notification.js",
|
|
) +
|
|
"?cachebuster=" +
|
|
chromeWin.now,
|
|
chromeWin,
|
|
);
|
|
},
|
|
);
|
|
} catch (e) {
|
|
console.log(
|
|
"Error occurred when attempting to load the translation notification script, but we continue nevertheless",
|
|
e,
|
|
);
|
|
}
|
|
}
|
|
|
|
onUiStateUpdate(uiState) {
|
|
// Set all values before showing a new translation infobar.
|
|
this.translationBrowserChromeUiNotificationManager.uiState = uiState;
|
|
this.setInfobarState(uiState.infobarState);
|
|
this.updateTranslationProgress(uiState);
|
|
if (this.shouldShowInfoBar(this.browser.contentPrincipal)) {
|
|
this.showTranslationInfoBarIfNotAlreadyShown();
|
|
} else {
|
|
this.hideTranslationInfoBarIfShown();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Syncs infobarState with the inner infobar state variable of the infobar
|
|
* @param val
|
|
*/
|
|
setInfobarState(val) {
|
|
const notif = this.notificationBox.getNotificationWithValue("translation");
|
|
if (notif) {
|
|
notif.state = val;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Informs the infobar element of the current translation progress
|
|
*/
|
|
updateTranslationProgress(uiState) {
|
|
// Don't bother updating translation progress if not currently translating
|
|
if (
|
|
this.translationBrowserChromeUiNotificationManager.uiState
|
|
.infobarState !== TranslationInfoBarStates.STATE_TRANSLATING
|
|
) {
|
|
return;
|
|
}
|
|
const notif = this.notificationBox.getNotificationWithValue("translation");
|
|
if (notif) {
|
|
const {
|
|
modelDownloading,
|
|
translationDurationMs,
|
|
localizedTranslationProgressText,
|
|
} = uiState;
|
|
|
|
// Always cancel ongoing timers so that we start from a clean state
|
|
if (this.shouldShowTranslationProgressTimer) {
|
|
clearTimeout(this.shouldShowTranslationProgressTimer);
|
|
}
|
|
|
|
// Only show progress if translation has been going on for at least 3 seconds
|
|
// or we are currently downloading a model
|
|
let shouldShowTranslationProgress;
|
|
const thresholdMsAfterWhichToShouldTranslationProgress = 3000;
|
|
if (
|
|
translationDurationMs >=
|
|
thresholdMsAfterWhichToShouldTranslationProgress ||
|
|
modelDownloading
|
|
) {
|
|
shouldShowTranslationProgress = true;
|
|
} else {
|
|
// Use a timer to show the translation progress after the threshold
|
|
this.shouldShowTranslationProgressTimer = setTimeout(() => {
|
|
notif.updateTranslationProgress(
|
|
true,
|
|
localizedTranslationProgressText,
|
|
);
|
|
clearTimeout(this.shouldShowTranslationProgressTimer);
|
|
}, thresholdMsAfterWhichToShouldTranslationProgress - translationDurationMs);
|
|
// Don't show until then
|
|
shouldShowTranslationProgress = false;
|
|
}
|
|
notif.updateTranslationProgress(
|
|
shouldShowTranslationProgress,
|
|
localizedTranslationProgressText,
|
|
);
|
|
}
|
|
}
|
|
|
|
shouldShowInfoBar(principal) {
|
|
if (
|
|
![
|
|
TranslationInfoBarStates.STATE_OFFER,
|
|
TranslationInfoBarStates.STATE_TRANSLATING,
|
|
TranslationInfoBarStates.STATE_TRANSLATED,
|
|
TranslationInfoBarStates.STATE_ERROR,
|
|
].includes(
|
|
this.translationBrowserChromeUiNotificationManager.uiState.infobarState,
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// Don't show the infobar if we have no language detection results yet
|
|
if (
|
|
!this.translationBrowserChromeUiNotificationManager.uiState
|
|
.detectedLanguageResults
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// Don't show the infobar if we couldn't confidently detect the language
|
|
if (
|
|
!this.translationBrowserChromeUiNotificationManager.uiState
|
|
.detectedLanguageResults.confident
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// Check if we should never show the infobar for this language.
|
|
const neverForLangs = this.Services.prefs.getCharPref(
|
|
"browser.translation.neverForLanguages",
|
|
);
|
|
if (
|
|
neverForLangs
|
|
.split(",")
|
|
.includes(
|
|
this.translationBrowserChromeUiNotificationManager.uiState
|
|
.detectedLanguageResults.language,
|
|
)
|
|
) {
|
|
// TranslationTelemetry.recordAutoRejectedTranslationOffer();
|
|
return false;
|
|
}
|
|
|
|
// or if we should never show the infobar for this domain.
|
|
const perms = this.Services.perms;
|
|
if (
|
|
perms.testExactPermissionFromPrincipal(principal, "translate") ===
|
|
perms.DENY_ACTION
|
|
) {
|
|
// TranslationTelemetry.recordAutoRejectedTranslationOffer();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
hideURLBarIcon() {
|
|
const chromeWin = this.browser.ownerGlobal;
|
|
const PopupNotifications = chromeWin.PopupNotifications;
|
|
const removeId = this.translationBrowserChromeUiNotificationManager.uiState
|
|
.originalShown
|
|
? "translated"
|
|
: "translate";
|
|
const notification = PopupNotifications.getNotification(
|
|
removeId,
|
|
this.browser,
|
|
);
|
|
if (notification) {
|
|
PopupNotifications.remove(notification);
|
|
}
|
|
}
|
|
|
|
showURLBarIcon() {
|
|
const chromeWin = this.browser.ownerGlobal;
|
|
const PopupNotifications = chromeWin.PopupNotifications;
|
|
const removeId = this.translationBrowserChromeUiNotificationManager.uiState
|
|
.originalShown
|
|
? "translated"
|
|
: "translate";
|
|
const notification = PopupNotifications.getNotification(
|
|
removeId,
|
|
this.browser,
|
|
);
|
|
if (notification) {
|
|
PopupNotifications.remove(notification);
|
|
}
|
|
|
|
const callback = (topic /* , aNewBrowser */) => {
|
|
if (topic === "swapping") {
|
|
const infoBarVisible = this.notificationBox.getNotificationWithValue(
|
|
"translation",
|
|
);
|
|
if (infoBarVisible) {
|
|
this.showTranslationInfoBar();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (topic !== "showing") {
|
|
return false;
|
|
}
|
|
const translationNotification = this.notificationBox.getNotificationWithValue(
|
|
"translation",
|
|
);
|
|
if (translationNotification) {
|
|
translationNotification.close();
|
|
} else {
|
|
this.showTranslationInfoBar();
|
|
}
|
|
return true;
|
|
};
|
|
|
|
const addId = this.translationBrowserChromeUiNotificationManager.uiState
|
|
.originalShown
|
|
? "translate"
|
|
: "translated";
|
|
PopupNotifications.show(
|
|
this.browser,
|
|
addId,
|
|
null,
|
|
addId + "-notification-icon",
|
|
null,
|
|
null,
|
|
{ dismissed: true, eventCallback: callback },
|
|
);
|
|
}
|
|
|
|
showTranslationInfoBarIfNotAlreadyShown() {
|
|
const translationNotification = this.notificationBox.getNotificationWithValue(
|
|
"translation",
|
|
);
|
|
if (!translationNotification && !this.translationInfoBarShown) {
|
|
this.showTranslationInfoBar();
|
|
}
|
|
this.showURLBarIcon();
|
|
}
|
|
|
|
hideTranslationInfoBarIfShown() {
|
|
const translationNotification = this.notificationBox.getNotificationWithValue(
|
|
"translation",
|
|
);
|
|
if (translationNotification) {
|
|
translationNotification.close();
|
|
}
|
|
this.hideURLBarIcon();
|
|
this.translationInfoBarShown = false;
|
|
}
|
|
|
|
showTranslationInfoBar() {
|
|
console.debug("showTranslationInfoBar");
|
|
this.translationInfoBarShown = true;
|
|
const notificationBox = this.notificationBox;
|
|
const chromeWin = this.browser.ownerGlobal;
|
|
const notif = notificationBox.appendNotification(
|
|
"",
|
|
"translation",
|
|
null,
|
|
notificationBox.PRIORITY_INFO_HIGH,
|
|
null,
|
|
null,
|
|
`translation-notification-${chromeWin.now}`,
|
|
);
|
|
notif.init(this.translationBrowserChromeUiNotificationManager);
|
|
this.translationBrowserChromeUiNotificationManager.infobarDisplayed(
|
|
notif._getSourceLang(),
|
|
notif._getTargetLang(),
|
|
);
|
|
return notif;
|
|
}
|
|
}
|