Bug 1614750, convert translation bar to use JSWindowActor, r=florian

Differential Revision: https://phabricator.services.mozilla.com/D67535
This commit is contained in:
Neil Deakin 2020-04-18 12:15:10 +00:00
Родитель 286aa3c732
Коммит c7e68f61b7
13 изменённых файлов: 371 добавлений и 363 удалений

Просмотреть файл

@ -76,7 +76,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TabModalPrompt: "chrome://global/content/tabprompts.jsm",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
Translation: "resource:///modules/translation/Translation.jsm",
Translation: "resource:///modules/translation/TranslationParent.jsm",
UITour: "resource:///modules/UITour.jsm",
UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
UrlbarInput: "resource:///modules/UrlbarInput.jsm",
@ -1805,7 +1805,6 @@ var gBrowserInit = {
// loading the frame script to ensure that we don't miss any
// message sent between when the frame script is loaded and when
// the listener is registered.
LanguageDetectionListener.init();
CaptivePortalWatcher.init();
ZoomUI.init(window);
@ -7394,17 +7393,6 @@ var gPageStyleMenu = {
},
};
var LanguageDetectionListener = {
init() {
window.messageManager.addMessageListener(
"Translation:DocumentState",
msg => {
Translation.documentStateReceived(msg.target, msg.data);
}
);
},
};
// Note that this is also called from non-browser windows on OSX, which do
// share menu items but not much else. See nonbrowser-mac.js.
var BrowserOffline = {

Просмотреть файл

@ -29,15 +29,6 @@ ActorManagerChild.attach(this, "browsers");
// BrowserChildGlobal
var global = this;
// Keep a reference to the translation content handler to avoid it it being GC'ed.
var trHandler = null;
if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
var { TranslationContentHandler } = ChromeUtils.import(
"resource:///modules/translation/TranslationContentHandler.jsm"
);
trHandler = new TranslationContentHandler(global, docShell);
}
var WebBrowserChrome = {
onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab) {
return BrowserUtils.onBeforeLinkTraversal(

Просмотреть файл

@ -403,6 +403,20 @@ let ACTORS = {
allFrames: true,
},
Translation: {
parent: {
moduleURI: "resource:///modules/translation/TranslationParent.jsm",
},
child: {
moduleURI: "resource:///modules/translation/TranslationChild.jsm",
events: {
pageshow: {},
load: { mozSystemGroup: true },
},
},
enablePreference: "browser.translation.detectLanguage",
},
UITour: {
parent: {
moduleURI: "resource:///modules/UITourParent.jsm",

Просмотреть файл

@ -539,7 +539,7 @@ var gMainPane = {
row.removeAttribute("hidden");
// Showing attribution only for Bing Translator.
var { Translation } = ChromeUtils.import(
"resource:///modules/translation/Translation.jsm"
"resource:///modules/translation/TranslationParent.jsm"
);
if (Translation.translationEngine == "Bing") {
document.getElementById("bingAttribution").removeAttribute("hidden");
@ -1379,7 +1379,7 @@ var gMainPane = {
openTranslationProviderAttribution() {
var { Translation } = ChromeUtils.import(
"resource:///modules/translation/Translation.jsm"
"resource:///modules/translation/TranslationParent.jsm"
);
Translation.openProviderAttribution();
},

Просмотреть файл

@ -0,0 +1,165 @@
/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["TranslationChild"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(
this,
"LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm"
);
const STATE_OFFER = 0;
const STATE_TRANSLATED = 2;
const STATE_ERROR = 3;
class TranslationChild extends JSWindowActorChild {
handleEvent(aEvent) {
switch (aEvent.type) {
case "pageshow":
// We are only listening to pageshow events.
let content = this.contentWindow;
if (!content.detectedLanguage) {
return;
}
let data = {};
let trDoc = content.translationDocument;
if (trDoc) {
data.state = trDoc.translationError ? STATE_ERROR : STATE_TRANSLATED;
data.translatedFrom = trDoc.translatedFrom;
data.translatedTo = trDoc.translatedTo;
data.originalShown = trDoc.originalShown;
} else {
data.state = STATE_OFFER;
data.originalShown = true;
}
data.detectedLanguage = content.detectedLanguage;
this.sendAsyncMessage("Translation:DocumentState", data);
break;
case "load":
this.checkForTranslation();
break;
}
}
checkForTranslation() {
let url = String(this.document.location);
if (!url.startsWith("http://") && !url.startsWith("https://")) {
return;
}
let content = this.contentWindow;
if (content.detectedLanguage) {
return;
}
// Grab a 60k sample of text from the page.
let encoder = Cu.createDocumentEncoder("text/plain");
encoder.init(this.document, "text/plain", encoder.SkipInvisibleContent);
let string = encoder.encodeToStringWithMaxLength(60 * 1024);
// Language detection isn't reliable on very short strings.
if (string.length < 100) {
return;
}
LanguageDetector.detectLanguage(string).then(result => {
// Bail if we're not confident.
if (!result.confident) {
return;
}
// The window might be gone by now.
if (Cu.isDeadWrapper(content)) {
return;
}
content.detectedLanguage = result.language;
let data = {
state: STATE_OFFER,
originalShown: true,
detectedLanguage: result.language,
};
this.sendAsyncMessage("Translation:DocumentState", data);
});
}
async doTranslation(aFrom, aTo) {
var { TranslationDocument } = ChromeUtils.import(
"resource:///modules/translation/TranslationDocument.jsm"
);
// If a TranslationDocument already exists for this document, it should
// be used instead of creating a new one so that we can use the original
// content of the page for the new translation instead of the newly
// translated text.
let translationDocument =
this.contentWindow.translationDocument ||
new TranslationDocument(this.document);
let engine = Services.prefs.getCharPref("browser.translation.engine");
let importScope = ChromeUtils.import(
`resource:///modules/translation/${engine}Translator.jsm`,
{}
);
let translator = new importScope[engine + "Translator"](
translationDocument,
aFrom,
aTo
);
this.contentWindow.translationDocument = translationDocument;
translationDocument.translatedFrom = aFrom;
translationDocument.translatedTo = aTo;
translationDocument.translationError = false;
let result;
try {
let translateResult = await translator.translate();
result = {
characterCount: translateResult.characterCount,
from: aFrom,
to: aTo,
success: true,
};
translationDocument.showTranslation();
return result;
} catch (ex) {
translationDocument.translationError = true;
result = { success: false };
if (ex == "unavailable") {
result.unavailable = true;
}
}
return result;
}
receiveMessage(aMessage) {
switch (aMessage.name) {
case "Translation:TranslateDocument": {
return this.doTranslation(aMessage.data.from, aMessage.data.to);
}
case "Translation:ShowOriginal":
this.contentWindow.translationDocument.showOriginal();
break;
case "Translation:ShowTranslation":
this.contentWindow.translationDocument.showTranslation();
break;
}
return undefined;
}
}

Просмотреть файл

@ -1,193 +0,0 @@
/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["TranslationContentHandler"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(
this,
"LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm"
);
const STATE_OFFER = 0;
const STATE_TRANSLATED = 2;
const STATE_ERROR = 3;
var TranslationContentHandler = function(global, docShell) {
let webProgress = docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(
this,
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
);
global.addEventListener("pageshow", this);
global.addMessageListener("Translation:TranslateDocument", this);
global.addMessageListener("Translation:ShowTranslation", this);
global.addMessageListener("Translation:ShowOriginal", this);
this.global = global;
};
TranslationContentHandler.prototype = {
handleEvent(aEvent) {
// We are only listening to pageshow events.
let target = aEvent.target;
// Only handle top-level frames.
let win = target.defaultView;
if (win.parent !== win) {
return;
}
let content = this.global.content;
if (!content.detectedLanguage) {
return;
}
let data = {};
let trDoc = content.translationDocument;
if (trDoc) {
data.state = trDoc.translationError ? STATE_ERROR : STATE_TRANSLATED;
data.translatedFrom = trDoc.translatedFrom;
data.translatedTo = trDoc.translatedTo;
data.originalShown = trDoc.originalShown;
} else {
data.state = STATE_OFFER;
data.originalShown = true;
}
data.detectedLanguage = content.detectedLanguage;
this.global.sendAsyncMessage("Translation:DocumentState", data);
},
/* nsIWebProgressListener implementation */
onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
if (
!aWebProgress.isTopLevel ||
!(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) ||
!this.global.content
) {
return;
}
try {
let url = aRequest.name;
if (!url.startsWith("http://") && !url.startsWith("https://")) {
return;
}
} catch (e) {
// nsIRequest.name throws NS_ERROR_NOT_IMPLEMENTED for view-source: tabs.
return;
}
let content = this.global.content;
if (content.detectedLanguage) {
return;
}
// Grab a 60k sample of text from the page.
let encoder = Cu.createDocumentEncoder("text/plain");
encoder.init(content.document, "text/plain", encoder.SkipInvisibleContent);
let string = encoder.encodeToStringWithMaxLength(60 * 1024);
// Language detection isn't reliable on very short strings.
if (string.length < 100) {
return;
}
LanguageDetector.detectLanguage(string).then(result => {
// Bail if we're not confident.
if (!result.confident) {
return;
}
// The window might be gone by now.
if (Cu.isDeadWrapper(content)) {
return;
}
content.detectedLanguage = result.language;
let data = {
state: STATE_OFFER,
originalShown: true,
detectedLanguage: result.language,
};
this.global.sendAsyncMessage("Translation:DocumentState", data);
});
},
QueryInterface: ChromeUtils.generateQI([
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
]),
receiveMessage(msg) {
switch (msg.name) {
case "Translation:TranslateDocument": {
var { TranslationDocument } = ChromeUtils.import(
"resource:///modules/translation/TranslationDocument.jsm"
);
// If a TranslationDocument already exists for this document, it should
// be used instead of creating a new one so that we can use the original
// content of the page for the new translation instead of the newly
// translated text.
let translationDocument =
this.global.content.translationDocument ||
new TranslationDocument(this.global.content.document);
let engine = Services.prefs.getCharPref("browser.translation.engine");
let importScope = ChromeUtils.import(
`resource:///modules/translation/${engine}Translator.jsm`,
{}
);
let translator = new importScope[engine + "Translator"](
translationDocument,
msg.data.from,
msg.data.to
);
this.global.content.translationDocument = translationDocument;
translationDocument.translatedFrom = msg.data.from;
translationDocument.translatedTo = msg.data.to;
translationDocument.translationError = false;
translator.translate().then(
result => {
this.global.sendAsyncMessage("Translation:Finished", {
characterCount: result.characterCount,
from: msg.data.from,
to: msg.data.to,
success: true,
});
translationDocument.showTranslation();
},
error => {
translationDocument.translationError = true;
let data = { success: false };
if (error == "unavailable") {
data.unavailable = true;
}
this.global.sendAsyncMessage("Translation:Finished", data);
}
);
break;
}
case "Translation:ShowOriginal":
this.global.content.translationDocument.showOriginal();
break;
case "Translation:ShowTranslation":
this.global.content.translationDocument.showTranslation();
break;
}
},
};

Просмотреть файл

@ -4,7 +4,11 @@
"use strict";
var EXPORTED_SYMBOLS = ["Translation", "TranslationTelemetry"];
var EXPORTED_SYMBOLS = [
"Translation",
"TranslationParent",
"TranslationTelemetry",
];
const TRANSLATION_PREF_SHOWUI = "browser.translation.ui.show";
const TRANSLATION_PREF_DETECT_LANG = "browser.translation.detectLanguage";
@ -18,6 +22,8 @@ var Translation = {
STATE_ERROR: 3,
STATE_UNAVAILABLE: 4,
translationListener: null,
serviceUnavailable: false,
supportedSourceLanguages: [
@ -57,6 +63,10 @@ var Translation = {
"zh",
],
setListenerForTests(listener) {
this.translationListener = listener;
},
_defaultTargetLanguage: "",
get defaultTargetLanguage() {
if (!this._defaultTargetLanguage) {
@ -67,49 +77,6 @@ var Translation = {
return this._defaultTargetLanguage;
},
documentStateReceived(aBrowser, aData) {
if (aData.state == this.STATE_OFFER) {
if (aData.detectedLanguage == this.defaultTargetLanguage) {
// Detected language is the same as the user's locale.
return;
}
if (!this.supportedSourceLanguages.includes(aData.detectedLanguage)) {
// Detected language is not part of the supported languages.
TranslationTelemetry.recordMissedTranslationOpportunity(
aData.detectedLanguage
);
return;
}
TranslationTelemetry.recordTranslationOpportunity(aData.detectedLanguage);
}
if (!Services.prefs.getBoolPref(TRANSLATION_PREF_SHOWUI)) {
return;
}
if (!aBrowser.translationUI) {
aBrowser.translationUI = new TranslationUI(aBrowser);
}
let trUI = aBrowser.translationUI;
// Set all values before showing a new translation infobar.
trUI._state = Translation.serviceUnavailable
? Translation.STATE_UNAVAILABLE
: aData.state;
trUI.detectedLanguage = aData.detectedLanguage;
trUI.translatedFrom = aData.translatedFrom;
trUI.translatedTo = aData.translatedTo;
trUI.originalShown = aData.originalShown;
trUI.showURLBarIcon();
if (trUI.shouldShowInfoBar(aBrowser.contentPrincipal)) {
trUI.showTranslationInfoBar();
}
},
openProviderAttribution() {
let attribution = this.supportedEngines[this.translationEngine];
const { BrowserWindowTracker } = ChromeUtils.import(
@ -145,10 +112,8 @@ var Translation = {
},
};
/* TranslationUI objects keep the information related to translation for
* a specific browser. This object is passed to the translation
* infobar so that it can initialize itself. The properties exposed to
* the infobar are:
/* Translation objects keep the information related to translation for
* a specific browser. The properties exposed to the infobar are:
* - detectedLanguage, code of the language detected on the web page.
* - state, the state in which the infobar should be displayed
* - translatedFrom, if already translated, source language code.
@ -160,24 +125,65 @@ var Translation = {
* - originalShown, boolean indicating if the original or translated
* version of the page is shown.
*/
function TranslationUI(aBrowser) {
this.browser = aBrowser;
}
class TranslationParent extends JSWindowActorParent {
actorCreated() {
this._state = 0;
this.originalShown = true;
}
TranslationUI.prototype = {
get browser() {
return this._browser;
},
set browser(aBrowser) {
if (this._browser) {
this._browser.messageManager.removeMessageListener(
"Translation:Finished",
this
);
let browser = this.browsingContext.top.embedderElement;
return browser.outerBrowser ? browser.outerBrowser : browser;
}
receiveMessage(aMessage) {
switch (aMessage.name) {
case "Translation:DocumentState":
this.documentStateReceived(aMessage.data);
break;
}
aBrowser.messageManager.addMessageListener("Translation:Finished", this);
this._browser = aBrowser;
},
}
documentStateReceived(aData) {
if (aData.state == Translation.STATE_OFFER) {
if (aData.detectedLanguage == Translation.defaultTargetLanguage) {
// Detected language is the same as the user's locale.
return;
}
if (
!Translation.supportedTargetLanguages.includes(aData.detectedLanguage)
) {
// Detected language is not part of the supported languages.
TranslationTelemetry.recordMissedTranslationOpportunity(
aData.detectedLanguage
);
return;
}
TranslationTelemetry.recordTranslationOpportunity(aData.detectedLanguage);
}
if (!Services.prefs.getBoolPref(TRANSLATION_PREF_SHOWUI)) {
return;
}
// Set all values before showing a new translation infobar.
this._state = Translation.serviceUnavailable
? Translation.STATE_UNAVAILABLE
: aData.state;
this.detectedLanguage = aData.detectedLanguage;
this.translatedFrom = aData.translatedFrom;
this.translatedTo = aData.translatedTo;
this.originalShown = aData.originalShown;
this.showURLBarIcon();
if (this.shouldShowInfoBar(this.browser.contentPrincipal)) {
this.showTranslationInfoBar();
}
}
translate(aFrom, aTo) {
if (
aFrom == aTo ||
@ -206,11 +212,16 @@ TranslationUI.prototype = {
this.translatedFrom = aFrom;
this.translatedTo = aTo;
this.browser.messageManager.sendAsyncMessage(
"Translation:TranslateDocument",
{ from: aFrom, to: aTo }
this.sendQuery("Translation:TranslateDocument", {
from: aFrom,
to: aTo,
}).then(
result => {
this.translationFinished(result);
},
() => {}
);
},
}
showURLBarIcon() {
let chromeWin = this.browser.ownerGlobal;
@ -229,8 +240,6 @@ TranslationUI.prototype = {
let infoBarVisible = this.notificationBox.getNotificationWithValue(
"translation"
);
aNewBrowser.translationUI = this;
this.browser = aNewBrowser;
if (infoBarVisible) {
this.showTranslationInfoBar();
}
@ -261,37 +270,36 @@ TranslationUI.prototype = {
null,
{ dismissed: true, eventCallback: callback }
);
},
}
_state: 0,
get state() {
return this._state;
},
}
set state(val) {
let notif = this.notificationBox.getNotificationWithValue("translation");
if (notif) {
notif.state = val;
}
this._state = val;
},
}
originalShown: true,
showOriginalContent() {
this.originalShown = true;
this.showURLBarIcon();
this.browser.messageManager.sendAsyncMessage("Translation:ShowOriginal");
this.sendAsyncMessage("Translation:ShowOriginal");
TranslationTelemetry.recordShowOriginalContent();
},
}
showTranslatedContent() {
this.originalShown = false;
this.showURLBarIcon();
this.browser.messageManager.sendAsyncMessage("Translation:ShowTranslation");
},
this.sendAsyncMessage("Translation:ShowTranslation");
}
get notificationBox() {
return this.browser.ownerGlobal.gBrowser.getNotificationBox(this.browser);
},
}
showTranslationInfoBar() {
let notificationBox = this.notificationBox;
@ -306,7 +314,7 @@ TranslationUI.prototype = {
);
notif.init(this);
return notif;
},
}
shouldShowInfoBar(aPrincipal) {
// Never show the infobar automatically while the translation
@ -335,38 +343,38 @@ TranslationUI.prototype = {
}
return true;
},
}
receiveMessage(msg) {
switch (msg.name) {
case "Translation:Finished":
if (msg.data.success) {
this.originalShown = false;
this.state = Translation.STATE_TRANSLATED;
this.showURLBarIcon();
translationFinished(result) {
if (result.success) {
this.originalShown = false;
this.state = Translation.STATE_TRANSLATED;
this.showURLBarIcon();
// Record the number of characters translated.
TranslationTelemetry.recordTranslation(
msg.data.from,
msg.data.to,
msg.data.characterCount
);
} else if (msg.data.unavailable) {
Translation.serviceUnavailable = true;
this.state = Translation.STATE_UNAVAILABLE;
} else {
this.state = Translation.STATE_ERROR;
}
break;
// Record the number of characters translated.
TranslationTelemetry.recordTranslation(
result.from,
result.to,
result.characterCount
);
} else if (result.unavailable) {
Translation.serviceUnavailable = true;
this.state = Translation.STATE_UNAVAILABLE;
} else {
this.state = Translation.STATE_ERROR;
}
},
if (Translation.translationListener) {
Translation.translationListener();
}
}
infobarClosed() {
if (this.state == Translation.STATE_OFFER) {
TranslationTelemetry.recordDeniedTranslationOffer();
}
},
};
}
}
/**
* Uses telemetry histograms for collecting statistics on the usage of the

Просмотреть файл

@ -130,6 +130,7 @@ class MozTranslationNotification extends MozElements.Notification {
return this._getAnonElt("translationStates").selectedIndex;
}
// aTranslation is the TranslationParent actor.
init(aTranslation) {
this.translation = aTranslation;

Просмотреть файл

@ -15,9 +15,9 @@ EXTRA_JS_MODULES.translation = [
'cld2/cld-worker.js.mem',
'GoogleTranslator.jsm',
'LanguageDetector.jsm',
'Translation.jsm',
'TranslationContentHandler.jsm',
'TranslationChild.jsm',
'TranslationDocument.jsm',
'TranslationParent.jsm',
'YandexTranslator.jsm'
]

Просмотреть файл

@ -5,7 +5,10 @@
// tests the translation infobar, using a fake 'Translation' implementation.
var tmp = {};
ChromeUtils.import("resource:///modules/translation/Translation.jsm", tmp);
ChromeUtils.import(
"resource:///modules/translation/TranslationParent.jsm",
tmp
);
const { PermissionTestUtils } = ChromeUtils.import(
"resource://testing-common/PermissionTestUtils.jsm"
);
@ -13,16 +16,19 @@ var { Translation } = tmp;
const kLanguagesPref = "browser.translation.neverForLanguages";
const kShowUIPref = "browser.translation.ui.show";
const kEnableTranslationPref = "browser.translation.detectLanguage";
function test() {
waitForExplicitFinish();
Services.prefs.setBoolPref(kShowUIPref, true);
Services.prefs.setBoolPref(kEnableTranslationPref, true);
let tab = BrowserTestUtils.addTab(gBrowser);
gBrowser.selectedTab = tab;
registerCleanupFunction(function() {
gBrowser.removeTab(tab);
Services.prefs.clearUserPref(kShowUIPref);
Services.prefs.clearUserPref(kEnableTranslationPref);
});
BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
(async function() {
@ -126,17 +132,20 @@ var gTests = [
desc: "never for language",
run: async function checkNeverForLanguage() {
// Show the infobar for example.com and fr.
Translation.documentStateReceived(gBrowser.selectedBrowser, {
let actor = gBrowser.selectedBrowser.browsingContext.currentWindowGlobal.getActor(
"Translation"
);
actor.documentStateReceived({
state: Translation.STATE_OFFER,
originalShown: true,
detectedLanguage: "fr",
});
let notif = await getInfoBar();
ok(notif, "the infobar is visible");
let ui = gBrowser.selectedBrowser.translationUI;
let principal = gBrowser.selectedBrowser.contentPrincipal;
ok(
ui.shouldShowInfoBar(principal, "fr"),
actor.shouldShowInfoBar(principal, "fr"),
"check shouldShowInfoBar initially returns true"
);
@ -163,7 +172,7 @@ var gTests = [
is(langs.length, 1, "one language in the exception list");
is(langs[0], "fr", "correct language in the exception list");
ok(
!ui.shouldShowInfoBar(principal, "fr"),
!actor.shouldShowInfoBar(principal, "fr"),
"the infobar wouldn't be shown anymore"
);
@ -187,17 +196,19 @@ var gTests = [
desc: "never for site",
run: async function checkNeverForSite() {
// Show the infobar for example.com and fr.
Translation.documentStateReceived(gBrowser.selectedBrowser, {
let actor = gBrowser.selectedBrowser.browsingContext.currentWindowGlobal.getActor(
"Translation"
);
actor.documentStateReceived({
state: Translation.STATE_OFFER,
originalShown: true,
detectedLanguage: "fr",
});
let notif = await getInfoBar();
ok(notif, "the infobar is visible");
let ui = gBrowser.selectedBrowser.translationUI;
let principal = gBrowser.selectedBrowser.contentPrincipal;
ok(
ui.shouldShowInfoBar(principal, "fr"),
actor.shouldShowInfoBar(principal, "fr"),
"check shouldShowInfoBar initially returns true"
);
@ -228,7 +239,7 @@ var gTests = [
"correct site in the exception list"
);
ok(
!ui.shouldShowInfoBar(principal, "fr"),
!actor.shouldShowInfoBar(principal, "fr"),
"the infobar wouldn't be shown anymore"
);

Просмотреть файл

@ -5,8 +5,11 @@
// tests the translation infobar, using a fake 'Translation' implementation.
var tmp = {};
ChromeUtils.import("resource:///modules/translation/Translation.jsm", tmp);
var { Translation } = tmp;
ChromeUtils.import(
"resource:///modules/translation/TranslationParent.jsm",
tmp
);
var { Translation, TranslationParent } = tmp;
const kShowUIPref = "browser.translation.ui.show";
@ -35,47 +38,55 @@ function waitForCondition(condition, nextTest, errorMsg) {
};
}
var TranslationStub = {
// Create a subclass that overrides the translation functions. This can be
// instantiated separately from the normal actor creation process. This will
// allow testing translations even when the browser.translation.detectLanguage
// preference is disabled.
class TranslationStub extends TranslationParent {
constructor(browser) {
super();
this._browser = browser;
this.actorCreated();
}
get browser() {
return this._browser;
}
sendAsyncMessage(name, data) {}
translate(aFrom, aTo) {
this.state = Translation.STATE_TRANSLATING;
this.translatedFrom = aFrom;
this.translatedTo = aTo;
},
}
_reset() {
this.translatedFrom = "";
this.translatedTo = "";
},
}
failTranslation() {
this.state = Translation.STATE_ERROR;
this._reset();
},
}
finishTranslation() {
this.showTranslatedContent();
this.state = Translation.STATE_TRANSLATED;
this._reset();
},
};
}
}
function showTranslationUI(aDetectedLanguage) {
let browser = gBrowser.selectedBrowser;
Translation.documentStateReceived(browser, {
let translation = new TranslationStub(browser);
translation.documentStateReceived({
state: Translation.STATE_OFFER,
originalShown: true,
detectedLanguage: aDetectedLanguage,
});
let ui = browser.translationUI;
for (let name of [
"translate",
"_reset",
"failTranslation",
"finishTranslation",
]) {
ui[name] = TranslationStub[name];
}
return ui.notificationBox.getNotificationWithValue("translation");
return translation.notificationBox.getNotificationWithValue("translation");
}
function hasTranslationInfoBar() {

Просмотреть файл

@ -4,7 +4,10 @@
"use strict";
var tmp = {};
ChromeUtils.import("resource:///modules/translation/Translation.jsm", tmp);
ChromeUtils.import(
"resource:///modules/translation/TranslationParent.jsm",
tmp
);
var { Translation, TranslationTelemetry } = tmp;
const Telemetry = Services.telemetry;
@ -108,9 +111,10 @@ var MetricsChecker = {
};
function getInfobarElement(browser, anonid) {
let notif = browser.translationUI.notificationBox.getNotificationWithValue(
"translation"
let actor = browser.browsingContext.currentWindowGlobal.getActor(
"Translation"
);
let notif = actor.notificationBox.getNotificationWithValue("translation");
return notif._getAnonElt(anonid);
}
@ -120,9 +124,12 @@ var offerTranslationFor = async function(text, from) {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, dataUrl);
let browser = gBrowser.getBrowserForTab(tab);
let actor = browser.browsingContext.currentWindowGlobal.getActor(
"Translation"
);
// Send a translation offer.
Translation.documentStateReceived(browser, {
actor.documentStateReceived({
state: Translation.STATE_OFFER,
originalShown: true,
detectedLanguage: from,
@ -133,8 +140,10 @@ var offerTranslationFor = async function(text, from) {
var acceptTranslationOffer = async function(tab) {
let browser = tab.linkedBrowser;
let translationPromise = waitForTranslationDone();
getInfobarElement(browser, "translate").doCommand();
await waitForMessage(browser, "Translation:Finished");
await translationPromise;
};
var translate = async function(text, from, closeTab = true) {
@ -147,10 +156,10 @@ var translate = async function(text, from, closeTab = true) {
return tab;
};
function waitForMessage({ messageManager }, name) {
function waitForTranslationDone() {
return new Promise(resolve => {
messageManager.addMessageListener(name, function onMessage() {
messageManager.removeMessageListener(name, onMessage);
Translation.setListenerForTests(() => {
Translation.setListenerForTests(null);
resolve();
});
});

Просмотреть файл

@ -19,7 +19,7 @@ const kApiKeyPref = "browser.translation.yandex.apiKeyOverride";
const kShowUIPref = "browser.translation.ui.show";
const { Translation } = ChromeUtils.import(
"resource:///modules/translation/Translation.jsm"
"resource:///modules/translation/TranslationParent.jsm"
);
add_task(async function setup() {
@ -140,11 +140,14 @@ function promiseTestPageLoad(url) {
function showTranslationUI(tab, aDetectedLanguage) {
let browser = gBrowser.selectedBrowser;
Translation.documentStateReceived(browser, {
let actor = browser.browsingContext.currentWindowGlobal.getActor(
"Translation"
);
actor.documentStateReceived({
state: Translation.STATE_OFFER,
originalShown: true,
detectedLanguage: aDetectedLanguage,
});
let ui = browser.translationUI;
return ui.notificationBox.getNotificationWithValue("translation");
return actor.notificationBox.getNotificationWithValue("translation");
}