Bug 1015527 - Back/Forward navigation shouldn't break the Translation UI, r=felipe, a=gavin.
authorFlorian Quèze <florian@queze.net>
Mon, 09 Jun 2014 16:50:58 +0200
changeset 207076 9a6785bfed9bbcae9f7b46f85ff71d56dcea1ecc
parent 207075 483704a964e27e5094dc4d668bf007e346250b85
child 207077 011d04986808d73e2d8ef5bb790ad1c3203e89d8
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe, gavin
bugs1015527
milestone32.0a2
Bug 1015527 - Back/Forward navigation shouldn't break the Translation UI, r=felipe, a=gavin.
browser/base/content/browser.js
browser/components/translation/Translation.jsm
browser/components/translation/TranslationContentHandler.jsm
browser/components/translation/TranslationDocument.jsm
browser/components/translation/test/browser_translation_exceptions.js
browser/components/translation/test/browser_translation_infobar.js
browser/components/translation/translation-infobar.xml
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5306,18 +5306,18 @@ function stylesheetSwitchAll(contentWind
 function setStyleDisabled(disabled) {
   if (disabled)
     gPageStyleMenu.disableStyle();
 }
 
 
 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);
     });
   }
 };
 
 
 var BrowserOffline = {
   _inited: false,
 
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -19,156 +19,167 @@ Cu.import("resource://gre/modules/Metric
 Cu.import("resource://gre/modules/Task.jsm", this);
 
 const DAILY_COUNTER_FIELD = {type: Metrics.Storage.FIELD_DAILY_COUNTER};
 const DAILY_LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_TEXT};
 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"],
 
   _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;
   },
 
-  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;
 
-    aBrowser.translationUI.showTranslationUI(aDetectedLanguage);
+    // 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;
+
+    trUI.showURLBarIcon();
+
+    if (trUI.shouldShowInfoBar(aBrowser.currentURI))
+      trUI.showTranslationInfoBar();
   }
 };
 
 /* 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:
  * - 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.
  * - 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.
  */
 function TranslationUI(aBrowser) {
   this.browser = aBrowser;
   aBrowser.messageManager.addMessageListener("Translation:Finished", this);
 }
 
 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;
 
     this.browser.messageManager.sendAsyncMessage(
       "Translation:TranslateDocument",
       { from: aFrom, to: aTo }
     );
   },
 
-  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)
       PopupNotifications.remove(notification);
 
     let callback = aTopic => {
       if (aTopic != "showing")
         return false;
       let notification = this.notificationBox.getNotificationWithValue("translation");
       if (notification)
         notification.close();
       else
         this.showTranslationInfoBar();
       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});
   },
 
   _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.originalShown = true;
     this.showURLBarIcon();
-    this.originalShown = true;
     this.browser.messageManager.sendAsyncMessage("Translation:ShowOriginal");
   },
 
   showTranslatedContent: function() {
-    this.showURLBarIcon(true);
     this.originalShown = false;
+    this.showURLBarIcon();
     this.browser.messageManager.sendAsyncMessage("Translation:ShowTranslation");
   },
 
   get notificationBox() this.browser.ownerGlobal.gBrowser.getNotificationBox(this.browser),
 
   showTranslationInfoBar: function() {
     let notificationBox = this.notificationBox;
     let notif = notificationBox.appendNotification("", "translation", null,
                                                    notificationBox.PRIORITY_INFO_HIGH);
     notif.init(this);
     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.
     let perms = Services.perms;
     return perms.testExactPermission(aURI, "translate") != perms.DENY_ACTION;
   },
 
   showTranslationUI: function(aDetectedLanguage) {
@@ -177,35 +188,35 @@ TranslationUI.prototype = {
     // Reset all values before showing a new translation infobar.
     this.state = 0;
     this.translatedFrom = "";
     this.translatedTo = "";
     this.originalShown = true;
 
     this.showURLBarIcon();
 
-    if (!this.shouldShowInfoBar(this.browser.currentURI, aDetectedLanguage))
+    if (!this.shouldShowInfoBar(this.browser.currentURI))
       return null;
 
     return this.showTranslationInfoBar();
   },
 
   receiveMessage: function(msg) {
     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();
 
           // Record the number of characters translated.
           TranslationHealthReport.recordTranslation(msg.data.from, msg.data.to,
                                                     msg.data.characterCount);
         } else {
-          this.state = this.STATE_ERROR;
+          this.state = Translation.STATE_ERROR;
         }
         break;
     }
   }
 };
 
 /**
  * Helper methods for recording translation data in FHR.
--- a/browser/components/translation/TranslationContentHandler.jsm
+++ b/browser/components/translation/TranslationContentHandler.jsm
@@ -7,52 +7,100 @@
 this.EXPORTED_SYMBOLS = [ "TranslationContentHandler" ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 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);
   this.global = global;
 }
 
 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 ||
         !(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) ||
         !this.global.content)
       return;
 
     let url = aRequest.name;
     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.
     if (string.length < 100)
       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);
     });
   },
 
   // Unused methods.
   onProgressChange: function() {},
   onLocationChange: function() {},
   onStatusChange:   function() {},
   onSecurityChange: function() {},
@@ -73,27 +121,32 @@ TranslationContentHandler.prototype = {
         // translated text.
         let translationDocument = this.global.content.translationDocument ||
                                   new TranslationDocument(this.global.content.document);
         let bingTranslation = new BingTranslation(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;
+
         bingTranslation.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;
             this.global.sendAsyncMessage("Translation:Finished", {success: false});
           }
         );
         break;
       }
 
       case "Translation:ShowOriginal":
         this.global.content.translationDocument.showOriginal();
--- a/browser/components/translation/TranslationDocument.jsm
+++ b/browser/components/translation/TranslationDocument.jsm
@@ -26,16 +26,21 @@ Cu.import("resource://gre/modules/Task.j
  */
 this.TranslationDocument = function(document) {
   this.itemsMap = new Map();
   this.roots = [];
   this._init(document);
 };
 
 this.TranslationDocument.prototype = {
+  translatedFrom: "",
+  translatedTo: "",
+  translationError: false,
+  originalShown: true,
+
   /**
    * Initializes the object and populates
    * the roots lists.
    *
    * @param document  The document to be translated
    */
   _init: function(document) {
     let window = document.defaultView;
@@ -180,24 +185,26 @@ this.TranslationDocument.prototype = {
     return generateTranslationHtmlForItem(item, str);
   },
 
   /**
    * Changes the document to display its translated
    * content.
    */
   showTranslation: function() {
+    this.originalShown = false;
     this._swapDocumentContent("translation");
   },
 
   /**
    * Changes the document to display its original
    * content.
    */
   showOriginal: function() {
+    this.originalShown = true;
     this._swapDocumentContent("original");
   },
 
   /**
    * Swap the document with the resulting translation,
    * or back with the original content.
    *
    * @param target   A string that is either "translation"
--- a/browser/components/translation/test/browser_translation_exceptions.js
+++ b/browser/components/translation/test/browser_translation_exceptions.js
@@ -99,17 +99,20 @@ let gTests = [
        "we start with an empty list of sites to never translate");
   }
 },
 
 {
   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;
     let uri = gBrowser.selectedBrowser.currentURI;
     ok(ui.shouldShowInfoBar(uri, "fr"),
        "check shouldShowInfoBar initially returns true");
 
     // Open the "options" drop down.
@@ -145,17 +148,20 @@ let gTests = [
     notif.close();
   }
 },
 
 {
   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;
     let uri = gBrowser.selectedBrowser.currentURI;
     ok(ui.shouldShowInfoBar(uri, "fr"),
        "check shouldShowInfoBar initially returns true");
 
     // Open the "options" drop down.
--- a/browser/components/translation/test/browser_translation_infobar.js
+++ b/browser/components/translation/test/browser_translation_infobar.js
@@ -29,41 +29,43 @@ function waitForCondition(condition, nex
     }
     tries++;
   }, 100);
   var moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
 var TranslationStub = {
   translate: function(aFrom, aTo) {
-    this.state = this.STATE_TRANSLATING;
+    this.state = Translation.STATE_TRANSLATING;
     this.translatedFrom = aFrom;
     this.translatedTo = aTo;
   },
 
   _reset: function() {
     this.translatedFrom = "";
     this.translatedTo = "";
   },
 
   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];
   return ui.notificationBox.getNotificationWithValue("translation");
 }
 
 function hasTranslationInfoBar() {
   return !!gBrowser.getNotificationBox().getNotificationWithValue("translation");
@@ -95,44 +97,44 @@ function checkURLBarIcon(aExpectTranslat
      "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 = 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");
   checkURLBarIcon();
 
   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");
   checkURLBarIcon();
 
   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.");
   // 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();
@@ -148,49 +150,49 @@ function run_tests(aFinishCallback) {
   // 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");
 
   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.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");
   // We want to show the 'translated' icon while re-translating,
   // because we are still displaying the previous translation.
   checkURLBarIcon(true);
   notif.translation.finishTranslation();
   checkURLBarIcon(true);
 
   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.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");
   checkURLBarIcon(true);
   notif.translation.finishTranslation();
   checkURLBarIcon(true);
 
   // Cleanup.
   notif.close();
 
   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();
 
   info("Reopen to check the 'Not Now' button closes the notification.");
   notif = showTranslationUI("fr");
   let notificationBox = gBrowser.getNotificationBox();
   is(hasTranslationInfoBar(), true, "there's a 'translate' notification");
--- a/browser/components/translation/translation-infobar.xml
+++ b/browser/components/translation/translation-infobar.xml
@@ -122,17 +122,17 @@
           let deck = this._getAnonElt('translationStates');
 
           let activeElt = document.activeElement;
           if (activeElt && deck.contains(activeElt))
             activeElt.blur();
 
           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;
             }
           }
           this.setAttribute("state", stateName);
 
           deck.selectedIndex = val;
           ]]>
@@ -183,17 +183,17 @@
         <body>
           return document.getAnonymousElementByAttribute(this, "anonid", aAnonId);
         </body>
       </method>
 
       <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 =
                 Translation.defaultTargetLanguage;
             }
 
             this._handleButtonHiding(false);
             this.translation.translate(this._getAnonElt("fromLanguage").value,
@@ -230,17 +230,17 @@
         </body>
       </method>
 
       <method name="optionsShowing">
         <body>
           <![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;
             let langBundle =
               Cc["@mozilla.org/intl/stringbundle;1"]
                 .getService(Ci.nsIStringBundleService)
                 .createBundle("chrome://global/locale/languageNames.properties");
             let langName = langBundle.GetStringFromName(lang);