fune/browser/components/translation/test/browser_translation_telemetry.js
Victor Porof ad522e3aae Bug 1561435 - Fix linting errors for browser/, r=standard8
# ignore-this-changeset

Differential Revision: https://phabricator.services.mozilla.com/D35950

--HG--
extra : source : ff6aa88097df9836d93d6aa5554ffcd160f07167
extra : intermediate-source : 2130a9484ece03d835939359c4a07966aa8d790c
2019-06-28 20:02:37 +02:00

326 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.
// eslint-disable-next-line no-unused-vars
for (let i of Array(4)) {
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);
}
});