Bug 977774 - Count the number of times a user opts out of Instant Translation. r=felipe. a=gavin
authorAsaf Romano <mano@mozilla.com>
Tue, 24 Jun 2014 11:40:24 +0300
changeset 207349 7717946aeb52207db46230eb0e95f3ff5491e93f
parent 207348 aa4cb3562ef13b0e1b7428f71e71068ba3946367
child 207350 b17023fb1dc476d2a7fc32daf2290c4cac3a0268
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
bugs977774
milestone32.0a2
Bug 977774 - Count the number of times a user opts out of Instant Translation. r=felipe. a=gavin
browser/components/translation/Translation.jsm
browser/components/translation/test/browser_translation_fhr.js
browser/components/translation/translation-infobar.xml
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -250,16 +250,21 @@ TranslationUI.prototype = {
         } else if (msg.data.unavailable) {
           Translation.serviceUnavailable = true;
           this.state = Translation.STATE_UNAVAILABLE;
         } else {
           this.state = Translation.STATE_ERROR;
         }
         break;
     }
+  },
+
+  infobarClosed: function() {
+    if (this.state == Translation.STATE_OFFER)
+      TranslationHealthReport.recordDeniedTranslationOffer();
   }
 };
 
 /**
  * Helper methods for recording translation data in FHR.
  */
 let TranslationHealthReport = {
   /**
--- a/browser/components/translation/test/browser_translation_fhr.js
+++ b/browser/components/translation/test/browser_translation_fhr.js
@@ -2,92 +2,155 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 let tmp = {};
 Cu.import("resource:///modules/translation/Translation.jsm", tmp);
 let {Translation} = tmp;
 
+let MetricsChecker = {
+  _metricsTime: new Date(),
+  _midnightError: new Error("Getting metrics around midnight may fail sometimes"),
+
+  updateMetrics: Task.async(function* () {
+    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 metricsTime = new Date();
+    let day = values.days.getDay(metricsTime);
+    if (!day) {
+      // This should never happen except when the test runs at midnight.
+      throw this._midnightError;
+    }
+
+    // .get() may return `undefined`, which we can't compute.
+    this._metrics = {
+      pageCount: day.get("pageTranslatedCount") || 0,
+      charCount: day.get("charactersTranslatedCount") || 0,
+      deniedOffers: day.get("deniedTranslationOffer") || 0
+    };
+    this._metricsTime = metricsTime;
+  }),
+
+  checkAdditions: Task.async(function* (additions) {
+    let prevMetrics = this._metrics, prevMetricsTime = this._metricsTime;
+    try {
+      yield this.updateMetrics();
+    } catch(ex if ex == this._midnightError) {
+      return;
+    }
+
+    // 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 (this._metricsTime.getDate() != prevMetricsTime.getDate()) {
+      for (let metric of Object.keys(prevMetrics)) {
+        prevMetrics[metric] = 0;
+      }
+    }
+
+    for (let metric of Object.keys(additions)) {
+      Assert.equal(prevMetrics[metric] + additions[metric], this._metrics[metric]);
+    }
+  })
+};
 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");
   });
+
+  yield MetricsChecker.updateMetrics();
 });
 
 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);
-  }
+  yield MetricsChecker.checkAdditions({ pageCount: 1, charCount: 21, deniedOffers: 0});
 });
 
-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();
+add_task(function* test_deny_translation_metric() {
+  function* offerAndDeny(elementAnonid) {
+    let tab = yield offerTranslatationFor("<h1>Hallo Welt!</h1>", "de", "en");
+    getInfobarElement(tab.linkedBrowser, elementAnonid).doCommand();
+    yield MetricsChecker.checkAdditions({ deniedOffers: 1 });
+    gBrowser.removeTab(tab);
+  }
+
+  yield offerAndDeny("notNow");
+  yield offerAndDeny("neverForSite");
+  yield offerAndDeny("neverForLanguage");
+  yield offerAndDeny("closeButton");
 
-    // Get the provider.
-    let provider = reporter.getProvider("org.mozilla.translation");
-    let measurement = provider.getMeasurement("translation", 1);
-    let values = yield measurement.getValues();
+  // Test that the close button doesn't record a denied translation if
+  // the infobar is not in its "offer" state.
+  let tab =
+    yield translate("<h1>Hallo Welt!</h1>", "de", "en", false);
+  yield MetricsChecker.checkAdditions({ deniedOffers: 0 });
+  gBrowser.removeTab(tab);
+});
 
-    let day = values.days.getDay(new Date());
-    if (!day) {
-      // This should never happen except when the test runs at midnight.
-      return [0, 0];
-    }
-
-    // .get() may return `undefined`, which we can't compute.
-    return [day.get("pageTranslatedCount") || 0, day.get("charactersTranslatedCount") || 0];
-  });
+function getInfobarElement(browser, anonid) {
+  let notif = browser.translationUI
+                     .notificationBox.getNotificationWithValue("translation");
+  return notif._getAnonElt(anonid);
 }
 
-function translate(text, from, to) {
-  return Task.spawn(function* task_translate() {
+function offerTranslatationFor(text, from) {
+  return Task.spawn(function* task_offer_translation() {
     // 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);
+    return tab;
+  });
+}
+
+function acceptTranslationOffer(tab, to) {
+  return Task.spawn(function* task_accept_translation_offer() {
+    let browser = tab.linkedBrowser;
+    getInfobarElement(browser, "toLanguage").value = to;
+    getInfobarElement(browser, "toLanguage").doCommand();
     yield waitForMessage(browser, "Translation:Finished");
+  });
+}
 
-    // Cleanup.
-    gBrowser.removeTab(tab);
+function translate(text, from, to, closeTab = true) {
+  return Task.spawn(function* task_translate() {
+    let tab = yield offerTranslatationFor(text, from);
+    yield acceptTranslationOffer(tab, to);
+    if (closeTab) {
+      gBrowser.removeTab(tab);
+    } else {
+      return tab;
+    }
   });
 }
 
 function waitForMessage({messageManager}, name) {
   return new Promise(resolve => {
     messageManager.addMessageListener(name, function onMessage() {
       messageManager.removeMessageListener(name, onMessage);
       resolve();
--- a/browser/components/translation/translation-infobar.xml
+++ b/browser/components/translation/translation-infobar.xml
@@ -33,17 +33,17 @@
               </xul:menulist>
               <xul:label class="translate-infobar-element" value="&translation.translateThisPage.label;"/>
               <xul:button class="translate-infobar-element"
                           label="&translation.translate.button;"
                           anonid="translate"
                           oncommand="document.getBindingParent(this).translate();"/>
               <xul:button class="translate-infobar-element"
                           label="&translation.notNow.button;" anonid="notNow"
-                          oncommand="document.getBindingParent(this).close();"/>
+                          oncommand="document.getBindingParent(this).closeCommand();"/>
             </xul:hbox>
 
             <!-- translating -->
             <xul:vbox class="translating-box" pack="center">
               <xul:label class="translate-infobar-element"
                          value="&translation.translatingContent.label;"/>
             </xul:vbox>
 
@@ -117,20 +117,21 @@
                 <xul:image src="chrome://browser/content/microsoft-translator-attribution.png"
                            aria-label="Microsoft Translator"/>
               </xul:menuitem>
             </xul:menupopup>
           </xul:button>
 
         </xul:hbox>
         <xul:toolbarbutton ondblclick="event.stopPropagation();"
+                           anonid="closeButton"
                            class="messageCloseButton close-icon tabbable"
                            xbl:inherits="hidden=hideclose"
                            tooltiptext="&closeNotification.tooltip;"
-                           oncommand="document.getBindingParent(this).close();"/>
+                           oncommand="document.getBindingParent(this).closeCommand();"/>
       </xul:hbox>
     </content>
     <implementation>
       <property name="state"
                 onget="return this._getAnonElt('translationStates').selectedIndex;">
         <setter>
           <![CDATA[
           let deck = this._getAnonElt('translationStates');
@@ -211,16 +212,26 @@
             }
 
             this.translation.translate(this._getAnonElt("fromLanguage").value,
                                        this._getAnonElt("toLanguage").value);
           ]]>
         </body>
       </method>
 
+      <!-- To be called when the infobar should be closed per user's wish (e.g.
+           by clicking the notification's close button -->
+      <method name="closeCommand">
+        <body>
+          <![CDATA[
+            this.close();
+            this.translation.infobarClosed();
+          ]]>
+        </body>
+      </method>
       <method name="_handleButtonHiding">
         <body>
           <![CDATA[
             let originalShown = this.translation.originalShown;
             this._getAnonElt("showOriginal").hidden = originalShown;
             this._getAnonElt("showTranslation").hidden = !originalShown;
           ]]>
         </body>
@@ -296,29 +307,29 @@
 
             let val = Services.prefs.getCharPref(kPrefName);
             if (val)
               val += ",";
             val += this._getAnonElt("neverForLanguage").langCode;
 
             Services.prefs.setCharPref(kPrefName, val);
 
-            this.close();
+            this.closeCommand();
           ]]>
         </body>
       </method>
 
       <method name="neverForSite">
         <body>
           <![CDATA[
             let uri = this.translation.browser.currentURI;
             let perms = Services.perms;
             perms.add(uri, "translate", perms.DENY_ACTION);
 
-            this.close();
+            this.closeCommand();
           ]]>
         </body>
       </method>
 
       <method name="openProviderAttribution">
         <body>
           <![CDATA[
             Translation.openProviderAttribution();