Bug 1557058 - use real data for protection report graph r=johannh

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Erica Wright 2019-07-10 14:44:26 +00:00
Родитель 32230bbfe3
Коммит 95d34cd78d
10 изменённых файлов: 470 добавлений и 104 удалений

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

@ -5,16 +5,32 @@
"use strict";
var EXPORTED_SYMBOLS = ["AboutProtectionsHandler"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { RemotePages } = ChromeUtils.import(
"resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm"
);
XPCOMUtils.defineLazyServiceGetter(
this,
"TrackingDBService",
"@mozilla.org/tracking-db-service;1",
"nsITrackingDBService"
);
let idToTextMap = new Map([
[Ci.nsITrackingDBService.TRACKERS_ID, "tracker"],
[Ci.nsITrackingDBService.TRACKING_COOKIES_ID, "cookie"],
[Ci.nsITrackingDBService.CRYPTOMINERS_ID, "cryptominer"],
[Ci.nsITrackingDBService.FINGERPRINTERS_ID, "fingerprinter"],
]);
var AboutProtectionsHandler = {
_inited: false,
_topics: ["openContentBlockingPreferences"],
_topics: ["OpenContentBlockingPreferences", "FetchContentBlockingEvents"],
init() {
this.receiveMessage = this.receiveMessage.bind(this);
this.pageListener = new RemotePages("about:protections");
for (let topic of this._topics) {
this.pageListener.addMessageListener(topic, this.receiveMessage);
@ -35,11 +51,38 @@ var AboutProtectionsHandler = {
receiveMessage(aMessage) {
let win = aMessage.target.browser.ownerGlobal;
switch (aMessage.name) {
case "openContentBlockingPreferences":
case "OpenContentBlockingPreferences":
win.openPreferences("privacy-trackingprotection", {
origin: "about-protections",
});
break;
case "FetchContentBlockingEvents":
TrackingDBService.getEventsByDateRange(
aMessage.data.from,
aMessage.data.to
).then(results => {
let dataToSend = {};
let largest = 0;
for (let result of results) {
let count = result.getResultByName("count");
let type = result.getResultByName("type");
let timestamp = result.getResultByName("timestamp");
dataToSend[timestamp] = dataToSend[timestamp] || { total: 0 };
dataToSend[timestamp][idToTextMap.get(type)] = count;
dataToSend[timestamp].total += count;
// Record the largest amount of tracking events found per day,
// to create the tallest column on the graph and compare other days to.
if (largest < dataToSend[timestamp].total) {
largest = dataToSend[timestamp].total;
}
}
dataToSend.largest = largest;
aMessage.target.sendAsyncMessage(
"SendContentBlockingRecords",
dataToSend
);
});
break;
}
},
};

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

@ -11,8 +11,8 @@
--card-padding: 22px;
--social-color: #AB71FF;
--social-color-darker: #7F27FF;
--crossSite-color: #0090F4;
--crossSite-color-darker: #0073C3;
--cookie-color: #0090F4;
--cookie-color-darker: #0073C3;
--tracker-color: #2AC3A2;
--tracker-color-darker: #229C82;
--fingerprinter-color: #FFBD4F;
@ -32,8 +32,8 @@ body[focuseddatatype=social] {
--tab-highlight: var(--social-color);
}
body[focuseddatatype=crossSite] {
--tab-highlight: var(--crossSite-color);
body[focuseddatatype=cookie] {
--tab-highlight: var(--cookie-color);
}
body[focuseddatatype=tracker] {
@ -172,12 +172,12 @@ body[focuseddatatype=cryptominer] {
background-color: var(--social-color-darker);
}
.crossSite-bar {
background-color: var(--crossSite-color);
.cookie-bar {
background-color: var(--cookie-color);
}
.hover-crossSite .crossSite-bar {
background-color: var(--crossSite-color-darker);
.hover-cookie .cookie-bar {
background-color: var(--cookie-color-darker);
}
.tracker-bar {
@ -232,10 +232,11 @@ label[data-type="social"] {
color: var(--social-color);
}
label[data-type="crossSite"] {
label[data-type="cookie"] {
color: var(--cookie-color);
background-image: url(chrome://browser/skin/controlcenter/3rdpartycookies.svg);
fill: var(--crossSite-color);
color: var(--crossSite-color);
fill: var(--cookie-color);
color: var(--cookie-color);
}
label[data-type="tracker"] {
@ -257,7 +258,7 @@ label[data-type="cryptominer"] {
}
.hover-social label[for="tab-social"],
.hover-crossSite label[for="tab-crossSite"],
.hover-cookie label[for="tab-cookie"],
.hover-tracker label[for="tab-tracker"],
.hover-fingerprinter label[for="tab-fingerprinter"],
.hover-cryptominer label[for="tab-cryptominer"],
@ -290,7 +291,7 @@ label:hover {
}
#tab-social:checked ~ #social,
#tab-crossSite:checked ~ #crossSite,
#tab-cookie:checked ~ #cookie,
#tab-tracker:checked ~ #tracker,
#tab-fingerprinter:checked ~ #fingerprinter,
#tab-cryptominer:checked ~ #cryptominer {

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

@ -43,8 +43,8 @@
<input id="tab-social" data-type="social" type="radio" name="tabs" checked>
<label for="tab-social" data-type="social">345</label>
<input id="tab-crossSite" data-type="crossSite" type="radio" name="tabs">
<label for="tab-crossSite" data-type="crossSite">123</label>
<input id="tab-cookie" data-type="cookie" type="radio" name="tabs">
<label for="tab-cookie" data-type="cookie">123</label>
<input id="tab-tracker" data-type="tracker" type="radio" name="tabs">
<label for="tab-tracker" data-type="tracker">1</label>
@ -59,7 +59,7 @@
<p class="content-title">Social Media Trackers</p>
<p>Social media like, post, and comment buttons on other websites can track you — even if you dont use them. Logging in to sites using your Facebook or Twitter account is another way they can track what you do on those sites. We remove these trackers so Facebook and Twitter see less of what you do online.</p>
</div>
<div id="crossSite" class="tab-content">
<div id="cookie" class="tab-content">
<p class="content-title">Cross-Site Tracking Cookies</p>
<p>Cross-site tracking cookies follow you from site to site to collect data about your browsing habits. Advertisers and analytics companies gather this data to create a profile of your interests across many sites. Blocking them reduces the number of personalized ads that follow you around.</p>
</div>

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

@ -5,114 +5,74 @@
/* eslint-env mozilla/frame-script */
document.addEventListener("DOMContentLoaded", e => {
let todayInMs = Date.now();
let weekAgoInMs = todayInMs - 7 * 24 * 60 * 60 * 1000;
RPMSendAsyncMessage("FetchContentBlockingEvents", {
from: weekAgoInMs,
to: todayInMs,
});
let dataTypes = [
"cryptominer",
"fingerprinter",
"tracker",
"crossSite",
"cookie",
"social",
];
let weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
let today = new Date().getDay();
let protectionDetails = document.getElementById("protection-details");
protectionDetails.addEventListener("click", () => {
RPMSendAsyncMessage("openContentBlockingPreferences");
RPMSendAsyncMessage("OpenContentBlockingPreferences");
});
let data = [
{
total: 41,
cryptominer: 1,
fingerprinter: 10,
tracker: 15,
crossSite: 12,
social: 3,
},
{
total: 246,
cryptominer: 5,
fingerprinter: 8,
tracker: 110,
crossSite: 103,
social: 20,
},
{
total: 59,
cryptominer: 0,
fingerprinter: 1,
tracker: 25,
crossSite: 25,
social: 8,
},
{
total: 177,
cryptominer: 0,
fingerprinter: 4,
tracker: 24,
crossSite: 136,
social: 13,
},
{
total: 16,
cryptominer: 1,
fingerprinter: 3,
tracker: 0,
crossSite: 7,
social: 5,
},
{
total: 232,
cryptominer: 0,
fingerprinter: 30,
tracker: 84,
crossSite: 86,
social: 32,
},
{
total: 153,
cryptominer: 0,
fingerprinter: 10,
tracker: 35,
crossSite: 95,
social: 13,
},
];
// Use this to populate the graph with real data in the future.
let createGraph = () => {
let largest = 10;
for (let day of data) {
if (largest < day.total) {
largest = day.total;
}
let createGraph = data => {
// Set a default top size for the height of the graph bars so that small
// numbers don't fill the whole graph.
let largest = 100;
if (largest < data.largest) {
largest = data.largest;
}
let graph = document.getElementById("graph");
for (let i = 0; i < weekdays.length; i++) {
for (let i = weekdays.length - 1; i >= 0; i--) {
// Start 7 days ago and count down to today.
let date = new Date();
date.setDate(date.getDate() - i);
let dateString = date.toISOString().split("T")[0];
let bar = document.createElement("div");
bar.className = "graph-bar";
let barHeight = (data[i].total / largest) * 100;
bar.style.height = `${barHeight}%`;
for (let type of dataTypes) {
let dataHeight = (data[i][type] / data[i].total) * 100;
let div = document.createElement("div");
div.className = `${type}-bar`;
div.setAttribute("data-type", type);
div.style.height = `${dataHeight}%`;
bar.appendChild(div);
if (data[dateString]) {
let content = data[dateString];
let barHeight = (content.total / largest) * 100;
bar.style.height = `${barHeight}%`;
for (let type of dataTypes) {
if (content[type]) {
let dataHeight = (content[type] / content.total) * 100;
let div = document.createElement("div");
div.className = `${type}-bar`;
div.setAttribute("data-type", type);
div.style.height = `${dataHeight}%`;
bar.appendChild(div);
}
}
} else {
// There were no content blocking events on this day.
bar.style.height = `0`;
}
graph.appendChild(bar);
let label = document.createElement("span");
label.className = "column-label";
if (i == 6) {
label.innerText = "Today";
label.textContent = "Today";
} else {
label.innerText = weekdays[(i + today) % 7];
label.textContent = weekdays[(i + 1 + new Date().getDay()) % 7];
}
graph.appendChild(label);
graph.prepend(label);
}
addListeners();
};
let addListeners = () => {
@ -130,7 +90,7 @@ document.addEventListener("DOMContentLoaded", e => {
});
wrapper.addEventListener("click", ev => {
if (ev.originalTarget.dataset) {
if (ev.originalTarget.dataset.type) {
document.getElementById(`tab-${ev.target.dataset.type}`).click();
}
});
@ -143,6 +103,8 @@ document.addEventListener("DOMContentLoaded", e => {
});
}
};
createGraph();
addListeners();
RPMAddMessageListener("SendContentBlockingRecords", message => {
createGraph(message.data);
});
});

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

@ -4,6 +4,8 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
JAR_MANIFESTS += ['jar.mn']
with Files('**'):

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

@ -0,0 +1,5 @@
[DEFAULT]
support-files =
!/browser/base/content/test/trackingUI/trackingPage.html
[browser_protections_report_ui.js]

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

@ -0,0 +1,251 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Note: This test may cause intermittents if run at exactly midnight.
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
const { Sqlite } = ChromeUtils.import("resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyServiceGetter(
this,
"TrackingDBService",
"@mozilla.org/tracking-db-service;1",
"nsITrackingDBService"
);
XPCOMUtils.defineLazyGetter(this, "DB_PATH", function() {
return OS.Path.join(OS.Constants.Path.profileDir, "protections.sqlite");
});
const SQL = {
insertCustomTimeEvent:
"INSERT INTO events (type, count, timestamp)" +
"VALUES (:type, :count, date(:timestamp));",
selectAll: "SELECT * FROM events",
};
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [["browser.contentblocking.database.enabled", true]],
});
});
add_task(async function test_graph_display() {
// This creates the schema.
await TrackingDBService.saveEvents(JSON.stringify({}));
let db = await Sqlite.openConnection({ path: DB_PATH });
let date = new Date().toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKERS_ID,
count: 1,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.CRYPTOMINERS_ID,
count: 2,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.FINGERPRINTERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 4,
timestamp: date,
});
date = new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKERS_ID,
count: 4,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.CRYPTOMINERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.FINGERPRINTERS_ID,
count: 2,
timestamp: date,
});
date = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKERS_ID,
count: 4,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.CRYPTOMINERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 1,
timestamp: date,
});
date = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.FINGERPRINTERS_ID,
count: 2,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 1,
timestamp: date,
});
date = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.CRYPTOMINERS_ID,
count: 2,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.FINGERPRINTERS_ID,
count: 2,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 1,
timestamp: date,
});
date = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.CRYPTOMINERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.FINGERPRINTERS_ID,
count: 2,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 8,
timestamp: date,
});
let tab = await BrowserTestUtils.openNewForegroundTab({
url: "about:protections",
gBrowser,
});
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
const DATA_TYPES = ["cryptominer", "fingerprinter", "tracker", "cookie"];
let allBars = null;
await ContentTaskUtils.waitForCondition(() => {
allBars = content.document.querySelectorAll(".graph-bar");
return allBars.length;
}, "The graph has been built");
is(allBars.length, 7, "7 bars have been found on the graph");
// today has each type
// yesterday will have no tracking cookies
// 2 days ago will have no fingerprinters
// 3 days ago will have no cryptominers
// 4 days ago will have no trackers
// 5 days ago will have no social (when we add social)
// 6 days ago will be empty
is(
allBars[6].childNodes.length,
DATA_TYPES.length,
"today has all of the data types shown"
);
is(
allBars[6].querySelector(".tracker-bar").style.height,
"10%",
"trackers take 10%"
);
is(
allBars[6].querySelector(".cryptominer-bar").style.height,
"20%",
"cryptominers take 20%"
);
is(
allBars[6].querySelector(".fingerprinter-bar").style.height,
"30%",
"fingerprinters take 30%"
);
is(
allBars[6].querySelector(".cookie-bar").style.height,
"40%",
"cross site tracking cookies take 40%"
);
is(
allBars[5].childNodes.length,
DATA_TYPES.length - 1,
"1 day ago is missing one type"
);
ok(
!allBars[5].querySelector(".cookie-bar"),
"there is no cross site tracking cookie section 1 day ago."
);
is(
allBars[4].childNodes.length,
DATA_TYPES.length - 1,
"2 days ago is missing one type"
);
ok(
!allBars[4].querySelector(".fingerprinter-bar"),
"there is no fingerprinter section 1 day ago."
);
is(
allBars[3].childNodes.length,
DATA_TYPES.length - 1,
"3 days ago is missing one type"
);
ok(
!allBars[3].querySelector(".cryptominer-bar"),
"there is no cryptominer section 1 day ago."
);
is(
allBars[2].childNodes.length,
DATA_TYPES.length - 1,
"4 days ago is missing one type"
);
ok(
!allBars[2].querySelector(".tracker-bar"),
"there is no tracker section 1 day ago."
);
// TODO test for social missing
is(allBars[0].childNodes.length, 0, "6 days ago has no content");
is(allBars[0].style.height, "0px", "6 days ago has no height");
});
// Use the TrackingDBService API to delete the data.
await TrackingDBService.clearAll();
// Make sure the data was deleted.
let rows = await db.execute(SQL.selectAll);
is(rows.length, 0, "length is 0");
await db.close();
BrowserTestUtils.removeTab(tab);
});

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

@ -52,6 +52,10 @@ const SQL = {
deleteEventsRecords: "DELETE FROM events;",
removeRecordsSince: "DELETE FROM events WHERE timestamp >= date(:date);",
selectByDateRange:
"SELECT * FROM events " +
"WHERE timestamp BETWEEN date(:dateFrom) AND date(:dateTo);",
};
/**
@ -242,8 +246,16 @@ TrackingDBService.prototype = {
async clearSince(date) {
let db = await this.ensureDB();
date = new Date(date).toISOString();
await removeRecordsSince(db, date);
},
async getEventsByDateRange(dateFrom, dateTo) {
let db = await this.ensureDB();
dateFrom = new Date(dateFrom).toISOString();
dateTo = new Date(dateTo).toISOString();
return db.execute(SQL.selectByDateRange, { dateFrom, dateTo });
},
};
var EXPORTED_SYMBOLS = ["TrackingDBService"];

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

@ -38,6 +38,13 @@ interface nsITrackingDBService : nsISupports
*/
Promise clearSince(in int64_t since);
/**
* Fetch events from the content blocking database
* @param dateFrom a unix timestamp.
* @param dateTo a unix timestamp.
*/
Promise getEventsByDateRange(in int64_t dateFrom, in int64_t dateTo);
const unsigned long OTHER_COOKIES_BLOCKED_ID = 0;
const unsigned long TRACKERS_ID = 1;
const unsigned long TRACKING_COOKIES_ID = 2;

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

@ -1,3 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Note: This test may cause intermittents if run at exactly midnight.
"use strict";
const { XPCOMUtils } = ChromeUtils.import(
@ -266,3 +272,80 @@ add_task(async function test_timestamp_aggragation() {
await db.close();
Services.prefs.clearUserPref("browser.contentblocking.database.enabled");
});
// This tests that TrackingDBService.getEventsByDateRange can accept two timestamps in unix epoch time
// and return entries that occur within the timestamps, rounded to the nearest day and inclusive.
add_task(async function test_getEventsByDateRange() {
Services.prefs.setBoolPref("browser.contentblocking.database.enabled", true);
// This creates the schema.
await TrackingDBService.saveEvents(JSON.stringify({}));
let db = await Sqlite.openConnection({ path: DB_PATH });
let d = new Date(1521009000000);
let date = d.toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.CRYPTOMINERS_ID,
count: 3,
timestamp: date,
});
date = new Date(d - 2 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKERS_ID,
count: 2,
timestamp: date,
});
date = new Date(d - 3 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 2,
timestamp: date,
});
date = new Date(d - 4 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 2,
timestamp: date,
});
date = new Date(d - 9 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.FINGERPRINTERS_ID,
count: 2,
timestamp: date,
});
let daysBefore1 = new Date(d - 24 * 60 * 60 * 1000);
let daysBefore4 = new Date(d - 4 * 24 * 60 * 60 * 1000);
let daysBefore9 = new Date(d - 9 * 24 * 60 * 60 * 1000);
let events = await TrackingDBService.getEventsByDateRange(daysBefore1, d);
equal(
events.length,
1,
"There is 1 event entry between the date and one day before, inclusive"
);
events = await TrackingDBService.getEventsByDateRange(daysBefore4, d);
equal(
events.length,
4,
"There is 4 event entries between the date and four days before, inclusive"
);
events = await TrackingDBService.getEventsByDateRange(
daysBefore9,
daysBefore4
);
equal(
events.length,
2,
"There is 2 event entries between nine and four days before, inclusive"
);
await TrackingDBService.clearAll();
await db.close();
Services.prefs.clearUserPref("browser.contentblocking.database.enabled");
});