Bug 1486507 - Record telemetry for browser language changes r=rpl,flod,chutten

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

--HG--
extra : rebase_source : 0f41affb5790bc14f467c7956ca7eb179ab3f9e5
extra : amend_source : efb27b06212185e373c5711243a79ea793b32291
This commit is contained in:
Mark Striemer 2018-11-10 10:45:23 -06:00
Родитель 6aa1593b81
Коммит b5de939bac
4 изменённых файлов: 238 добавлений и 15 удалений

Просмотреть файл

@ -27,9 +27,15 @@ ChromeUtils.defineModuleGetter(this, "SelectionChangedMenulist",
* the user has opted to search for more languages.
*/
async function installFromUrl(url, hash) {
async function installFromUrl(url, hash, callback) {
let telemetryInfo = {
source: "about:preferences",
};
let install = await AddonManager.getInstallForURL(
url, "application/x-xpinstall", hash);
url, "application/x-xpinstall", hash, null, null, null, null, telemetryInfo);
if (callback) {
callback(install.installId.toString());
}
await install.install();
return install.addon;
}
@ -45,12 +51,13 @@ async function dictionaryIdsForLocale(locale) {
}
class OrderedListBox {
constructor({richlistbox, upButton, downButton, removeButton, onRemove}) {
constructor({richlistbox, upButton, downButton, removeButton, onRemove, onReorder}) {
this.richlistbox = richlistbox;
this.upButton = upButton;
this.downButton = downButton;
this.removeButton = removeButton;
this.onRemove = onRemove;
this.onReorder = onReorder;
this.items = [];
@ -87,6 +94,8 @@ class OrderedListBox {
this.richlistbox.insertBefore(selectedEl, prevEl);
this.richlistbox.ensureElementIsVisible(selectedEl);
this.setButtonState();
this.onReorder();
}
moveDown() {
@ -104,6 +113,8 @@ class OrderedListBox {
this.richlistbox.insertBefore(nextEl, selectedEl);
this.richlistbox.ensureElementIsVisible(selectedEl);
this.setButtonState();
this.onReorder();
}
removeItem() {
@ -305,6 +316,8 @@ function compareItems(a, b) {
}
var gBrowserLanguagesDialog = {
telemetryId: null,
accepted: false,
_availableLocales: null,
_selectedLocales: null,
selectedLocales: null,
@ -314,14 +327,21 @@ var gBrowserLanguagesDialog = {
return Services.prefs.getBoolPref("intl.multilingual.downloadEnabled");
},
recordTelemetry(method, extra = null) {
Services.telemetry.recordEvent(
"intl.ui.browserLanguage", method, "dialog", this.telemetryId, extra);
},
beforeAccept() {
this.selected = this.getSelectedLocales();
this.accepted = true;
return true;
},
async onLoad() {
// Maintain the previously selected locales even if we cancel out.
let {selected, search} = window.arguments[0] || {};
let {telemetryId, selected, search} = window.arguments[0];
this.telemetryId = telemetryId;
this.selectedLocales = selected;
// This is a list of available locales that the user selected. It's more
@ -352,6 +372,7 @@ var gBrowserLanguagesDialog = {
downButton: document.getElementById("down"),
removeButton: document.getElementById("remove"),
onRemove: (item) => this.selectedLocaleRemoved(item),
onReorder: () => this.recordTelemetry("reorder"),
});
this._selectedLocales.setItems(getLocaleDisplayInfo(selectedLocales));
},
@ -365,6 +386,9 @@ var gBrowserLanguagesDialog = {
onChange: (item) => {
this.hideError();
if (item.value == "search") {
// Record the search event here so we don't track the search from
// the main preferences pane twice.
this.recordTelemetry("search");
this.loadLocalesFromAMO();
}
},
@ -450,8 +474,10 @@ var gBrowserLanguagesDialog = {
async availableLanguageSelected(item) {
if (Services.locale.availableLocales.includes(item.value)) {
this.recordTelemetry("add");
this.requestLocalLanguage(item);
} else if (this.availableLangpacks.has(item.value)) {
// Telemetry is tracked in requestRemoteLanguage.
await this.requestRemoteLanguage(item);
} else {
this.showError();
@ -479,7 +505,8 @@ var gBrowserLanguagesDialog = {
let addon;
try {
addon = await installFromUrl(url, hash);
addon = await installFromUrl(
url, hash, (installId) => this.recordTelemetry("add", {installId}));
} catch (e) {
this.showError();
return;
@ -535,6 +562,8 @@ var gBrowserLanguagesDialog = {
},
async selectedLocaleRemoved(item) {
this.recordTelemetry("remove");
this._availableLocales.addItem(item);
// If the item we added is at the top of the list, it needs the label.

Просмотреть файл

@ -756,6 +756,15 @@ var gMainPane = {
},
initBrowserLocale() {
// Enable telemetry.
Services.telemetry.setEventRecordingEnabled("intl.ui.browserLanguage", true);
// This will register the "command" listener.
let menulist = document.getElementById("defaultBrowserLanguage");
new SelectionChangedMenulist(menulist, event => {
gMainPane.onBrowserLanguageChange(event);
});
gMainPane.setBrowserLocales(Services.locale.appLocaleAsBCP47);
},
@ -794,11 +803,6 @@ var gMainPane = {
menupopup.appendChild(fragment);
menulist.value = selected;
// This will register the "command" listener.
new SelectionChangedMenulist(menulist, event => {
gMainPane.onBrowserLanguageChange(event);
});
document.getElementById("browserLanguagesBox").hidden = false;
},
@ -867,6 +871,9 @@ var gMainPane = {
let locales = localesString.split(",");
Services.locale.requestedLocales = locales;
// Record the change in telemetry before we restart.
gMainPane.recordBrowserLanguagesTelemetry("apply");
// Restart with the new locale.
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
@ -887,6 +894,9 @@ var gMainPane = {
return;
}
// Note the change in telemetry.
gMainPane.recordBrowserLanguagesTelemetry("reorder");
let locales = Array.from(new Set([
locale,
...Services.locale.requestedLocales,
@ -1024,8 +1034,17 @@ var gMainPane = {
gSubDialog.open("chrome://browser/content/preferences/languages.xul");
},
recordBrowserLanguagesTelemetry(method, value = null) {
Services.telemetry.recordEvent("intl.ui.browserLanguage", method, "main", value);
},
showBrowserLanguages({search}) {
let opts = {selected: gMainPane.selectedLocales, search};
// Record the telemetry event with an id to associate related actions.
let telemetryId = parseInt(Services.telemetry.msSinceProcessStart(), 10).toString();
let method = search ? "search" : "manage";
gMainPane.recordBrowserLanguagesTelemetry(method, telemetryId);
let opts = {selected: gMainPane.selectedLocales, search, telemetryId};
gSubDialog.open(
"chrome://browser/content/preferences/browserLanguages.xul",
null, opts, this.browserLanguagesClosed);
@ -1033,9 +1052,11 @@ var gMainPane = {
/* Show or hide the confirm change message bar based on the updated ordering. */
browserLanguagesClosed() {
let selected = this.gBrowserLanguagesDialog.selected;
let {accepted, selected} = this.gBrowserLanguagesDialog;
let active = Services.locale.appLocalesAsBCP47;
this.gBrowserLanguagesDialog.recordTelemetry(accepted ? "accept" : "cancel");
// Prepare for changing the locales if they are different than the current locales.
if (selected && selected.join(",") != active.join(",")) {
gMainPane.showConfirmLanguageChangeMessageBar(selected);

Просмотреть файл

@ -9,6 +9,7 @@ AddonTestUtils.initMochitest(this);
const BROWSER_LANGUAGES_URL = "chrome://browser/content/preferences/browserLanguages.xul";
const DICTIONARY_ID_PL = "pl@dictionaries.addons.mozilla.org";
const TELEMETRY_CATEGORY = "intl.ui.browserLanguage";
function langpackId(locale) {
return `langpack-${locale}@firefox.mozilla.org`;
@ -149,6 +150,26 @@ function assertAvailableLocales(list, locales) {
is(items[0].getAttribute("class"), "label-item", "The first row is a label");
}
function getDialogId(dialogDoc) {
return dialogDoc.ownerGlobal.arguments[0].telemetryId;
}
function assertTelemetryRecorded(events) {
let snapshot = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
// Make sure we got some data.
ok(snapshot.parent && snapshot.parent.length > 0, "Got parent telemetry events in the snapshot");
// Only look at the related events after stripping the timestamp and category.
let relatedEvents = snapshot.parent
.filter(([timestamp, category]) => category == TELEMETRY_CATEGORY)
.map(relatedEvent => relatedEvent.slice(2, 6));
// Events are now an array of: method, object[, value[, extra]] as expected.
Assert.deepEqual(relatedEvents, events, "The events are recorded correctly");
}
function selectLocale(localeCode, available, dialogDoc) {
let [locale] = Array.from(available.firstElementChild.children)
.filter(item => item.value == localeCode);
@ -248,8 +269,23 @@ add_task(async function testDisabledBrowserLanguages() {
is(pl.userDisabled, false, "pl is now enabled");
is(pl.version, "2.0", "pl is upgraded to version 2.0");
let dialogId = getDialogId(dialogDoc);
ok(dialogId, "There's a dialogId");
let {installId} = pl.install;
ok(installId, "There's an installId");
await Promise.all(addons.map(addon => addon.uninstall()));
BrowserTestUtils.removeTab(gBrowser.selectedTab);
// FIXME: Also here
assertTelemetryRecorded([
["manage", "main", dialogId],
["search", "dialog", dialogId],
["add", "dialog", dialogId, {installId}],
// Cancel is recorded when the tab is closed.
["cancel", "dialog", dialogId],
]);
});
add_task(async function testReorderingBrowserLanguages() {
@ -277,6 +313,7 @@ add_task(async function testReorderingBrowserLanguages() {
// Open the dialog.
let {dialog, dialogDoc, selected} = await openDialog(doc);
let firstDialogId = getDialogId(dialogDoc);
// The initial order is set by the pref, filtered by available.
assertLocaleOrder(selected, "en-US,pl,he");
@ -298,6 +335,7 @@ add_task(async function testReorderingBrowserLanguages() {
let newDialog = await openDialog(doc);
dialog = newDialog.dialog;
dialogDoc = newDialog.dialogDoc;
let secondDialogId = getDialogId(dialogDoc);
selected = newDialog.selected;
// The initial order comes from the previous settings.
@ -315,9 +353,22 @@ add_task(async function testReorderingBrowserLanguages() {
await dialogClosed;
is(messageBar.hidden, true, "The message bar is hidden again");
await Promise.all(addons.map(addon => addon.uninstall()));
ok(firstDialogId, "There was an id on the first dialog");
ok(secondDialogId, "There was an id on the second dialog");
ok(firstDialogId != secondDialogId, "The dialog ids are different");
ok(firstDialogId < secondDialogId, "The second dialog id is larger than the first");
await Promise.all(addons.map(addon => addon.uninstall()));
BrowserTestUtils.removeTab(gBrowser.selectedTab);
assertTelemetryRecorded([
["manage", "main", firstDialogId],
["reorder", "dialog", firstDialogId],
["accept", "dialog", firstDialogId],
["manage", "main", secondDialogId],
["reorder", "dialog", secondDialogId],
["accept", "dialog", secondDialogId],
]);
});
add_task(async function testAddAndRemoveSelectedLanguages() {
@ -344,6 +395,7 @@ add_task(async function testAddAndRemoveSelectedLanguages() {
// Open the dialog.
let {dialog, dialogDoc, available, selected} = await openDialog(doc);
let dialogId = getDialogId(dialogDoc);
// The initial order is set by the pref.
assertLocaleOrder(selected, "en-US");
@ -382,8 +434,21 @@ add_task(async function testAddAndRemoveSelectedLanguages() {
"The locales are set on the message bar button");
await Promise.all(addons.map(addon => addon.uninstall()));
BrowserTestUtils.removeTab(gBrowser.selectedTab);
assertTelemetryRecorded([
["manage", "main", dialogId],
// Install id is not recorded since it was already installed.
["add", "dialog", dialogId],
["add", "dialog", dialogId],
["remove", "dialog", dialogId],
["remove", "dialog", dialogId],
["add", "dialog", dialogId],
["accept", "dialog", dialogId],
]);
});
add_task(async function testInstallFromAMO() {
@ -414,6 +479,7 @@ add_task(async function testInstallFromAMO() {
// Open the dialog.
let {dialog, dialogDoc, available, selected} = await openDialog(doc, true);
let firstDialogId = getDialogId(dialogDoc);
// Make sure the message bar is still hidden.
is(messageBar.hidden, true, "The message bar is still hidden after searching");
@ -445,6 +511,12 @@ add_task(async function testInstallFromAMO() {
{childList: true},
target => selectedLocales.itemCount == 2);
let langpack = await AddonManager.getAddonByID(langpackId("pl"));
Assert.deepEqual(
langpack.installTelemetryInfo,
{source: "about:preferences"},
"The source is set to preferences");
// Verify the list is correct.
assertLocaleOrder(selected, "pl,en-US");
assertAvailableLocales(available, ["fr", "he"]);
@ -472,10 +544,12 @@ add_task(async function testInstallFromAMO() {
await dialogClosed;
// Disable the Polish langpack.
let langpack = await AddonManager.getAddonByID("langpack-pl@firefox.mozilla.org");
langpack = await AddonManager.getAddonByID("langpack-pl@firefox.mozilla.org");
let installId = langpack.install.installId;
await langpack.disable();
({dialogDoc, available, selected} = await openDialog(doc, true));
let secondDialogId = getDialogId(dialogDoc);
// Wait for the available langpacks to load.
if (available.itemCount == 1) {
@ -493,6 +567,22 @@ add_task(async function testInstallFromAMO() {
await Promise.all(installs.map(item => item.uninstall()));
BrowserTestUtils.removeTab(gBrowser.selectedTab);
ok(installId, "The langpack has an installId");
// FIXME: Most are here
assertTelemetryRecorded([
// First dialog installs a locale and accepts.
["search", "main", firstDialogId],
// It has an installId since it was downloaded.
["add", "dialog", firstDialogId, {installId}],
// It got moved down to avoid errors with finding translations.
["reorder", "dialog", firstDialogId],
["accept", "dialog", firstDialogId],
// The second dialog just checks the state and is closed with the tab.
["search", "main", secondDialogId],
["cancel", "dialog", secondDialogId],
]);
});
let hasSearchOption = popup => Array.from(popup.children).some(el => el.value == "search");
@ -537,3 +627,55 @@ add_task(async function testDownloadDisabled() {
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
add_task(async function testReorderMainPane() {
await SpecialPowers.pushPrefEnv({
set: [
["intl.multilingual.enabled", true],
["intl.multilingual.downloadEnabled", false],
["intl.locale.requested", "en-US"],
["extensions.langpacks.signatures.required", false],
],
});
// Clear the telemetry from other tests.
Services.telemetry.clearEvents();
let langpacks = await createTestLangpacks();
let addons = await Promise.all(langpacks.map(async ([locale, file]) => {
let install = await AddonTestUtils.promiseInstallFile(file);
return install.addon;
}));
await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
let doc = gBrowser.contentDocument;
let messageBar = doc.getElementById("confirmBrowserLanguage");
is(messageBar.hidden, true, "The message bar is hidden at first");
let available = doc.getElementById("defaultBrowserLanguage");
let availableLocales = Array.from(available.firstElementChild.children);
let availableCodes = availableLocales.map(item => item.value).sort().join(",");
is(availableCodes, "en-US,fr,he,pl",
"All of the available locales are listed");
is(available.selectedItem.value, "en-US", "English is selected");
let hebrew = availableLocales[availableLocales.findIndex(item => item.value == "he")];
hebrew.click();
available.firstElementChild.hidePopup();
await BrowserTestUtils.waitForCondition(
() => !messageBar.hidden, "Wait for message bar to show");
is(messageBar.hidden, false, "The message bar is now shown");
is(messageBar.querySelector("button").getAttribute("locales"), "he,en-US",
"The locales are set on the message bar button");
await Promise.all(addons.map(addon => addon.uninstall()));
BrowserTestUtils.removeTab(gBrowser.selectedTab);
assertTelemetryRecorded([
["reorder", "main"],
]);
});

Просмотреть файл

@ -652,3 +652,34 @@ security.ui.identitypopup:
cr: Whether Cookie Restrictions was active while the user interacted with the UI
products:
- firefox
intl.ui.browserLanguage:
action:
description: >
User interactions for the browser language within about-preferences in the main pane and in
the browser language dialog. Each dialog event (on the dialog object, and the manage and
search methods of the main object) has a value which is a monotonically increasing number
that links it with other events related to the same dialog instance.
objects:
- dialog
- main
methods:
- manage
- search
- add
- remove
- reorder
- apply
- accept
- cancel
extra_keys:
installId: The id for an install.
products:
- firefox
expiry_version: "70"
notification_emails:
- flod@mozilla.com
- mstriemer@mozilla.com
release_channel_collection: opt-out
record_in_processes: ["main"]
bug_numbers: [1486507]