Bug 973292 - Record the number of characters that are translated with FHR. r=felipe a=gavin
authorTim Taubert <ttaubert@mozilla.com>
Wed, 11 Jun 2014 15:57:52 +0200
changeset 207009 625c2e5272d55a08f29562a370cf9acd7b9fecd4
parent 207008 93b24685a72dfbc4984014f094f615b12d82aaaf
child 207010 290cb33fc0177f1ec5d0ffa3a1abc6612bdc877f
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
bugs973292
milestone32.0a2
Bug 973292 - Record the number of characters that are translated with FHR. r=felipe a=gavin
browser/components/translation/BingTranslator.jsm
browser/components/translation/Translation.jsm
browser/components/translation/TranslationContentHandler.jsm
browser/components/translation/test/browser.ini
browser/components/translation/test/browser_translation_fhr.js
--- a/browser/components/translation/BingTranslator.jsm
+++ b/browser/components/translation/BingTranslator.jsm
@@ -41,16 +41,17 @@ const MAX_REQUESTS = 15;
  *                             task is finished.
  */
 this.BingTranslation = function(translationDocument, sourceLanguage, targetLanguage) {
   this.translationDocument = translationDocument;
   this.sourceLanguage = sourceLanguage;
   this.targetLanguage = targetLanguage;
   this._pendingRequests = 0;
   this._partialSuccess = false;
+  this._translatedCharacterCount = 0;
 };
 
 this.BingTranslation.prototype = {
   /**
    * Performs the translation, splitting the document into several chunks
    * respecting the data limits of the API.
    *
    * @returns {Promise}          A promise that will resolve when the translation
@@ -100,27 +101,31 @@ this.BingTranslation.prototype = {
    * @param   request   The BingRequest sent to the server.
    */
   _chunkCompleted: function(bingRequest) {
      this._pendingRequests--;
      if (bingRequest.requestSucceeded &&
          this._parseChunkResult(bingRequest)) {
        // error on request
        this._partialSuccess = true;
+       // Count the number of characters successfully translated.
+       this._translatedCharacterCount += bingRequest.characterCount;
      }
 
     // Check if all pending requests have been
     // completed and then resolves the promise.
     // If at least one chunk was successful, the
     // promise will be resolved positively which will
     // display the "Success" state for the infobar. Otherwise,
     // the "Error" state will appear.
     if (this._pendingRequests == 0) {
       if (this._partialSuccess) {
-        this._onFinishedDeferred.resolve("success");
+        this._onFinishedDeferred.resolve({
+          characterCount: this._translatedCharacterCount
+        });
       } else {
         this._onFinishedDeferred.reject("failure");
       }
     }
   },
 
   /**
    * This function parses the result returned by Bing's Http.svc API,
@@ -232,16 +237,17 @@ this.BingTranslation.prototype = {
  * @param sourceLanguage    The source language of the document.
  * @param targetLanguage    The target language for the translation.
  *
  */
 function BingRequest(translationData, sourceLanguage, targetLanguage) {
   this.translationData = translationData;
   this.sourceLanguage = sourceLanguage;
   this.targetLanguage = targetLanguage;
+  this.characterCount = 0;
 }
 
 BingRequest.prototype = {
   /**
    * Initiates the request
    */
   fireRequest: function() {
     return Task.spawn(function *(){
@@ -258,16 +264,17 @@ BingRequest.prototype = {
           '<Options>' +
             '<ContentType xmlns="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2">text/html</ContentType>' +
             '<ReservedFlags xmlns="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2" />' +
           '</Options>' +
           '<Texts xmlns:s="http://schemas.microsoft.com/2003/10/Serialization/Arrays">';
 
       for (let [, text] of this.translationData) {
         requestString += '<s:string>' + text + '</s:string>';
+        this.characterCount += text.length;
       }
 
       requestString += '</Texts>' +
           '<To>' + this.targetLanguage + '</To>' +
         '</TranslateArrayRequest>';
 
       let utf8 = CommonUtils.encodeUTF8(requestString);
 
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -190,16 +190,20 @@ TranslationUI.prototype = {
 
   receiveMessage: function(msg) {
     switch (msg.name) {
       case "Translation:Finished":
         if (msg.data.success) {
           this.state = this.STATE_TRANSLATED;
           this.showURLBarIcon(true);
           this.originalShown = false;
+
+          // Record the number of characters translated.
+          TranslationHealthReport.recordTranslation(msg.data.from, msg.data.to,
+                                                    msg.data.characterCount);
         } else {
           this.state = this.STATE_ERROR;
         }
         break;
     }
   }
 };
 
--- a/browser/components/translation/TranslationContentHandler.jsm
+++ b/browser/components/translation/TranslationContentHandler.jsm
@@ -74,18 +74,23 @@ TranslationContentHandler.prototype = {
         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;
         bingTranslation.translate().then(
-          success => {
-            this.global.sendAsyncMessage("Translation:Finished", {success: true});
+          result => {
+            this.global.sendAsyncMessage("Translation:Finished", {
+              characterCount: result.characterCount,
+              from: msg.data.from,
+              to: msg.data.to,
+              success: true
+            });
             translationDocument.showTranslation();
           },
           error => {
             this.global.sendAsyncMessage("Translation:Finished", {success: false});
           }
         );
         break;
       }
--- a/browser/components/translation/test/browser.ini
+++ b/browser/components/translation/test/browser.ini
@@ -1,4 +1,6 @@
 [DEFAULT]
 
+[browser_translation_fhr.js]
+skip-if = true # Needs to wait until bug 1022725.
 [browser_translation_infobar.js]
 [browser_translation_exceptions.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/translation/test/browser_translation_fhr.js
@@ -0,0 +1,106 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let tmp = {};
+Cu.import("resource:///modules/translation/Translation.jsm", tmp);
+let {Translation} = tmp;
+
+add_task(function* setup() {
+  Services.prefs.setBoolPref("toolkit.telemetry.enabled", true);
+  Services.prefs.setBoolPref("browser.translation.detectLanguage", true);
+  Services.prefs.setBoolPref("browser.translation.ui.show", true);
+
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("toolkit.telemetry.enabled");
+    Services.prefs.clearUserPref("browser.translation.detectLanguage");
+    Services.prefs.clearUserPref("browser.translation.ui.show");
+  });
+});
+
+add_task(function* test_fhr() {
+  let start = new Date();
+
+  // Translate a page.
+  yield translate("<h1>Hallo Welt!</h1>", "de", "en");
+  let [pageCount, charCount] = yield retrieveTranslationCounts();
+
+  // Translate another page.
+  yield translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de", "en");
+
+  let [pageCount2, charCount2] = yield retrieveTranslationCounts();
+
+  // Check that it's still the same day of the month as when we started. This
+  // prevents intermittent failures when the test starts before and ends after
+  // midnight.
+  if (start.getDate() == new Date().getDate()) {
+    Assert.equal(pageCount2, pageCount + 1);
+    Assert.equal(charCount2, charCount + 21);
+  }
+});
+
+function retrieveTranslationCounts() {
+  return Task.spawn(function* task_retrieve_counts() {
+    let svc = Cc["@mozilla.org/datareporting/service;1"].getService();
+    let reporter = svc.wrappedJSObject.healthReporter;
+    yield reporter.onInit();
+
+    // Get the provider.
+    let provider = reporter.getProvider("org.mozilla.translation");
+    let measurement = provider.getMeasurement("translation", 1);
+    let values = yield measurement.getValues();
+
+    let day = values.days.getDay(new Date());
+    if (!day) {
+      // This should never happen except when the test runs at midnight.
+      return [0, 0];
+    }
+
+    return [day.get("pageTranslatedCount"), day.get("charactersTranslatedCount")];
+  });
+}
+
+function translate(text, from, to) {
+  return Task.spawn(function* task_translate() {
+    // Create some content to translate.
+    let tab = gBrowser.selectedTab =
+      gBrowser.addTab("data:text/html;charset=utf-8," + text);
+
+    // Wait until that's loaded.
+    let browser = tab.linkedBrowser;
+    yield promiseBrowserLoaded(browser);
+
+    // Send a translation offer.
+    Translation.documentStateReceived(browser, {state: Translation.STATE_OFFER,
+                                                originalShown: true,
+                                                detectedLanguage: from});
+
+    // Translate the page.
+    browser.translationUI.translate(from, to);
+    yield waitForMessage(browser, "Translation:Finished");
+
+    // Cleanup.
+    gBrowser.removeTab(tab);
+  });
+}
+
+function waitForMessage({messageManager}, name) {
+  return new Promise(resolve => {
+    messageManager.addMessageListener(name, function onMessage() {
+      messageManager.removeMessageListener(name, onMessage);
+      resolve();
+    });
+  });
+}
+
+function promiseBrowserLoaded(browser) {
+  return new Promise(resolve => {
+    browser.addEventListener("load", function onLoad(event) {
+      if (event.target == browser.contentDocument) {
+        browser.removeEventListener("load", onLoad, true);
+        resolve();
+      }
+    }, true);
+  });
+}