Bug 974461 - Display translation icon at the same time as the translation infobar, r=felipe.

--HG--
extra : rebase_source : a00d5c02ba42c66de943d9c4c34171f3f35022ed
This commit is contained in:
Florian Quèze 2014-04-07 19:39:07 +02:00
Родитель 184c9722c2
Коммит 171e950cb8
4 изменённых файлов: 290 добавлений и 127 удалений

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

@ -0,0 +1,134 @@
/* 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";
this.EXPORTED_SYMBOLS = ["Translation"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm");
/* Create an object keeping 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:
* - supportedSourceLanguages, array of supported source language codes
* - supportedTargetLanguages, array of supported target language codes
* - detectedLanguage, code of the language detected on the web page.
* - defaultTargetLanguage, code of the language to use by default for
* translation.
* - 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.
* - showOriginalContent, method showing the original page content.
* - showTranslatedContent, method showing the translation for an
* already translated page whose original content is shown.
* - originalShown, boolean indicating if the original or translated
* version of the page is shown.
*/
this.Translation = function(aBrowser) {
this.browser = aBrowser;
};
this.Translation.prototype = {
supportedSourceLanguages: ["en", "zh", "ja", "es", "de", "fr", "ru", "ar", "ko", "pt"],
supportedTargetLanguages: ["en", "pl", "tr", "vi"],
STATE_OFFER: 0,
STATE_TRANSLATING: 1,
STATE_TRANSLATED: 2,
STATE_ERROR: 3,
_defaultTargetLanguage: "",
get defaultTargetLanguage() {
if (!this._defaultTargetLanguage) {
this._defaultTargetLanguage = Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIXULChromeRegistry)
.getSelectedLocale("global")
.split("-")[0];
}
return this._defaultTargetLanguage;
},
get doc() this.browser.contentDocument,
translate: function(aFrom, aTo) {
this.state = this.STATE_TRANSLATING;
this.translatedFrom = aFrom;
this.translatedTo = aTo;
},
showURLBarIcon: function(aTranslated) {
let chromeWin = this.browser.ownerGlobal;
let PopupNotifications = chromeWin.PopupNotifications;
let removeId = aTranslated ? "translate" : "translated";
let notification =
PopupNotifications.getNotification(removeId, this.browser);
if (notification)
PopupNotifications.remove(notification);
let callback = aTopic => {
if (aTopic != "showing")
return false;
if (!this.notificationBox.getNotificationWithValue("translation"))
this.showTranslationInfoBar();
return true;
};
let addId = aTranslated ? "translated" : "translate";
PopupNotifications.show(this.browser, addId, null,
addId + "-notification-icon", null, null,
{dismissed: true, eventCallback: callback});
},
_state: 0,
get state() this._state,
set state(val) {
let notif = this.notificationBox.getNotificationWithValue("translation");
if (notif)
notif.state = val;
this._state = val;
},
originalShown: true,
showOriginalContent: function() {
this.showURLBarIcon();
this.originalShown = true;
},
showTranslatedContent: function() {
this.showURLBarIcon(true);
this.originalShown = false;
},
get notificationBox() this.browser.ownerGlobal.gBrowser.getNotificationBox(),
showTranslationInfoBar: function() {
let notificationBox = this.notificationBox;
let notif = notificationBox.appendNotification("", "translation", null,
notificationBox.PRIORITY_INFO_HIGH);
notif.init(this);
return notif;
},
showTranslationUI: function(aDetectedLanguage) {
this.detectedLanguage = aDetectedLanguage;
// Reset all values before showing a new translation infobar.
this.state = 0;
this.translatedFrom = "";
this.translatedTo = "";
this.originalShown = true;
this.showURLBarIcon();
return this.showTranslationInfoBar();
}
};

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

@ -7,7 +7,8 @@ JS_MODULES_PATH = 'modules/translation'
EXTRA_JS_MODULES = [
'cld2/cld-worker.js',
'cld2/cld-worker.js.mem',
'LanguageDetector.jsm'
'LanguageDetector.jsm',
'Translation.jsm'
]
JAR_MANIFESTS += ['jar.mn']

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

@ -4,152 +4,191 @@
// tests the translation infobar, using a fake 'Translation' implementation.
var Translation = {
supportedSourceLanguages: ["en", "zh", "ja", "es", "de", "fr", "ru", "ar", "ko", "pt"],
supportedTargetLanguages: ["en", "pl", "tr", "vi"],
defaultTargetLanguage: "en",
Components.utils.import("resource:///modules/translation/Translation.jsm");
function waitForCondition(condition, nextTest, errorMsg) {
var tries = 0;
var interval = setInterval(function() {
if (tries >= 30) {
ok(false, errorMsg);
moveOn();
}
var conditionPassed;
try {
conditionPassed = condition();
} catch (e) {
ok(false, e + "\n" + e.stack);
conditionPassed = false;
}
if (conditionPassed) {
moveOn();
}
tries++;
}, 100);
var moveOn = function() { clearInterval(interval); nextTest(); };
}
var TranslationStub = {
__proto__: Translation.prototype,
_translateFrom: "",
_translateTo: "",
_deferred: null,
translate: function(aFrom, aTo) {
this._translateFrom = aFrom;
this._translateTo = aTo;
this._deferred = Promise.defer();
return this._deferred.promise;
this.state = this.STATE_TRANSLATING;
this.translatedFrom = aFrom;
this.translatedTo = aTo;
},
_reset: function() {
this._translateFrom = "";
this._translateTo = "";
this._deferred = null;
this.translatedFrom = "";
this.translatedTo = "";
},
failTranslation: function() {
this._deferred.reject();
this.state = this.STATE_ERROR;
this._reset();
},
finishTranslation: function() {
this._deferred.resolve();
this.showTranslatedContent();
this.state = this.STATE_TRANSLATED;
this._reset();
},
_showOriginalCalled: false,
showOriginalContent: function() {
this._showOriginalCalled = true;
},
_showTranslationCalled: false,
showTranslatedContent: function() {
this._showTranslationCalled = true;
},
showTranslationUI: function(aLanguage) {
let notificationBox = gBrowser.getNotificationBox();
let notif = notificationBox.appendNotification("", "translation", null,
notificationBox.PRIORITY_INFO_HIGH);
notif.init(this);
notif.detectedLanguage = aLanguage;
return notif;
}
};
function test() {
waitForExplicitFinish();
// Show an info bar saying the current page is in French
let notif = Translation.showTranslationUI("fr");
is(notif.state, notif.STATE_OFFER, "the infobar is offering translation");
let tab = gBrowser.addTab();
gBrowser.selectedTab = tab;
tab.linkedBrowser.addEventListener("load", function onload() {
tab.linkedBrowser.removeEventListener("load", onload, true);
TranslationStub.browser = gBrowser.selectedBrowser;
registerCleanupFunction(function () {
gBrowser.removeTab(tab);
});
run_tests(() => {
finish();
});
}, true);
content.location = "data:text/plain,test page";
}
function checkURLBarIcon(aExpectTranslated = false) {
is(!PopupNotifications.getNotification("translate"), aExpectTranslated,
"translate icon " + (aExpectTranslated ? "not " : "") + "shown");
is(!!PopupNotifications.getNotification("translated"), aExpectTranslated,
"translated icon " + (aExpectTranslated ? "" : "not ") + "shown");
}
function run_tests(aFinishCallback) {
info("Show an info bar saying the current page is in French");
let notif = TranslationStub.showTranslationUI("fr");
is(notif.state, TranslationStub.STATE_OFFER, "the infobar is offering translation");
is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed");
checkURLBarIcon();
// Click the "Translate" button
info("Click the 'Translate' button");
notif._getAnonElt("translate").click();
is(notif.state, notif.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!Translation._deferred, "Translation.translate has been called");
is(Translation._translateFrom, "fr", "from language correct");
is(Translation._translateTo, Translation.defaultTargetLanguage, "from language correct");
is(notif.state, TranslationStub.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "fr", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "from language correct");
checkURLBarIcon();
// Make the translation fail and check we are in the error state.
Translation.failTranslation();
is(notif.state, notif.STATE_ERROR, "infobar in the error state");
info("Make the translation fail and check we are in the error state.");
TranslationStub.failTranslation();
is(notif.state, TranslationStub.STATE_ERROR, "infobar in the error state");
checkURLBarIcon();
// Click the try again button
info("Click the try again button");
notif._getAnonElt("tryAgain").click();
is(notif.state, notif.STATE_TRANSLATING, "infobar in the translating state");
ok(!!Translation._deferred, "Translation.translate has been called");
is(Translation._translateFrom, "fr", "from language correct");
is(Translation._translateTo, Translation.defaultTargetLanguage, "to language correct");
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "fr", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "from language correct");
checkURLBarIcon();
// Make the translation succeed and check we are in the 'translated' state.
Translation.finishTranslation();
is(notif.state, notif.STATE_TRANSLATED, "infobar in the translated state");
info("Make the translation succeed and check we are in the 'translated' state.");
TranslationStub.finishTranslation();
is(notif.state, TranslationStub.STATE_TRANSLATED, "infobar in the translated state");
checkURLBarIcon(true);
// Test 'Show Original' / 'Show Translation' buttons.
info("Test 'Show original' / 'Show Translation' buttons.");
// First check 'Show Original' is visible and 'Show Translation' is hidden.
ok(!notif._getAnonElt("showOriginal").hidden, "'Show Original' button visible");
ok(notif._getAnonElt("showTranslation").hidden, "'Show Translation' button hidden");
// Click the button.
notif._getAnonElt("showOriginal").click();
// Check the correct function has been called.
ok(Translation._showOriginalCalled, "'Translation.showOriginalContent' called")
ok(!Translation._showTranslationCalled, "'Translation.showTranslatedContent' not called")
Translation._showOriginalCalled = false;
// Check that the url bar icon shows the original content is displayed.
checkURLBarIcon();
// And the 'Show Translation' button is now visible.
ok(notif._getAnonElt("showOriginal").hidden, "'Show Original' button hidden");
ok(!notif._getAnonElt("showTranslation").hidden, "'Show Translation' button visible");
// Click the 'Show Translation' button
notif._getAnonElt("showTranslation").click();
// Check the correct function has been called.
ok(!Translation._showOriginalCalled, "'Translation.showOriginalContent' not called")
ok(Translation._showTranslationCalled, "'Translation.showTranslatedContent' called")
Translation._showTranslationCalled = false;
// Check that the url bar icon shows the page is translated.
checkURLBarIcon(true);
// Check that the 'Show Original' button is visible again.
ok(!notif._getAnonElt("showOriginal").hidden, "'Show Original' button visible");
ok(notif._getAnonElt("showTranslation").hidden, "'Show Translation' button hidden");
// Check that changing the source language causes a re-translation
info("Check that changing the source language causes a re-translation");
let from = notif._getAnonElt("fromLanguage");
from.value = "es";
from.doCommand();
is(notif.state, notif.STATE_TRANSLATING, "infobar in the translating state");
ok(!!Translation._deferred, "Translation.translate has been called");
is(Translation._translateFrom, "es", "from language correct");
is(Translation._translateTo, Translation.defaultTargetLanguage, "to language correct");
Translation.finishTranslation();
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "es", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "to language correct");
// We want to show the 'translated' icon while re-translating,
// because we are still displaying the previous translation.
checkURLBarIcon(true);
TranslationStub.finishTranslation();
checkURLBarIcon(true);
// Check that changing the target language causes a re-translation
info("Check that changing the target language causes a re-translation");
let to = notif._getAnonElt("toLanguage");
to.value = "pl";
to.doCommand();
is(notif.state, notif.STATE_TRANSLATING, "infobar in the translating state");
ok(!!Translation._deferred, "Translation.translate has been called");
is(Translation._translateFrom, "es", "from language correct");
is(Translation._translateTo, "pl", "to language correct");
Translation.finishTranslation();
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "es", "from language correct");
is(TranslationStub.translatedTo, "pl", "to language correct");
checkURLBarIcon(true);
TranslationStub.finishTranslation();
checkURLBarIcon(true);
// Cleanup.
notif.close();
// Reopen the info bar to check that it's possible to override the detected language.
notif = Translation.showTranslationUI("fr");
is(notif.state, notif.STATE_OFFER, "the infobar is offering translation");
info("Reopen the info bar to check that it's possible to override the detected language.");
notif = TranslationStub.showTranslationUI("fr");
is(notif.state, TranslationStub.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.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!Translation._deferred, "Translation.translate has been called");
is(Translation._translateFrom, "ja", "from language correct");
is(notif.state, TranslationStub.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "ja", "from language correct");
notif.close();
// Reopen one last time to check the 'Not Now' button closes the notification.
notif = Translation.showTranslationUI("fr");
info("Reopen to check the 'Not Now' button closes the notification.");
notif = TranslationStub.showTranslationUI("fr");
let notificationBox = gBrowser.getNotificationBox();
ok(!!notificationBox.getNotificationWithValue("translation"), "there's a 'translate' notification");
notif._getAnonElt("notNow").click();
ok(!notificationBox.getNotificationWithValue("translation"), "no 'translate' notification after clicking 'not now'");
finish();
info("Check that clicking the url bar icon reopens the info bar");
checkURLBarIcon();
// Clicking the anchor element causes a 'showing' event to be sent
// asynchronously to our callback that will then show the infobar.
PopupNotifications.getNotification("translate").anchorElement.click();
waitForCondition(() => !!notificationBox.getNotificationWithValue("translation"), () => {
ok(!!notificationBox.getNotificationWithValue("translation"),
"there's a 'translate' notification");
aFinishCallback();
}, "timeout waiting for the info bar to reappear");
}

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

@ -86,27 +86,10 @@
</xul:hbox>
</content>
<implementation>
<field name="STATE_OFFER" readonly="true">0</field>
<field name="STATE_TRANSLATING" readonly="true">1</field>
<field name="STATE_TRANSLATED" readonly="true">2</field>
<field name="STATE_ERROR" readonly="true">3</field>
<property name="state"
onget="return this._getAnonElt('translationStates').selectedIndex;"
onset="this._getAnonElt('translationStates').selectedIndex = val;"/>
<!-- Initialize the infobar with a translation object exposing these
properties:
- supportedSourceLanguages, array of supported source language codes
- supportedTargetLanguages, array of supported target language codes
- defaultTargetLanguage, code of the language to use by default for
translation.
- translate, method starting the translation of the current page.
Returns a promise.
- showOriginalContent, method showing the original page content.
- showTranslatedContent, method showing the translation for an
already translated page whose original content is shown.
-->
<method name="init">
<parameter name="aTranslation"/>
<body>
@ -116,6 +99,7 @@
.getService(Ci.nsIStringBundleService)
.createBundle("chrome://global/locale/languageNames.properties");
// Fill the lists of supported source languages.
let detectedLanguage = this._getAnonElt("detectedLanguage");
let fromLanguage = this._getAnonElt("fromLanguage");
for (let code of this.translation.supportedSourceLanguages) {
@ -123,10 +107,24 @@
detectedLanguage.appendItem(name, code);
fromLanguage.appendItem(name, code);
}
detectedLanguage.value = this.translation.detectedLanguage;
// translatedFrom is only set if we have already translated this page.
if (aTranslation.translatedFrom)
fromLanguage.value = aTranslation.translatedFrom;
// Fill the list of supporter target languages.
let toLanguage = this._getAnonElt("toLanguage");
for (let code of this.translation.supportedTargetLanguages)
toLanguage.appendItem(bundle.GetStringFromName(code), code);
if (aTranslation.translatedTo)
toLanguage.value = aTranslation.translatedTo;
if (aTranslation.state)
this.state = aTranslation.state;
this._handleButtonHiding(aTranslation.originalShown);
]]>
</body>
</method>
@ -138,34 +136,29 @@
</body>
</method>
<field name="_detectedLanguage">""</field>
<property name="detectedLanguage" onget="return this._detectedLanguage;">
<setter><![CDATA[
this._getAnonElt("detectedLanguage").value = val;
this._detectedLanguage = val;
return val;
]]></setter>
</property>
<method name="translate">
<body>
<![CDATA[
if (this.state == this.STATE_OFFER) {
if (this.state == this.translation.STATE_OFFER) {
this._getAnonElt("fromLanguage").value =
this._getAnonElt("detectedLanguage").value;
this._getAnonElt("toLanguage").value =
this.translation.defaultTargetLanguage;
}
this._getAnonElt("showOriginal").hidden = false;
this._getAnonElt("showTranslation").hidden = true;
this.state = this.STATE_TRANSLATING;
this._handleButtonHiding(false);
this.translation.translate(this._getAnonElt("fromLanguage").value,
this._getAnonElt("toLanguage").value)
.then(() => { this.state = this.STATE_TRANSLATED; },
() => { this.state = this.STATE_ERROR; });
this._getAnonElt("toLanguage").value);
]]>
</body>
</method>
<method name="_handleButtonHiding">
<parameter name="aOriginalShown"/>
<body>
<![CDATA[
this._getAnonElt("showOriginal").hidden = aOriginalShown;
this._getAnonElt("showTranslation").hidden = !aOriginalShown;
]]>
</body>
</method>
@ -173,9 +166,7 @@
<method name="showOriginal">
<body>
<![CDATA[
this._getAnonElt("showOriginal").hidden = true;
this._getAnonElt("showTranslation").hidden = false;
this._handleButtonHiding(true);
this.translation.showOriginalContent();
]]>
</body>
@ -184,9 +175,7 @@
<method name="showTranslation">
<body>
<![CDATA[
this._getAnonElt("showOriginal").hidden = false;
this._getAnonElt("showTranslation").hidden = true;
this._handleButtonHiding(false);
this.translation.showTranslatedContent();
]]>
</body>