Bug 1557058 - use real data for protection report graph r=nhnt11,johannh
☠☠ backed out by 90203ccec5a6 ☠ ☠
authorErica Wright <ewright@mozilla.com>
Thu, 04 Jul 2019 20:57:16 +0000
changeset 544187 cbb9a325576ed78a813d8d535c6f8f8504aeccaa
parent 544186 e9a1abfdd5b6759ee046f9c98f20a772f57aecc6
child 544188 dda10c593af043a0b1db62a3fd9ea22f66654d0c
push id2131
push userffxbld-merge
push dateMon, 26 Aug 2019 18:30:20 +0000
treeherdermozilla-release@b19ffb3ca153 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnhnt11, johannh
bugs1557058
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1557058 - use real data for protection report graph r=nhnt11,johannh Differential Revision: https://phabricator.services.mozilla.com/D36249
browser/components/about/AboutProtectionsHandler.jsm
browser/components/protections/content/protections.css
browser/components/protections/content/protections.html
browser/components/protections/content/protections.js
browser/components/protections/moz.build
browser/components/protections/test/browser/browser.ini
browser/components/protections/test/browser/browser_protections_ui.js
toolkit/components/antitracking/TrackingDBService.jsm
toolkit/components/antitracking/nsITrackingDBService.idl
toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js
--- a/browser/components/about/AboutProtectionsHandler.jsm
+++ b/browser/components/about/AboutProtectionsHandler.jsm
@@ -1,25 +1,37 @@
 /* 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/. */
 
 "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");
 
-const {RemotePages} = ChromeUtils.import("resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm");
+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",
+    "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);
     }
     this._inited = true;
   },
 
   uninit() {
@@ -30,14 +42,35 @@ var AboutProtectionsHandler = {
       this.pageListener.removeMessageListener(topic, this.receiveMessage);
     }
     this.pageListener.destroy();
   },
 
   receiveMessage(aMessage) {
     let win = aMessage.target.browser.ownerGlobal;
     switch (aMessage.name) {
-      case "openContentBlockingPreferences":
-        win.openPreferences("privacy-trackingprotection", {origin: "about-protections"});
-        break;
+    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;
+        this.pageListener.sendAsyncMessage("SendContentBlockingRecords", dataToSend);
+      });
+      break;
     }
   },
 };
--- a/browser/components/protections/content/protections.css
+++ b/browser/components/protections/content/protections.css
@@ -6,18 +6,18 @@
   --card-background:  #FFF;
   --clickable-text-hover: hsla(0,0%,70%,.2);
   --clickable-text-active: hsla(0,0%,70%,.3);
   --card-divider: rgba(12,12,13,0.1) 1px solid;
   --report-background: #FAFAFC;
   --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;
   --fingerprinter-color-darker: #ffA40C;
   --cryptominer-color: #AFAFBB;
   --cryptominer-color-darker: #88889A;
   --tab-highlight: var(--social-color); /* start with social selected */
 }
@@ -27,18 +27,18 @@ body {
   font: message-box;
   margin-top: 82px;
 }
 
 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] {
   --tab-highlight: var(--tracker-color);
 }
 
 body[focuseddatatype=fingerprinter] {
   --tab-highlight: var(--fingerprinter-color);
@@ -167,22 +167,22 @@ body[focuseddatatype=cryptominer] {
 .social-bar {
   background-color: var(--social-color);
 }
 
 .hover-social .social-bar {
   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 {
   background-color: var(--tracker-color);
 }
 
 .hover-tracker .tracker-bar {
   background-color: var(--tracker-color-darker);
@@ -223,34 +223,34 @@ label {
   text-align: center;
   border: 1px solid transparent;
 }
 
 label[data-type="social"] {
   color: var(--social-color);
 }
 
-label[data-type="crossSite"] {
-  color: var(--crossSite-color);
+label[data-type="cookie"] {
+  color: var(--cookie-color);
 }
 
 label[data-type="tracker"] {
   color: var(--tracker-color);
 }
 
 label[data-type="fingerprinter"] {
   color: var(--fingerprinter-color);
 }
 
 label[data-type="cryptominer"] {
   color: var(--cryptominer-color);
 }
 
 .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"],
 label:hover {
   background-color: var(--clickable-text-hover);
   cursor: pointer;
 }
 
@@ -273,14 +273,14 @@ input:checked + label {
   font-weight: bold;
 }
 
 .tab-content p {
   margin: 0;
 }
 
 #tab-social:checked ~ #social,
-#tab-crossSite:checked ~ #crossSite,
+#tab-cookie:checked ~ #cookie,
 #tab-tracker:checked ~ #tracker,
 #tab-fingerprinter:checked ~ #fingerprinter,
 #tab-cryptominer:checked ~ #cryptominer {
   display: block;
 }
--- a/browser/components/protections/content/protections.html
+++ b/browser/components/protections/content/protections.html
@@ -38,33 +38,33 @@
               Firefox blocked 970 trackers over the past week
             </p>
             <div id="graph-wrapper">
               <div id="graph"></div>
               <div id="legend">
                 <input id="tab-social" data-type="social" type="radio" name="tabs" checked>
                 <label for="tab-social" data-type="social">Social</label>
 
-                <input id="tab-crossSite" data-type="crossSite" type="radio" name="tabs">
-                <label for="tab-crossSite" data-type="crossSite">cross-site-tracker</label>
+                <input id="tab-cookie" data-type="cookie" type="radio" name="tabs">
+                <label for="tab-cookie" data-type="cookie">cross-site-tracker</label>
 
                 <input id="tab-tracker" data-type="tracker" type="radio" name="tabs">
                 <label for="tab-tracker" data-type="tracker">ad-tracker</label>
 
                 <input id="tab-fingerprinter" data-type="fingerprinter" type="radio" name="tabs">
                 <label for="tab-fingerprinter" data-type="fingerprinter">fingerprinter</label>
 
                 <input id="tab-cryptominer" data-type="cryptominer" type="radio" name="tabs">
                 <label for="tab-cryptominer" data-type="cryptominer">cryptominer</label>
 
                 <div id="social" class="tab-content">
                   <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 don’t 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>
                 <div id="tracker" class="tab-content">
                   <p class="content-title">Tracking Content</p>
                   <p>Websites may load outside ads, videos, and other content that contain hidden trackers. Blocking tracking content can make websites load faster, but some buttons, forms, and login fields might not work.</p>
                 </div>
                 <div id="fingerprinter" class="tab-content">
--- a/browser/components/protections/content/protections.js
+++ b/browser/components/protections/content/protections.js
@@ -1,68 +1,74 @@
 /* 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/. */
 
 /* eslint-env mozilla/frame-script */
 
 document.addEventListener("DOMContentLoaded", (e) => {
-  let dataTypes = ["cryptominer", "fingerprinter", "tracker", "crossSite", "social"];
+  let todayInMs = Date.now();
+  let weekAgoInMs = todayInMs - (7 * 24 * 60 * 60 * 1000);
+  RPMSendAsyncMessage("FetchContentBlockingEvents", {from: weekAgoInMs, to: todayInMs});
+
+  let dataTypes = ["cryptominer", "fingerprinter", "tracker", "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 = () => {
     let wrapper = document.querySelector(".body-wrapper");
     wrapper.addEventListener("mouseover", (ev) => {
       if (ev.originalTarget.dataset) {
         wrapper.classList.add("hover-" + ev.originalTarget.dataset.type);
       }
@@ -70,24 +76,26 @@ document.addEventListener("DOMContentLoa
 
     wrapper.addEventListener("mouseout", (ev) => {
       if (ev.originalTarget.dataset) {
         wrapper.classList.remove("hover-" + ev.originalTarget.dataset.type);
       }
     });
 
     wrapper.addEventListener("click", (ev) => {
-      if (ev.originalTarget.dataset) {
+      if (ev.originalTarget.dataset.type) {
         document.getElementById(`tab-${ev.target.dataset.type}`).click();
       }
     });
 
     // Change the class on the body to change the color variable.
     let radios = document.querySelectorAll("#legend input");
     for (let radio of radios) {
       radio.addEventListener("change", (ev) => {
         document.body.setAttribute("focuseddatatype", ev.target.dataset.type);
       });
     }
   };
-  createGraph();
-  addListeners();
+
+  RPMAddMessageListener("SendContentBlockingRecords", (message) => {
+    createGraph(message.data);
+  });
 });
--- a/browser/components/protections/moz.build
+++ b/browser/components/protections/moz.build
@@ -1,10 +1,12 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+
 JAR_MANIFESTS += ['jar.mn']
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Tracking Protection')
new file mode 100644
--- /dev/null
+++ b/browser/components/protections/test/browser/browser.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+  !/browser/base/content/test/trackingUI/trackingPage.html
+
+[browser_protections_ui.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/protections/test/browser/browser_protections_ui.js
@@ -0,0 +1,113 @@
+/* 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, {}, function() {
+    const DATA_TYPES = ["cryptominer", "fingerprinter", "tracker", "cookie"];
+    let allBars = content.document.querySelectorAll(".graph-bar");
+    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), it is the tallest of the bars at 100%
+    // 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);
+});
--- a/toolkit/components/antitracking/TrackingDBService.jsm
+++ b/toolkit/components/antitracking/TrackingDBService.jsm
@@ -44,16 +44,20 @@ const SQL = {
     "WHERE type = :type " +
     "AND timestamp = date(:date);",
 
   deleteEventsRecords:
     "DELETE FROM events;",
 
   removeRecordsSince:
     "DELETE FROM events WHERE timestamp >= date(:date);",
+
+  selectByDateRange:
+    "SELECT * FROM events " +
+    "WHERE timestamp BETWEEN date(:dateFrom) AND date(:dateTo);",
 };
 
 /**
  * Creates the database schema.
  */
 async function createDatabase(db) {
   await db.execute(SQL.createEvents);
 }
@@ -220,13 +224,21 @@ TrackingDBService.prototype = {
 
   async clearAll() {
     let db = await this.ensureDB();
     await removeAllRecords(db);
   },
 
   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"];
--- a/toolkit/components/antitracking/nsITrackingDBService.idl
+++ b/toolkit/components/antitracking/nsITrackingDBService.idl
@@ -33,14 +33,21 @@ interface nsITrackingDBService : nsISupp
 
   /**
    * Clear all content blocking database entries added since the specified time.
    * @param since   a unix timestamp representing the number of milliseconds from
    *                Jan 1, 1970 00:00:00 UTC.
    */
   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;
   const unsigned long CRYPTOMINERS_ID = 3;
   const unsigned long FINGERPRINTERS_ID = 4;
 };
--- a/toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js
+++ b/toolkit/components/antitracking/test/xpcshell/test_tracking_db_service.js
@@ -1,8 +1,14 @@
+/* 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("resource://gre/modules/XPCOMUtils.jsm");
 const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 const {Sqlite} = ChromeUtils.import("resource://gre/modules/Sqlite.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "TrackingDBService",
                                   "@mozilla.org/tracking-db-service;1",
@@ -173,8 +179,50 @@ add_task(async function test_timestamp_a
   // Use the TrackingDBService API to delete the data.
   await TrackingDBService.clearAll();
   // Make sure the data was deleted.
   rows = await db.execute(SQL.selectAll);
   equal(rows.length, 0, "length is 0");
   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");
+});