forked from mirrors/gecko-dev
		
	Differential Revision: https://phabricator.services.mozilla.com/D17526 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			297 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			297 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* Any copyright is dedicated to the Public Domain.
 | 
						|
 * http://creativecommons.org/publicdomain/zero/1.0/ */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
var tmp = {};
 | 
						|
ChromeUtils.import("resource:///modules/translation/Translation.jsm", tmp);
 | 
						|
var {Translation, TranslationTelemetry} = tmp;
 | 
						|
const Telemetry = Services.telemetry;
 | 
						|
 | 
						|
var MetricsChecker = {
 | 
						|
  HISTOGRAMS: {
 | 
						|
    OPPORTUNITIES: Services.telemetry.getHistogramById("TRANSLATION_OPPORTUNITIES"),
 | 
						|
    OPPORTUNITIES_BY_LANG: Services.telemetry.getKeyedHistogramById("TRANSLATION_OPPORTUNITIES_BY_LANGUAGE"),
 | 
						|
    PAGES: Services.telemetry.getHistogramById("TRANSLATED_PAGES"),
 | 
						|
    PAGES_BY_LANG: Services.telemetry.getKeyedHistogramById("TRANSLATED_PAGES_BY_LANGUAGE"),
 | 
						|
    CHARACTERS: Services.telemetry.getHistogramById("TRANSLATED_CHARACTERS"),
 | 
						|
    DENIED: Services.telemetry.getHistogramById("DENIED_TRANSLATION_OFFERS"),
 | 
						|
    AUTO_REJECTED: Services.telemetry.getHistogramById("AUTO_REJECTED_TRANSLATION_OFFERS"),
 | 
						|
    SHOW_ORIGINAL: Services.telemetry.getHistogramById("REQUESTS_OF_ORIGINAL_CONTENT"),
 | 
						|
    TARGET_CHANGES: Services.telemetry.getHistogramById("CHANGES_OF_TARGET_LANGUAGE"),
 | 
						|
    DETECTION_CHANGES: Services.telemetry.getHistogramById("CHANGES_OF_DETECTED_LANGUAGE"),
 | 
						|
    SHOW_UI: Services.telemetry.getHistogramById("SHOULD_TRANSLATION_UI_APPEAR"),
 | 
						|
    DETECT_LANG: Services.telemetry.getHistogramById("SHOULD_AUTO_DETECT_LANGUAGE"),
 | 
						|
  },
 | 
						|
 | 
						|
  reset() {
 | 
						|
    for (let i of Object.keys(this.HISTOGRAMS)) {
 | 
						|
      this.HISTOGRAMS[i].clear();
 | 
						|
    }
 | 
						|
    this.updateMetrics();
 | 
						|
  },
 | 
						|
 | 
						|
  updateMetrics() {
 | 
						|
    this._metrics = {
 | 
						|
      opportunitiesCount: this.HISTOGRAMS.OPPORTUNITIES.snapshot().sum || 0,
 | 
						|
      pageCount: this.HISTOGRAMS.PAGES.snapshot().sum || 0,
 | 
						|
      charCount: this.HISTOGRAMS.CHARACTERS.snapshot().sum || 0,
 | 
						|
      deniedOffers: this.HISTOGRAMS.DENIED.snapshot().sum || 0,
 | 
						|
      autoRejectedOffers: this.HISTOGRAMS.AUTO_REJECTED.snapshot().sum || 0,
 | 
						|
      showOriginal: this.HISTOGRAMS.SHOW_ORIGINAL.snapshot().sum || 0,
 | 
						|
      detectedLanguageChangedBefore: this.HISTOGRAMS.DETECTION_CHANGES.snapshot().values[1] || 0,
 | 
						|
      detectedLanguageChangeAfter: this.HISTOGRAMS.DETECTION_CHANGES.snapshot().values[0] || 0,
 | 
						|
      targetLanguageChanged: this.HISTOGRAMS.TARGET_CHANGES.snapshot().sum || 0,
 | 
						|
      showUI: this.HISTOGRAMS.SHOW_UI.snapshot().sum || 0,
 | 
						|
      detectLang: this.HISTOGRAMS.DETECT_LANG.snapshot().sum || 0,
 | 
						|
      // Metrics for Keyed histograms are estimated below.
 | 
						|
      opportunitiesCountByLang: {},
 | 
						|
      pageCountByLang: {},
 | 
						|
    };
 | 
						|
 | 
						|
    let opportunities = this.HISTOGRAMS.OPPORTUNITIES_BY_LANG.snapshot();
 | 
						|
    let pages = this.HISTOGRAMS.PAGES_BY_LANG.snapshot();
 | 
						|
    for (let source of Translation.supportedSourceLanguages) {
 | 
						|
      this._metrics.opportunitiesCountByLang[source] = opportunities[source] ?
 | 
						|
        opportunities[source].sum : 0;
 | 
						|
      for (let target of Translation.supportedTargetLanguages) {
 | 
						|
        if (source === target) continue;
 | 
						|
        let key = source + " -> " + target;
 | 
						|
        this._metrics.pageCountByLang[key] = pages[key] ? pages[key].sum : 0;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * A recurrent loop for making assertions about collected metrics.
 | 
						|
   */
 | 
						|
  _assertionLoop(prevMetrics, metrics, additions) {
 | 
						|
    for (let metric of Object.keys(additions)) {
 | 
						|
      let addition = additions[metric];
 | 
						|
      // Allows nesting metrics. Useful for keyed histograms.
 | 
						|
      if (typeof addition === "object") {
 | 
						|
        this._assertionLoop(prevMetrics[metric], metrics[metric], addition);
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      Assert.equal(prevMetrics[metric] + addition, metrics[metric]);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  checkAdditions(additions) {
 | 
						|
    let prevMetrics = this._metrics;
 | 
						|
    this.updateMetrics();
 | 
						|
    this._assertionLoop(prevMetrics, this._metrics, additions);
 | 
						|
  },
 | 
						|
 | 
						|
};
 | 
						|
 | 
						|
function getInfobarElement(browser, anonid) {
 | 
						|
  let notif = browser.translationUI
 | 
						|
                     .notificationBox.getNotificationWithValue("translation");
 | 
						|
  return notif._getAnonElt(anonid);
 | 
						|
}
 | 
						|
 | 
						|
var offerTranslationFor = async function(text, from) {
 | 
						|
  // Create some content to translate.
 | 
						|
  const dataUrl = "data:text/html;charset=utf-8," + text;
 | 
						|
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, dataUrl);
 | 
						|
 | 
						|
  let browser = gBrowser.getBrowserForTab(tab);
 | 
						|
 | 
						|
  // Send a translation offer.
 | 
						|
  Translation.documentStateReceived(browser, {state: Translation.STATE_OFFER,
 | 
						|
                                              originalShown: true,
 | 
						|
                                              detectedLanguage: from});
 | 
						|
 | 
						|
  return tab;
 | 
						|
};
 | 
						|
 | 
						|
var acceptTranslationOffer = async function(tab) {
 | 
						|
  let browser = tab.linkedBrowser;
 | 
						|
  getInfobarElement(browser, "translate").doCommand();
 | 
						|
  await waitForMessage(browser, "Translation:Finished");
 | 
						|
};
 | 
						|
 | 
						|
var translate = async function(text, from, closeTab = true) {
 | 
						|
  let tab = await offerTranslationFor(text, from);
 | 
						|
  await acceptTranslationOffer(tab);
 | 
						|
  if (closeTab) {
 | 
						|
    gBrowser.removeTab(tab);
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
  return tab;
 | 
						|
};
 | 
						|
 | 
						|
function waitForMessage({messageManager}, name) {
 | 
						|
  return new Promise(resolve => {
 | 
						|
    messageManager.addMessageListener(name, function onMessage() {
 | 
						|
      messageManager.removeMessageListener(name, onMessage);
 | 
						|
      resolve();
 | 
						|
    });
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function simulateUserSelectInMenulist(menulist, value) {
 | 
						|
  menulist.value = value;
 | 
						|
  menulist.doCommand();
 | 
						|
}
 | 
						|
 | 
						|
add_task(async function setup() {
 | 
						|
  const setupPrefs = prefs => {
 | 
						|
    let prefsBackup = {};
 | 
						|
    for (let p of prefs) {
 | 
						|
      prefsBackup[p] = Services.prefs.setBoolPref;
 | 
						|
      Services.prefs.setBoolPref(p, true);
 | 
						|
    }
 | 
						|
    return prefsBackup;
 | 
						|
  };
 | 
						|
 | 
						|
  const restorePrefs = (prefs, backup) => {
 | 
						|
    for (let p of prefs) {
 | 
						|
      Services.prefs.setBoolPref(p, backup[p]);
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  const prefs = [
 | 
						|
    "browser.translation.detectLanguage",
 | 
						|
    "browser.translation.ui.show",
 | 
						|
  ];
 | 
						|
 | 
						|
  let prefsBackup = setupPrefs(prefs);
 | 
						|
 | 
						|
  let oldCanRecord = Telemetry.canRecordExtended;
 | 
						|
  Telemetry.canRecordExtended = true;
 | 
						|
 | 
						|
  registerCleanupFunction(() => {
 | 
						|
    restorePrefs(prefs, prefsBackup);
 | 
						|
    Telemetry.canRecordExtended = oldCanRecord;
 | 
						|
  });
 | 
						|
 | 
						|
  // Reset histogram metrics.
 | 
						|
  MetricsChecker.reset();
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_telemetry() {
 | 
						|
  // Translate a page.
 | 
						|
  await translate("<h1>Привет, мир!</h1>", "ru");
 | 
						|
 | 
						|
  // Translate another page.
 | 
						|
  await translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de");
 | 
						|
  await MetricsChecker.checkAdditions({
 | 
						|
    opportunitiesCount: 2,
 | 
						|
    opportunitiesCountByLang: { "ru": 1, "de": 1 },
 | 
						|
    pageCount: 1,
 | 
						|
    pageCountByLang: { "de -> en": 1 },
 | 
						|
    charCount: 21,
 | 
						|
    deniedOffers: 0,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_deny_translation_metric() {
 | 
						|
  async function offerAndDeny(elementAnonid) {
 | 
						|
    let tab = await offerTranslationFor("<h1>Hallo Welt!</h1>", "de", "en");
 | 
						|
    getInfobarElement(tab.linkedBrowser, elementAnonid).doCommand();
 | 
						|
    await MetricsChecker.checkAdditions({ deniedOffers: 1 });
 | 
						|
    gBrowser.removeTab(tab);
 | 
						|
  }
 | 
						|
 | 
						|
  await offerAndDeny("notNow");
 | 
						|
  await offerAndDeny("neverForSite");
 | 
						|
  await offerAndDeny("neverForLanguage");
 | 
						|
  await offerAndDeny("closeButton");
 | 
						|
 | 
						|
  // Test that the close button doesn't record a denied translation if
 | 
						|
  // the infobar is not in its "offer" state.
 | 
						|
  let tab = await translate("<h1>Hallo Welt!</h1>", "de", false);
 | 
						|
  await MetricsChecker.checkAdditions({ deniedOffers: 0 });
 | 
						|
  gBrowser.removeTab(tab);
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_show_original() {
 | 
						|
  let tab =
 | 
						|
    await translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de", false);
 | 
						|
  await MetricsChecker.checkAdditions({ pageCount: 1, showOriginal: 0 });
 | 
						|
  getInfobarElement(tab.linkedBrowser, "showOriginal").doCommand();
 | 
						|
  await MetricsChecker.checkAdditions({ pageCount: 0, showOriginal: 1 });
 | 
						|
  gBrowser.removeTab(tab);
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_language_change() {
 | 
						|
  // This is run 4 times, the total additions are checked afterwards.
 | 
						|
  for (let i of Array(4)) { // eslint-disable-line no-unused-vars
 | 
						|
    let tab = await offerTranslationFor("<h1>Hallo Welt!</h1>", "fr");
 | 
						|
    let browser = tab.linkedBrowser;
 | 
						|
    // In the offer state, translation is executed by the Translate button,
 | 
						|
    // so we expect just a single recoding.
 | 
						|
    let detectedLangMenulist = getInfobarElement(browser, "detectedLanguage");
 | 
						|
    simulateUserSelectInMenulist(detectedLangMenulist, "de");
 | 
						|
    simulateUserSelectInMenulist(detectedLangMenulist, "it");
 | 
						|
    simulateUserSelectInMenulist(detectedLangMenulist, "de");
 | 
						|
    await acceptTranslationOffer(tab);
 | 
						|
 | 
						|
    // In the translated state, a change in the form or to menulists
 | 
						|
    // triggers re-translation right away.
 | 
						|
    let fromLangMenulist = getInfobarElement(browser, "fromLanguage");
 | 
						|
    simulateUserSelectInMenulist(fromLangMenulist, "it");
 | 
						|
    simulateUserSelectInMenulist(fromLangMenulist, "de");
 | 
						|
 | 
						|
    // Selecting the same item shouldn't count.
 | 
						|
    simulateUserSelectInMenulist(fromLangMenulist, "de");
 | 
						|
 | 
						|
    let toLangMenulist = getInfobarElement(browser, "toLanguage");
 | 
						|
    simulateUserSelectInMenulist(toLangMenulist, "fr");
 | 
						|
    simulateUserSelectInMenulist(toLangMenulist, "en");
 | 
						|
    simulateUserSelectInMenulist(toLangMenulist, "it");
 | 
						|
 | 
						|
    // Selecting the same item shouldn't count.
 | 
						|
    simulateUserSelectInMenulist(toLangMenulist, "it");
 | 
						|
 | 
						|
    // Setting the target language to the source language is a no-op,
 | 
						|
    // so it shouldn't count.
 | 
						|
    simulateUserSelectInMenulist(toLangMenulist, "de");
 | 
						|
 | 
						|
    gBrowser.removeTab(tab);
 | 
						|
  }
 | 
						|
  await MetricsChecker.checkAdditions({
 | 
						|
    detectedLanguageChangedBefore: 4,
 | 
						|
    detectedLanguageChangeAfter: 8,
 | 
						|
    targetLanguageChanged: 12,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_never_offer_translation() {
 | 
						|
  Services.prefs.setCharPref("browser.translation.neverForLanguages", "fr");
 | 
						|
 | 
						|
  let tab = await offerTranslationFor("<h1>Hallo Welt!</h1>", "fr");
 | 
						|
 | 
						|
  await MetricsChecker.checkAdditions({
 | 
						|
    autoRejectedOffers: 1,
 | 
						|
  });
 | 
						|
 | 
						|
  gBrowser.removeTab(tab);
 | 
						|
  Services.prefs.clearUserPref("browser.translation.neverForLanguages");
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_translation_preferences() {
 | 
						|
  let preferenceChecks = {
 | 
						|
    "browser.translation.ui.show": [
 | 
						|
      {value: false, expected: {showUI: 0}},
 | 
						|
      {value: true, expected: {showUI: 1}},
 | 
						|
    ],
 | 
						|
    "browser.translation.detectLanguage": [
 | 
						|
      {value: false, expected: {detectLang: 0}},
 | 
						|
      {value: true, expected: {detectLang: 1}},
 | 
						|
    ],
 | 
						|
  };
 | 
						|
 | 
						|
  for (let preference of Object.keys(preferenceChecks)) {
 | 
						|
    for (let check of preferenceChecks[preference]) {
 | 
						|
      MetricsChecker.reset();
 | 
						|
      Services.prefs.setBoolPref(preference, check.value);
 | 
						|
      // Preference metrics are collected once when the provider is initialized.
 | 
						|
      TranslationTelemetry.init();
 | 
						|
      await MetricsChecker.checkAdditions(check.expected);
 | 
						|
    }
 | 
						|
    Services.prefs.clearUserPref(preference);
 | 
						|
  }
 | 
						|
});
 |