Bug 1015527 - Back/Forward navigation shouldn't break the Translation UI, r=felipe.

This commit is contained in:
Florian Quèze 2014-06-09 16:50:58 +02:00
parent ea84d98c38
commit c54bd196cb
7 changed files with 127 additions and 48 deletions

View File

@ -5311,8 +5311,8 @@ function setStyleDisabled(disabled) {
var LanguageDetectionListener = {
init: function() {
window.messageManager.addMessageListener("LanguageDetection:Result", msg => {
Translation.languageDetected(msg.target, msg.data);
window.messageManager.addMessageListener("Translation:DocumentState", msg => {
Translation.documentStateReceived(msg.target, msg.data);
});
}
};

View File

@ -24,6 +24,11 @@ const DAILY_LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC
this.Translation = {
STATE_OFFER: 0,
STATE_TRANSLATING: 1,
STATE_TRANSLATED: 2,
STATE_ERROR: 3,
supportedSourceLanguages: ["en", "zh", "ja", "es", "de", "fr", "ru", "ar", "ko", "pt"],
supportedTargetLanguages: ["en", "pl", "tr", "vi"],
@ -38,21 +43,33 @@ this.Translation = {
return this._defaultTargetLanguage;
},
languageDetected: function(aBrowser, aDetectedLanguage) {
if (this.supportedSourceLanguages.indexOf(aDetectedLanguage) == -1 ||
aDetectedLanguage == this.defaultTargetLanguage)
return;
documentStateReceived: function(aBrowser, aData) {
if (aData.state == this.STATE_OFFER) {
if (this.supportedSourceLanguages.indexOf(aData.detectedLanguage) == -1 ||
aData.detectedLanguage == this.defaultTargetLanguage)
return;
TranslationHealthReport.recordTranslationOpportunity(aDetectedLanguage);
TranslationHealthReport.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 = aData.state;
trUI.detectedLanguage = aData.detectedLanguage;
trUI.translatedFrom = aData.translatedFrom;
trUI.translatedTo = aData.translatedTo;
trUI.originalShown = aData.originalShown;
aBrowser.translationUI.showTranslationUI(aDetectedLanguage);
trUI.showURLBarIcon();
if (trUI.shouldShowInfoBar(aBrowser.currentURI))
trUI.showTranslationInfoBar();
}
};
@ -62,7 +79,6 @@ this.Translation = {
* the infobar are:
* - detectedLanguage, code of the language detected on the web page.
* - state, the state in which the infobar should be displayed
* - STATE_{OFFER,TRANSLATING,TRANSLATED,ERROR} constants.
* - translatedFrom, if already translated, source language code.
* - translatedTo, if already translated, target language code.
* - translate, method starting the translation of the current page.
@ -78,20 +94,15 @@ function TranslationUI(aBrowser) {
}
TranslationUI.prototype = {
STATE_OFFER: 0,
STATE_TRANSLATING: 1,
STATE_TRANSLATED: 2,
STATE_ERROR: 3,
translate: function(aFrom, aTo) {
if (aFrom == aTo ||
(this.state == this.STATE_TRANSLATED &&
(this.state == Translation.STATE_TRANSLATED &&
this.translatedFrom == aFrom && this.translatedTo == aTo)) {
// Nothing to do.
return;
}
this.state = this.STATE_TRANSLATING;
this.state = Translation.STATE_TRANSLATING;
this.translatedFrom = aFrom;
this.translatedTo = aTo;
@ -101,10 +112,10 @@ TranslationUI.prototype = {
);
},
showURLBarIcon: function(aTranslated) {
showURLBarIcon: function() {
let chromeWin = this.browser.ownerGlobal;
let PopupNotifications = chromeWin.PopupNotifications;
let removeId = aTranslated ? "translate" : "translated";
let removeId = this.originalShown ? "translated" : "translate";
let notification =
PopupNotifications.getNotification(removeId, this.browser);
if (notification)
@ -121,7 +132,7 @@ TranslationUI.prototype = {
return true;
};
let addId = aTranslated ? "translated" : "translate";
let addId = this.originalShown ? "translate" : "translated";
PopupNotifications.show(this.browser, addId, null,
addId + "-notification-icon", null, null,
{dismissed: true, eventCallback: callback});
@ -138,14 +149,14 @@ TranslationUI.prototype = {
originalShown: true,
showOriginalContent: function() {
this.showURLBarIcon();
this.originalShown = true;
this.showURLBarIcon();
this.browser.messageManager.sendAsyncMessage("Translation:ShowOriginal");
},
showTranslatedContent: function() {
this.showURLBarIcon(true);
this.originalShown = false;
this.showURLBarIcon();
this.browser.messageManager.sendAsyncMessage("Translation:ShowTranslation");
},
@ -159,11 +170,11 @@ TranslationUI.prototype = {
return notif;
},
shouldShowInfoBar: function(aURI, aDetectedLanguage) {
shouldShowInfoBar: function(aURI) {
// Check if we should never show the infobar for this language.
let neverForLangs =
Services.prefs.getCharPref("browser.translation.neverForLanguages");
if (neverForLangs.split(",").indexOf(aDetectedLanguage) != -1)
if (neverForLangs.split(",").indexOf(this.detectedLanguage) != -1)
return false;
// or if we should never show the infobar for this domain.
@ -182,7 +193,7 @@ TranslationUI.prototype = {
this.showURLBarIcon();
if (!this.shouldShowInfoBar(this.browser.currentURI, aDetectedLanguage))
if (!this.shouldShowInfoBar(this.browser.currentURI))
return null;
return this.showTranslationInfoBar();
@ -192,11 +203,11 @@ TranslationUI.prototype = {
switch (msg.name) {
case "Translation:Finished":
if (msg.data.success) {
this.state = this.STATE_TRANSLATED;
this.showURLBarIcon(true);
this.state = Translation.STATE_TRANSLATED;
this.originalShown = false;
this.showURLBarIcon();
} else {
this.state = this.STATE_ERROR;
this.state = Translation.STATE_ERROR;
}
break;
}

View File

@ -12,11 +12,17 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm");
const STATE_OFFER = 0;
const STATE_TRANSLATED = 2;
const STATE_ERROR = 3;
this.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);
@ -24,6 +30,35 @@ this.TranslationContentHandler = function(global, docShell) {
}
TranslationContentHandler.prototype = {
handleEvent: function(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: function(aWebProgress, aRequest, aStateFlags, aStatus) {
if (!aWebProgress.isTopLevel ||
@ -35,10 +70,14 @@ TranslationContentHandler.prototype = {
if (!url.startsWith("http://") && !url.startsWith("https://"))
return;
let content = this.global.content;
if (content.detectedLanguage)
return;
// Grab a 60k sample of text from the page.
let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
.createInstance(Ci.nsIDocumentEncoder);
encoder.init(this.global.content.document, "text/plain", encoder.SkipInvisibleContent);
encoder.init(content.document, "text/plain", encoder.SkipInvisibleContent);
let string = encoder.encodeToStringWithMaxLength(60 * 1024);
// Language detection isn't reliable on very short strings.
@ -46,8 +85,17 @@ TranslationContentHandler.prototype = {
return;
LanguageDetector.detectLanguage(string).then(result => {
if (result.confident)
this.global.sendAsyncMessage("LanguageDetection:Result", result.language);
if (!result.confident)
return;
content.detectedLanguage = result.language;
let data = {
state: STATE_OFFER,
originalShown: true,
detectedLanguage: result.language
};
this.global.sendAsyncMessage("Translation:DocumentState", data);
});
},
@ -78,12 +126,17 @@ TranslationContentHandler.prototype = {
msg.data.to);
this.global.content.translationDocument = translationDocument;
translationDocument.translatedFrom = msg.data.from;
translationDocument.translatedTo = msg.data.to;
translationDocument.translationError = false;
bingTranslation.translate().then(
success => {
this.global.sendAsyncMessage("Translation:Finished", {success: true});
translationDocument.showTranslation();
},
error => {
translationDocument.translationError = true;
this.global.sendAsyncMessage("Translation:Finished", {success: false});
}
);

View File

@ -31,6 +31,11 @@ this.TranslationDocument = function(document) {
};
this.TranslationDocument.prototype = {
translatedFrom: "",
translatedTo: "",
translationError: false,
originalShown: true,
/**
* Initializes the object and populates
* the roots lists.
@ -185,6 +190,7 @@ this.TranslationDocument.prototype = {
* content.
*/
showTranslation: function() {
this.originalShown = false;
this._swapDocumentContent("translation");
},
@ -193,6 +199,7 @@ this.TranslationDocument.prototype = {
* content.
*/
showOriginal: function() {
this.originalShown = true;
this._swapDocumentContent("original");
},

View File

@ -104,7 +104,10 @@ let gTests = [
desc: "never for language",
run: function* checkNeverForLanguage() {
// Show the infobar for example.com and fr.
Translation.languageDetected(gBrowser.selectedBrowser, "fr");
Translation.documentStateReceived(gBrowser.selectedBrowser,
{state: Translation.STATE_OFFER,
originalShown: true,
detectedLanguage: "fr"});
let notif = getInfoBar();
ok(notif, "the infobar is visible");
let ui = gBrowser.selectedBrowser.translationUI;
@ -150,7 +153,10 @@ let gTests = [
desc: "never for site",
run: function* checkNeverForSite() {
// Show the infobar for example.com and fr.
Translation.languageDetected(gBrowser.selectedBrowser, "fr");
Translation.documentStateReceived(gBrowser.selectedBrowser,
{state: Translation.STATE_OFFER,
originalShown: true,
detectedLanguage: "fr"});
let notif = getInfoBar();
ok(notif, "the infobar is visible");
let ui = gBrowser.selectedBrowser.translationUI;

View File

@ -34,7 +34,7 @@ function waitForCondition(condition, nextTest, errorMsg) {
var TranslationStub = {
translate: function(aFrom, aTo) {
this.state = this.STATE_TRANSLATING;
this.state = Translation.STATE_TRANSLATING;
this.translatedFrom = aFrom;
this.translatedTo = aTo;
},
@ -45,20 +45,22 @@ var TranslationStub = {
},
failTranslation: function() {
this.state = this.STATE_ERROR;
this.state = Translation.STATE_ERROR;
this._reset();
},
finishTranslation: function() {
this.showTranslatedContent();
this.state = this.STATE_TRANSLATED;
this.state = Translation.STATE_TRANSLATED;
this._reset();
}
};
function showTranslationUI(aDetectedLanguage) {
let browser = gBrowser.selectedBrowser;
Translation.languageDetected(browser, aDetectedLanguage);
Translation.documentStateReceived(browser, {state: Translation.STATE_OFFER,
originalShown: true,
detectedLanguage: aDetectedLanguage});
let ui = browser.translationUI;
for (let name of ["translate", "_reset", "failTranslation", "finishTranslation"])
ui[name] = TranslationStub[name];
@ -100,13 +102,13 @@ function checkURLBarIcon(aExpectTranslated = false) {
function run_tests(aFinishCallback) {
info("Show an info bar saying the current page is in French");
let notif = showTranslationUI("fr");
is(notif.state, notif.translation.STATE_OFFER, "the infobar is offering translation");
is(notif.state, Translation.STATE_OFFER, "the infobar is offering translation");
is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed");
checkURLBarIcon();
info("Click the 'Translate' button");
notif._getAnonElt("translate").click();
is(notif.state, notif.translation.STATE_TRANSLATING, "the infobar is in the translating state");
is(notif.state, Translation.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(notif.translation.translatedFrom, "fr", "from language correct");
is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "from language correct");
@ -114,12 +116,12 @@ function run_tests(aFinishCallback) {
info("Make the translation fail and check we are in the error state.");
notif.translation.failTranslation();
is(notif.state, notif.translation.STATE_ERROR, "infobar in the error state");
is(notif.state, Translation.STATE_ERROR, "infobar in the error state");
checkURLBarIcon();
info("Click the try again button");
notif._getAnonElt("tryAgain").click();
is(notif.state, notif.translation.STATE_TRANSLATING, "infobar in the translating state");
is(notif.state, Translation.STATE_TRANSLATING, "infobar in the translating state");
ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(notif.translation.translatedFrom, "fr", "from language correct");
is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "from language correct");
@ -127,7 +129,7 @@ function run_tests(aFinishCallback) {
info("Make the translation succeed and check we are in the 'translated' state.");
notif.translation.finishTranslation();
is(notif.state, notif.translation.STATE_TRANSLATED, "infobar in the translated state");
is(notif.state, Translation.STATE_TRANSLATED, "infobar in the translated state");
checkURLBarIcon(true);
info("Test 'Show original' / 'Show Translation' buttons.");
@ -153,7 +155,7 @@ function run_tests(aFinishCallback) {
let from = notif._getAnonElt("fromLanguage");
from.value = "es";
from.doCommand();
is(notif.state, notif.translation.STATE_TRANSLATING, "infobar in the translating state");
is(notif.state, Translation.STATE_TRANSLATING, "infobar in the translating state");
ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(notif.translation.translatedFrom, "es", "from language correct");
is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "to language correct");
@ -167,7 +169,7 @@ function run_tests(aFinishCallback) {
let to = notif._getAnonElt("toLanguage");
to.value = "pl";
to.doCommand();
is(notif.state, notif.translation.STATE_TRANSLATING, "infobar in the translating state");
is(notif.state, Translation.STATE_TRANSLATING, "infobar in the translating state");
ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(notif.translation.translatedFrom, "es", "from language correct");
is(notif.translation.translatedTo, "pl", "to language correct");
@ -180,12 +182,12 @@ function run_tests(aFinishCallback) {
info("Reopen the info bar to check that it's possible to override the detected language.");
notif = showTranslationUI("fr");
is(notif.state, notif.translation.STATE_OFFER, "the infobar is offering translation");
is(notif.state, Translation.STATE_OFFER, "the infobar is offering translation");
is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed");
// Change the language and click 'Translate'
notif._getAnonElt("detectedLanguage").value = "ja";
notif._getAnonElt("translate").click();
is(notif.state, notif.translation.STATE_TRANSLATING, "the infobar is in the translating state");
is(notif.state, Translation.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(notif.translation.translatedFrom, "ja", "from language correct");
notif.close();

View File

@ -127,7 +127,7 @@
let stateName;
for (let name of ["OFFER", "TRANSLATING", "TRANSLATED", "ERROR"]) {
if (this.translation["STATE_" + name] == val) {
if (Translation["STATE_" + name] == val) {
stateName = name.toLowerCase();
break;
}
@ -188,7 +188,7 @@
<method name="translate">
<body>
<![CDATA[
if (this.state == this.translation.STATE_OFFER) {
if (this.state == Translation.STATE_OFFER) {
this._getAnonElt("fromLanguage").value =
this._getAnonElt("detectedLanguage").value;
this._getAnonElt("toLanguage").value =
@ -235,7 +235,7 @@
<![CDATA[
// Get the source language name.
let lang;
if (this.state == this.translation.STATE_OFFER)
if (this.state == Translation.STATE_OFFER)
lang = this._getAnonElt("detectedLanguage").value;
else
lang = this._getAnonElt("fromLanguage").value;