Bug 1557050 - Add basic telemetry to protection report. r=mtigley,johannh

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Erica Wright 2019-08-08 18:53:41 +00:00
Родитель 963ded49f8
Коммит cafcffa6a9
12 изменённых файлов: 450 добавлений и 9 удалений

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

@ -1610,6 +1610,8 @@ pref("browser.contentblocking.report.lockwise.enabled", true);
// Enable Protections report's Monitor card by default.
pref("browser.contentblocking.report.monitor.enabled", true);
pref("browser.contentblocking.report.monitor.url", "https://monitor.firefox.com");
pref("browser.contentblocking.report.lockwise.url", "https://lockwise.firefox.com/");
// Enables the new Protections Panel.
#ifdef NIGHTLY_BUILD

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

@ -78,6 +78,10 @@ var AboutProtectionsHandler = {
for (let topic of this._topics) {
this.pageListener.addMessageListener(topic, this.receiveMessage);
}
Services.telemetry.setEventRecordingEnabled(
"security.ui.protections",
true
);
this._inited = true;
},

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

@ -4,6 +4,11 @@
/* eslint-env mozilla/frame-script */
const LOCKWISE_URL = RPMGetStringPref(
"browser.contentblocking.report.lockwise.url",
""
);
export default class LockwiseCard {
constructor(document) {
this.doc = document;
@ -17,15 +22,27 @@ export default class LockwiseCard {
"open-about-logins-button"
);
openAboutLoginsButton.addEventListener("click", () => {
this.doc.sendTelemetryEvent("click", "lw_open_button");
RPMSendAsyncMessage("OpenAboutLogins");
});
const syncLink = this.doc.querySelector(".synced-devices-text a");
// Register a click handler for the anchor since it's not possible to navigate to about:preferences via href
syncLink.addEventListener("click", () => {
this.doc.sendTelemetryEvent("click", "lw_app_link");
RPMSendAsyncMessage("OpenSyncPreferences");
});
const lockwiseAppLink = this.doc.getElementById("lockwise-inline-link");
lockwiseAppLink.href = LOCKWISE_URL;
lockwiseAppLink.addEventListener("click", () => {
this.doc.sendTelemetryEvent("click", "lw_sync_link");
});
const lockwiseReportLink = this.doc.getElementById("lockwise-how-it-works");
lockwiseReportLink.addEventListener("click", () => {
this.doc.sendTelemetryEvent("click", "lw_about_link");
});
RPMAddMessageListener("SendUserLoginsData", ({ data }) => {
// Once data for the user is retrieved, display the lockwise card.
this.buildContent(data);

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

@ -4,7 +4,10 @@
/* eslint-env mozilla/frame-script */
const MONITOR_SIGN_IN_URL = "https://monitor.firefox.com";
const MONITOR_SIGN_IN_URL = RPMGetStringPref(
"browser.contentblocking.report.monitor.url",
""
);
export default class MonitorClass {
constructor(document) {
@ -17,6 +20,21 @@ export default class MonitorClass {
this.getMonitorData(data);
RPMSendAsyncMessage("FetchMonitorData");
});
let monitorReportLink = this.doc.getElementById("full-report-link");
monitorReportLink.addEventListener("click", () => {
this.doc.sendTelemetryEvent("click", "mtr_report_link");
});
let monitorAboutLink = this.doc.getElementById("monitor-link");
monitorAboutLink.addEventListener("click", () => {
this.doc.sendTelemetryEvent("click", "mtr_about_link");
});
let openLockwise = this.doc.getElementById("lockwise-link");
openLockwise.addEventListener("click", () => {
this.doc.sendTelemetryEvent("click", "lw_open_breach_link");
});
}
/**
@ -58,6 +76,9 @@ export default class MonitorClass {
signUpForMonitorLink.href = this.buildMonitorUrl(monitorData.userEmail);
signUpForMonitorLink.setAttribute("data-l10n-id", "monitor-sign-up");
headerContent.setAttribute("data-l10n-id", "monitor-header-content");
signUpForMonitorLink.addEventListener("click", () => {
this.doc.sendTelemetryEvent("click", "mtr_signup_button");
});
}
}

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

@ -91,7 +91,7 @@
<span>
<!-- Insert Monitor header content here. -->
</span>
<a href="" data-l10n-id="monitor-link"></a>
<a id="monitor-link" href="" data-l10n-id="monitor-link"></a>
</p>
<span class="inline-text-icon monitor-scanned-text" data-l10n-id="auto-scan"></span>
</div>
@ -129,15 +129,15 @@
</span>
<span id="info-exposed-passwords" class="info-text"></span>
</div>
<div class="monitor-view-full-report" data-l10n-id="full-report-link">
<a data-l10n-name="monitor-inline-link" href=""></a>
<div id="full-report-link" class="monitor-view-full-report" data-l10n-id="full-report-link">
<a id="monitor-inline-link" data-l10n-name="monitor-inline-link" href=""></a>
</div>
<div class="monitor-breached-passwords hidden">
<span data-type="breached-lockwise-passwords" class="number-of-breaches block">
<!-- Display number of exposed stored passwords here. -->
</span>
<span id="password-warning">
<a href="" data-l10n-name="lockwise-link"></a>
<a id="lockwise-link" href="" data-l10n-name="lockwise-link"></a>
</span>
</div>
</div>
@ -163,7 +163,7 @@
<div class="no-logins hidden">
<div class="lockwise-mobile-app-icon"></div>
<span data-l10n-id="lockwise-no-logins-content">
<a data-l10n-name="lockwise-inline-link" href=""></a>
<a id="lockwise-inline-link" data-l10n-name="lockwise-inline-link" href=""></a>
</span>
</div>
<div class="has-logins hidden">
@ -172,7 +172,7 @@
</span>
<span id="lockwise-passwords-stored" class="inline-text-icon passwords-stored-text">
<!-- Display message for stored logins here. -->
<a data-l10n-name="lockwise-how-it-works" href=""></a>
<a id="lockwise-how-it-works" data-l10n-name="lockwise-how-it-works" href=""></a>
</span>
<span class="number-of-synced-devices block">
<!-- Display number of synced devices here. -->
@ -181,7 +181,7 @@
<span>
<!-- Display message for status of synced devices here. -->
</span>
<a class="hidden" href="" data-l10n-id="turn-on-sync"></a>
<a id="turn-on-sync" class="hidden" href="" data-l10n-id="turn-on-sync"></a>
</span>
</div>
</div>

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

@ -7,6 +7,11 @@
import LockwiseCard from "./lockwise-card.js";
import MonitorCard from "./monitor-card.js";
// We need to send the close telemetry before unload while we still have a connection to RPM.
window.addEventListener("beforeunload", () => {
document.sendTelemetryEvent("close", "protection_report");
});
document.addEventListener("DOMContentLoaded", e => {
let todayInMs = Date.now();
let weekAgoInMs = todayInMs - 7 * 24 * 60 * 60 * 1000;
@ -53,6 +58,17 @@ document.addEventListener("DOMContentLoaded", e => {
legend.style.gridTemplateAreas =
"'social cookie tracker fingerprinter cryptominer'";
document.sendTelemetryEvent = (action, object) => {
// eslint-disable-next-line no-undef
// eslint-disable-next-line no-undef
RPMRecordTelemetryEvent("security.ui.protections", action, object, "", {
category: cbCategory,
});
};
// Send telemetry on arriving and closing this page
document.sendTelemetryEvent("show", "protection_report");
let createGraph = data => {
// All of our dates are recorded as 00:00 GMT, add 12 hours to the timestamp
// to ensure we display the correct date no matter the user's location.

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

@ -6,3 +6,5 @@ support-files =
[browser_protections_lockwise.js]
[browser_protections_monitor.js]
[browser_protections_report_ui.js]
[browser_protections_telemetry.js]
skip-if = true # see Bug 1572188

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

@ -0,0 +1,285 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
requestLongerTimeout(2);
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.contentblocking.database.enabled", true],
["browser.contentblocking.report.monitor.enabled", true],
["browser.contentblocking.report.lockwise.enabled", true],
["browser.contentblocking.report.proxy.enabled", true],
// Change the endpoints to prevent non-local network connections when landing on the page.
["browser.contentblocking.report.monitor.url", ""],
["browser.contentblocking.report.lockwise.url", ""],
],
});
let oldCanRecord = Services.telemetry.canRecordExtended;
Services.telemetry.canRecordExtended = true;
registerCleanupFunction(() => {
Services.telemetry.canRecordExtended = oldCanRecord;
});
});
add_task(async function checkTelemetryLoadEvents() {
// There's an arbitrary interval of 2 seconds in which the content
// processes sync their event data with the parent process, we wait
// this out to ensure that we clear everything that is left over from
// previous tests and don't receive random events in the middle of our tests.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(c => setTimeout(c, 2000));
// Clear everything.
Services.telemetry.clearEvents();
await TestUtils.waitForCondition(() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
true
).content;
return !events || !events.length;
});
Services.telemetry.setEventRecordingEnabled("security.ui.protections", true);
let tab = await BrowserTestUtils.openNewForegroundTab({
url: "about:protections",
gBrowser,
});
let loadEvents = await TestUtils.waitForCondition(() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
true
).content;
if (events && events.length) {
events = events.filter(
e => e[1] == "security.ui.protections" && e[2] == "show"
);
if (events.length == 1) {
return events;
}
}
return null;
}, "recorded telemetry for showing the report");
is(loadEvents.length, 1, `recorded telemetry for showing the report`);
await reloadTab(tab);
loadEvents = await TestUtils.waitForCondition(() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
true
).content;
if (events && events.length) {
events = events.filter(
e => e[1] == "security.ui.protections" && e[2] == "close"
);
if (events.length == 1) {
return events;
}
}
return null;
}, "recorded telemetry for closing the report");
is(loadEvents.length, 1, `recorded telemetry for closing the report`);
await BrowserTestUtils.removeTab(tab);
});
function waitForTelemetryEventCount(count) {
info("waiting for telemetry event count of " + count);
return TestUtils.waitForCondition(() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
false
).content;
info("got " + (events && events.length) + " events");
if (events && events.length == count) {
return events;
}
return null;
}, "waiting for telemetry event count of: " + count);
}
add_task(async function checkTelemetryClickEvents() {
// There's an arbitrary interval of 2 seconds in which the content
// processes sync their event data with the parent process, we wait
// this out to ensure that we clear everything that is left over from
// previous tests and don't receive random events in the middle of our tests.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(c => setTimeout(c, 2000));
// Clear everything.
Services.telemetry.clearEvents();
await TestUtils.waitForCondition(() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
true
).content;
return !events || !events.length;
});
Services.telemetry.setEventRecordingEnabled("security.ui.protections", true);
let tab = await BrowserTestUtils.openNewForegroundTab({
url: "about:protections",
gBrowser,
});
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
// Show all elements, so we can click on them, even though our user is not logged in.
let hidden_elements = content.document.querySelectorAll(".hidden");
for (let el of hidden_elements) {
el.style.display = "block ";
}
const syncLink = await ContentTaskUtils.waitForCondition(() => {
return content.document.getElementById("turn-on-sync");
}, "syncLink exists");
syncLink.click();
});
let events = await waitForTelemetryEventCount(2);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
e[2] == "click" &&
e[3] == "lw_app_link"
);
is(events.length, 1, `recorded telemetry for lw_app_link`);
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
const openAboutLogins = await ContentTaskUtils.waitForCondition(() => {
return content.document.getElementById("open-about-logins-button");
}, "openAboutLogins exists");
openAboutLogins.click();
});
events = await waitForTelemetryEventCount(3);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
e[2] == "click" &&
e[3] == "lw_open_button"
);
is(events.length, 1, `recorded telemetry for lw_open_button`);
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
const lockwiseAppLink = await ContentTaskUtils.waitForCondition(() => {
return content.document.getElementById("lockwise-inline-link");
}, "lockwiseAppLink exists");
lockwiseAppLink.click();
});
events = await waitForTelemetryEventCount(4);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
e[2] == "click" &&
e[3] == "lw_sync_link"
);
is(events.length, 1, `recorded telemetry for lw_sync_link`);
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
const lockwiseReportLink = await ContentTaskUtils.waitForCondition(() => {
return content.document.getElementById("lockwise-how-it-works");
}, "lockwiseReportLink exists");
lockwiseReportLink.click();
});
events = await waitForTelemetryEventCount(5);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
e[2] == "click" &&
e[3] == "lw_about_link"
);
is(events.length, 1, `recorded telemetry for lw_about_link`);
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
let openLockwise = await ContentTaskUtils.waitForCondition(() => {
return content.document.getElementById("lockwise-link");
}, "openLockwise exists");
openLockwise.click();
});
events = await waitForTelemetryEventCount(6);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
e[2] == "click" &&
e[3] == "lw_open_breach_link"
);
is(events.length, 1, `recorded telemetry for lw_open_breach_link`);
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
let monitorReportLink = await ContentTaskUtils.waitForCondition(() => {
return content.document.getElementById("monitor-inline-link");
}, "monitorReportLink exists");
monitorReportLink.click();
});
events = await waitForTelemetryEventCount(7);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
e[2] == "click" &&
e[3] == "mtr_report_link"
);
is(events.length, 1, `recorded telemetry for mtr_report_link`);
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
let monitorAboutLink = await ContentTaskUtils.waitForCondition(() => {
return content.document.getElementById("monitor-link");
}, "monitorAboutLink exists");
monitorAboutLink.click();
});
events = await waitForTelemetryEventCount(8);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
e[2] == "click" &&
e[3] == "mtr_about_link"
);
is(events.length, 1, `recorded telemetry for mtr_about_link`);
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
const signUpForMonitorLink = await ContentTaskUtils.waitForCondition(() => {
return content.document.getElementById("sign-up-for-monitor-link");
}, "signUpForMonitorLink exists");
signUpForMonitorLink.click();
});
events = await waitForTelemetryEventCount(9);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
e[2] == "click" &&
e[3] == "mtr_signup_button"
);
is(events.length, 1, `recorded telemetry for mtr_signup_button`);
await BrowserTestUtils.removeTab(tab);
// We open two extra tabs with the click events.
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

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

@ -62,7 +62,12 @@ let RPMAccessManager = {
"browser.contentblocking.report.lockwise.enabled",
"browser.contentblocking.report.monitor.enabled",
],
getStringPref: ["browser.contentblocking.category"],
getStringPref: [
"browser.contentblocking.category",
"browser.contentblocking.report.lockwise.url",
"browser.contentblocking.report.monitor.url",
],
recordTelemetryEvent: ["yes"],
},
"about:newinstall": {
getUpdateChannel: ["yes"],
@ -479,4 +484,26 @@ class MessagePort {
return this.sendRequest("FxAccountsEndpoint", aEntrypoint);
}
recordTelemetryEvent(category, event, object, value, extra) {
let principal = this.window.document.nodePrincipal;
if (
!RPMAccessManager.checkAllowAccess(
principal,
"recordTelemetryEvent",
"yes"
)
) {
throw new Error(
"RPMAccessManager does not allow access to recordTelemetryEvent"
);
}
return Services.telemetry.recordEvent(
category,
event,
object,
value,
extra
);
}
}

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

@ -59,6 +59,9 @@ class ChildMessagePort extends MessagePort {
Cu.exportFunction(this.getFxAccountsEndpoint.bind(this), window, {
defineAs: "RPMGetFxAccountsEndpoint",
});
Cu.exportFunction(this.recordTelemetryEvent.bind(this), window, {
defineAs: "RPMRecordTelemetryEvent",
});
// Send a message for load events
let loadListener = () => {

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

@ -1494,6 +1494,69 @@ security.ui.certerror:
has_sts: If the error page is for a site with HSTS headers or with a pinned key.
panel_open: If the advanced panel was open at the time of the interaction.
security.ui.protections:
show:
objects: [
"protection_report",
]
bug_numbers:
- 1557050
description: >
User arrived on the protection report.
expiry_version: "75"
record_in_processes: ["content"]
release_channel_collection: opt-out
notification_emails:
- chsiang@mozilla.com
- seceng-telemetry@mozilla.com
products:
- firefox
extra_keys:
category: The category of protections the user is in, standard, strict or custom.
close:
objects: [
"protection_report",
]
bug_numbers:
- 1557050
description: >
User closed on the protection report.
expiry_version: "75"
record_in_processes: ["content"]
release_channel_collection: opt-out
notification_emails:
- chsiang@mozilla.com
- seceng-telemetry@mozilla.com
products:
- firefox
extra_keys:
category: The category of protections the user is in, standard, strict or custom.
click:
bug_numbers:
- 1557050
description: >
User interaction by click events on the protection report.
objects: [
"lw_app_link",
"lw_open_button",
"lw_sync_link",
"lw_about_link",
"lw_open_breach_link",
"mtr_report_link",
"mtr_about_link",
"mtr_signup_button",
]
expiry_version: "75"
record_in_processes: ["content"]
release_channel_collection: opt-out
notification_emails:
- chsiang@mozilla.com
- seceng-telemetry@mozilla.com
products:
- firefox
extra_keys:
category: The category of protections the user is in, standard, strict or custom.
security.ui.identitypopup:
open:
objects: ["identity_popup"]

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

@ -34,6 +34,7 @@ module.exports = {
RPMIsWindowPrivate: false,
RPMSendAsyncMessage: false,
RPMAddMessageListener: false,
RPMRecordTelemetryEvent: false,
RPMRemoveMessageListener: false,
},
};