diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /browser/components/translation/test/browser_translation_telemetry.js | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/translation/test/browser_translation_telemetry.js')
-rw-r--r-- | browser/components/translation/test/browser_translation_telemetry.js | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/browser/components/translation/test/browser_translation_telemetry.js b/browser/components/translation/test/browser_translation_telemetry.js new file mode 100644 index 0000000000..768daea7c1 --- /dev/null +++ b/browser/components/translation/test/browser_translation_telemetry.js @@ -0,0 +1,332 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { Translation, TranslationTelemetry } = ChromeUtils.import( + "resource:///modules/translation/TranslationParent.jsm" +); +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 actor = browser.browsingContext.currentWindowGlobal.getActor( + "Translation" + ); + let notif = actor.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); + let actor = browser.browsingContext.currentWindowGlobal.getActor( + "Translation" + ); + + // Send a translation offer. + actor.documentStateReceived({ + state: Translation.STATE_OFFER, + originalShown: true, + detectedLanguage: from, + }); + + return tab; +}; + +var acceptTranslationOffer = async function(tab) { + let browser = tab.linkedBrowser; + let translationPromise = waitForTranslationDone(); + + getInfobarElement(browser, "translate").doCommand(); + await translationPromise; +}; + +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 waitForTranslationDone() { + return new Promise(resolve => { + Translation.setListenerForTests(() => { + Translation.setListenerForTests(null); + resolve(); + }); + }); +} + +function simulateUserSelectInMenulist(menulist, value) { + menulist.value = value; + menulist.doCommand(); +} + +add_setup(async function() { + 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); + } +}); |