diff --git a/browser/base/content/browser-contentblocking.js b/browser/base/content/browser-contentblocking.js index 91d50e4035d4..b0b54b778015 100644 --- a/browser/base/content/browser-contentblocking.js +++ b/browser/base/content/browser-contentblocking.js @@ -8,6 +8,8 @@ var TrackingProtection = { PREF_ENABLED_GLOBALLY: "privacy.trackingprotection.enabled", PREF_ENABLED_IN_PRIVATE_WINDOWS: "privacy.trackingprotection.pbmode.enabled", PREF_UI_ENABLED: "browser.contentblocking.trackingprotection.control-center.ui.enabled", + PREF_TRACKING_TABLE: "urlclassifier.trackingTable", + PREF_TRACKING_ANNOTATION_TABLE: "urlclassifier.trackingAnnotationTable", enabledGlobally: false, enabledInPrivateWindows: false, @@ -17,6 +19,16 @@ var TrackingProtection = { document.getElementById("identity-popup-content-blocking-category-tracking-protection"); }, + get subViewList() { + delete this.subViewList; + return this.subViewList = document.getElementById("identity-popup-trackersView-list"); + }, + + get strictInfo() { + delete this.strictInfo; + return this.strictInfo = document.getElementById("identity-popup-trackersView-strict-info"); + }, + init() { this.updateEnabled(); @@ -24,6 +36,8 @@ var TrackingProtection = { Services.prefs.addObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this); XPCOMUtils.defineLazyPreferenceGetter(this, "visible", this.PREF_UI_ENABLED, false); + XPCOMUtils.defineLazyPreferenceGetter(this, "trackingTable", this.PREF_TRACKING_TABLE, false); + XPCOMUtils.defineLazyPreferenceGetter(this, "trackingAnnotationTable", this.PREF_TRACKING_ANNOTATION_TABLE, false); }, uninit() { @@ -51,6 +65,86 @@ var TrackingProtection = { isBlockerActivated(state) { return state & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT; }, + + isAllowing(state) { + return state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT; + }, + + async updateSubView() { + let previousURI = gBrowser.currentURI.spec; + let previousWindow = gBrowser.selectedBrowser.innerWindowID; + + let contentBlockingLogJSON = await gBrowser.selectedBrowser.getContentBlockingLog(); + let contentBlockingLog = JSON.parse(contentBlockingLogJSON); + + // Don't tell the user to turn on TP if they are already blocking trackers. + this.strictInfo.hidden = this.enabled; + + let fragment = document.createDocumentFragment(); + for (let [origin, actions] of Object.entries(contentBlockingLog)) { + let listItem = await this._createListItem(origin, actions); + if (listItem) { + fragment.appendChild(listItem); + } + } + + // This might have taken a while. Only update the list if we're still on the same page. + if (previousURI == gBrowser.currentURI.spec && + previousWindow == gBrowser.selectedBrowser.innerWindowID) { + this.subViewList.textContent = ""; + this.subViewList.append(fragment); + } + }, + + // Given a URI from a source that was tracking-annotated, figure out + // if it's really on the tracking table or just on the annotation table. + _isOnTrackingTable(uri) { + if (this.trackingTable == this.trackingAnnotationTable) { + return true; + } + return new Promise(resolve => { + classifierService.asyncClassifyLocalWithTables(uri, this.trackingTable, [], [], + (code, list) => resolve(!!list)); + }); + }, + + async _createListItem(origin, actions) { + // Figure out if this list entry was actually detected by TP or something else. + let isDetected = false; + let isAllowed = false; + for (let [state] of actions) { + isAllowed = isAllowed || this.isAllowing(state); + isDetected = isDetected || isAllowed || this.isBlockerActivated(state); + } + + if (!isDetected) { + return null; + } + + let uri = Services.io.newURI(origin); + + // Because we might use different lists for annotation vs. blocking, we + // need to make sure that this is a tracker that we would actually have blocked + // before showing it to the user. + let isTracker = await this._isOnTrackingTable(uri); + if (!isTracker) { + return null; + } + + let listItem = document.createXULElement("hbox"); + listItem.className = "identity-popup-trackersView-list-item"; + listItem.classList.toggle("allowed", isAllowed); + + let image = document.createXULElement("image"); + listItem.append(image); + + let label = document.createXULElement("label"); + label.value = uri.host; + label.setAttribute("crop", "end"); + listItem.append(label); + + return listItem; + }, }; var ThirdPartyCookies = { @@ -147,6 +241,11 @@ var ContentBlocking = { return this.appMenuLabel = document.getElementById("appMenu-tp-label"); }, + get identityPopup() { + delete this.identityPopup; + return this.identityPopup = document.getElementById("identity-popup"); + }, + strings: { get appMenuTitle() { delete this.appMenuTitle; @@ -238,7 +337,7 @@ var ContentBlocking = { }, hideIdentityPopupAndReload() { - document.getElementById("identity-popup").hidePopup(); + this.identityPopup.hidePopup(); BrowserReload(); }, @@ -251,7 +350,7 @@ var ContentBlocking = { }, submitBreakageReport() { - document.getElementById("identity-popup").hidePopup(); + this.identityPopup.hidePopup(); let reportEndpoint = Services.prefs.getStringPref(this.PREF_REPORT_BREAKAGE_URL); if (!reportEndpoint) { @@ -314,6 +413,11 @@ var ContentBlocking = { this.identityPopupMultiView.showSubView("identity-popup-breakageReportView"); }, + async showTrackersSubview() { + await TrackingProtection.updateSubView(); + this.identityPopupMultiView.showSubView("identity-popup-trackersView"); + }, + shieldHistogramAdd(value) { if (PrivateBrowsingUtils.isWindowPrivate(window)) { return; diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 02581a9214f8..1d48c4956667 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -143,6 +143,7 @@ if (AppConstants.NIGHTLY_BUILD) { // lazy service getters XPCOMUtils.defineLazyServiceGetters(this, { + classifierService: ["@mozilla.org/url-classifier/dbservice;1", "nsIURIClassifier"], Favicons: ["@mozilla.org/browser/favicon-service;1", "nsIFaviconService"], gAboutNewTabService: ["@mozilla.org/browser/aboutnewtab-service;1", "nsIAboutNewTabService"], gDNSService: ["@mozilla.org/network/dns-service;1", "nsIDNSService"], diff --git a/browser/base/content/test/trackingUI/browser.ini b/browser/base/content/test/trackingUI/browser.ini index 8c58ff26f4f8..5076c96a3ea2 100644 --- a/browser/base/content/test/trackingUI/browser.ini +++ b/browser/base/content/test/trackingUI/browser.ini @@ -23,3 +23,4 @@ support-files = [browser_trackingUI_state.js] [browser_trackingUI_state_all_disabled.js] [browser_trackingUI_telemetry.js] +[browser_trackingUI_trackers_subview.js] diff --git a/browser/base/content/test/trackingUI/browser_trackingUI_pbmode_exceptions.js b/browser/base/content/test/trackingUI/browser_trackingUI_pbmode_exceptions.js index c462c3ab9255..601eab311516 100644 --- a/browser/base/content/test/trackingUI/browser_trackingUI_pbmode_exceptions.js +++ b/browser/base/content/test/trackingUI/browser_trackingUI_pbmode_exceptions.js @@ -59,9 +59,9 @@ function testTrackingPage(window) { ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible"); ok(!hidden("#identity-popup-content-blocking-category-list"), "category list is visible"); - ok(hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-add-blocking"), - "TP category item is not showing add blocking"); - ok(!hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-state-label"), + ok(hidden("#identity-popup-content-blocking-category-tracking-protection > #identity-popup-content-blocking-tracking-protection-label-allowed"), + "TP category item is not showing the allowed label"); + ok(!hidden("#identity-popup-content-blocking-category-tracking-protection > #identity-popup-content-blocking-tracking-protection-label-blocked"), "TP category item is set to blocked"); } @@ -84,9 +84,9 @@ function testTrackingPageUnblocked() { ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible"); ok(!hidden("#identity-popup-content-blocking-category-list"), "category list is visible"); - ok(hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-add-blocking"), - "TP category item is not showing add blocking"); - ok(hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-state-label"), + ok(!hidden("#identity-popup-content-blocking-category-tracking-protection > #identity-popup-content-blocking-tracking-protection-label-allowed"), + "TP category item is showing the allowed label"); + ok(hidden("#identity-popup-content-blocking-category-tracking-protection > #identity-popup-content-blocking-tracking-protection-label-blocked"), "TP category item is not set to blocked"); } diff --git a/browser/base/content/test/trackingUI/browser_trackingUI_state.js b/browser/base/content/test/trackingUI/browser_trackingUI_state.js index 9a00d88691ea..a9835053c61c 100644 --- a/browser/base/content/test/trackingUI/browser_trackingUI_state.js +++ b/browser/base/content/test/trackingUI/browser_trackingUI_state.js @@ -134,20 +134,26 @@ function testTrackingPage(window) { ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible"); ok(!hidden("#identity-popup-content-blocking-category-list"), "category list is visible"); - let category = Services.prefs.getIntPref(TPC_PREF) == Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER ? - "#identity-popup-content-blocking-category-3rdpartycookies" : - "#identity-popup-content-blocking-category-tracking-protection"; - is(hidden(category + " > .identity-popup-content-blocking-category-add-blocking"), blockedByTP, - "Category item is" + (blockedByTP ? " not" : "") + " showing add blocking"); - is(hidden(category + " > .identity-popup-content-blocking-category-state-label"), !blockedByTP, - "Category item is" + (blockedByTP ? "" : " not") + " set to blocked"); - if (Services.prefs.getIntPref(TPC_PREF) == Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER) { + let cookiesBlocked = Services.prefs.getIntPref(TPC_PREF) == Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER; + if (cookiesBlocked) { + let category = "#identity-popup-content-blocking-category-3rdpartycookies"; + is(hidden(category + " > .identity-popup-content-blocking-category-add-blocking"), blockedByTP, + "Category item is" + (blockedByTP ? " not" : "") + " showing add blocking"); + is(hidden(category + " > .identity-popup-content-blocking-category-state-label"), !blockedByTP, + "Category item is" + (blockedByTP ? "" : " not") + " set to blocked"); + ok(hidden("#identity-popup-content-blocking-category-label-default"), "Not showing default cookie restrictions label."); ok(!hidden("#identity-popup-content-blocking-category-label-trackers"), "Showing trackers cookie restrictions label."); } else { + let category = "#identity-popup-content-blocking-category-tracking-protection"; + is(hidden(category + " > #identity-popup-content-blocking-tracking-protection-label-allowed"), blockedByTP, + "Category item is" + (blockedByTP ? " not" : "") + " showing the allowed label"); + is(!hidden(category + " > #identity-popup-content-blocking-tracking-protection-label-blocked"), blockedByTP, + "Category item is" + (blockedByTP ? "" : " not") + " set to blocked"); + ok(hidden("#identity-popup-content-blocking-category-label-trackers"), "Not showing trackers cookie restrictions label."); ok(!hidden("#identity-popup-content-blocking-category-label-default"), @@ -175,14 +181,22 @@ function testTrackingPageUnblocked(blockedByTP, window) { ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible"); ok(!hidden("#identity-popup-content-blocking-category-list"), "category list is visible"); - let category = Services.prefs.getIntPref(TPC_PREF) == Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER ? - "#identity-popup-content-blocking-category-3rdpartycookies" : - "#identity-popup-content-blocking-category-tracking-protection"; - is(hidden(category + " > .identity-popup-content-blocking-category-add-blocking"), blockedByTP, - "Category item is" + (blockedByTP ? " not" : "") + " showing add blocking"); - // Always hidden no matter if blockedByTP or not, since we have an exception. - ok(hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-state-label"), - "TP category item is not set to blocked"); + + let cookiesBlocked = Services.prefs.getIntPref(TPC_PREF) == Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER; + if (cookiesBlocked) { + let category = "#identity-popup-content-blocking-category-3rdpartycookies"; + is(hidden(category + " > .identity-popup-content-blocking-category-add-blocking"), blockedByTP, + "Category item is" + (blockedByTP ? " not" : "") + " showing add blocking"); + ok(!hidden("#identity-popup-content-blocking-category-tracking-protection > #identity-popup-content-blocking-tracking-protection-label-allowed"), + "TP category item is showing the allowed label"); + } else { + let category = "#identity-popup-content-blocking-category-tracking-protection"; + // If there's an exception we always show the "Allowed" label. + ok(!hidden(category + " > #identity-popup-content-blocking-tracking-protection-label-allowed"), + "Category item is showing the allowed label"); + ok(hidden(category + " > #identity-popup-content-blocking-tracking-protection-label-blocked"), + "Category item is not set to blocked"); + } } async function testContentBlocking(tab) { diff --git a/browser/base/content/test/trackingUI/browser_trackingUI_trackers_subview.js b/browser/base/content/test/trackingUI/browser_trackingUI_trackers_subview.js new file mode 100644 index 000000000000..45e6f19968c7 --- /dev/null +++ b/browser/base/content/test/trackingUI/browser_trackingUI_trackers_subview.js @@ -0,0 +1,118 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html"; + +const TP_PREF = "privacy.trackingprotection.enabled"; + +add_task(async function setup() { + await UrlClassifierTestUtils.addTestTrackers(); +}); + +function openIdentityPopup() { + let mainView = document.getElementById("identity-popup-mainView"); + let viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + gIdentityHandler._identityBox.click(); + return viewShown; +} + +function waitForSecurityChange(blocked) { + return new Promise(resolve => { + let webProgressListener = { + onStateChange: () => {}, + onStatusChange: () => {}, + onLocationChange: () => {}, + onSecurityChange: (webProgress, request, oldState, state) => { + if ((!blocked && state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT) || + (blocked && state & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT)) { + gBrowser.removeProgressListener(webProgressListener); + resolve(); + } + }, + onProgressChange: () => {}, + QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener]), + }; + + gBrowser.addProgressListener(webProgressListener); + }); +} + +async function assertSitesListed(blocked) { + await BrowserTestUtils.withNewTab(TRACKING_PAGE, async function(browser) { + await openIdentityPopup(); + + let categoryItem = + document.getElementById("identity-popup-content-blocking-category-tracking-protection"); + ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible"); + let trackersView = document.getElementById("identity-popup-trackersView"); + let viewShown = BrowserTestUtils.waitForEvent(trackersView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Trackers view was shown"); + + let listItems = document.querySelectorAll(".identity-popup-trackersView-list-item"); + is(listItems.length, 1, "We have 1 tracker in the list"); + + let strictInfo = document.getElementById("identity-popup-trackersView-strict-info"); + is(BrowserTestUtils.is_hidden(strictInfo), Services.prefs.getBoolPref(TP_PREF), + "Strict info is hidden if TP is enabled."); + + let mainView = document.getElementById("identity-popup-mainView"); + viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + let backButton = trackersView.querySelector(".subviewbutton-back"); + backButton.click(); + await viewShown; + + ok(true, "Main view was shown"); + + let change = waitForSecurityChange(blocked); + + await ContentTask.spawn(browser, {}, function() { + content.postMessage("more-tracking", "*"); + }); + + await change; + + viewShown = BrowserTestUtils.waitForEvent(trackersView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Trackers view was shown"); + + listItems = Array.from(document.querySelectorAll(".identity-popup-trackersView-list-item")); + is(listItems.length, 2, "We have 2 trackers in the list"); + + let listItem = listItems.find(item => item.querySelector("label").value == "trackertest.org"); + ok(listItem, "Has an item for trackertest.org"); + ok(BrowserTestUtils.is_visible(listItem), "List item is visible"); + is(listItem.classList.contains("allowed"), !blocked, + "Indicates whether the tracker was blocked or allowed"); + + listItem = listItems.find(item => item.querySelector("label").value == "itisatracker.org"); + ok(listItem, "Has an item for itisatracker.org"); + ok(BrowserTestUtils.is_visible(listItem), "List item is visible"); + is(listItem.classList.contains("allowed"), !blocked, + "Indicates whether the tracker was blocked or allowed"); + }); +} + +add_task(async function testTrackersSubView() { + Services.prefs.setBoolPref(TP_PREF, false); + await assertSitesListed(false); + Services.prefs.setBoolPref(TP_PREF, true); + await assertSitesListed(true); + let uri = Services.io.newURI("https://tracking.example.org"); + Services.perms.add(uri, "trackingprotection", Services.perms.ALLOW_ACTION); + await assertSitesListed(false); + Services.perms.remove(uri, "trackingprotection"); + await assertSitesListed(true); + Services.prefs.clearUserPref(TP_PREF); +}); + +add_task(function cleanup() { + Services.prefs.clearUserPref(TP_PREF); + UrlClassifierTestUtils.cleanupTestTrackers(); +}); diff --git a/browser/base/content/test/trackingUI/trackingAPI.js b/browser/base/content/test/trackingUI/trackingAPI.js index 5e842e708922..ad95c1c71442 100644 --- a/browser/base/content/test/trackingUI/trackingAPI.js +++ b/browser/base/content/test/trackingUI/trackingAPI.js @@ -6,6 +6,12 @@ onmessage = event => { document.body.appendChild(ifr); } break; + case "more-tracking": { + let ifr = document.createElement("iframe"); + ifr.src = "https://itisatracker.org/"; + document.body.appendChild(ifr); + } + break; case "cookie": { let ifr = document.createElement("iframe"); ifr.src = "https://trackertest.org/browser/browser/base/content/test/trackingUI/cookieServer.sjs"; diff --git a/browser/components/controlcenter/content/panel.inc.xul b/browser/components/controlcenter/content/panel.inc.xul index 423d63a62865..9f3156d53f63 100644 --- a/browser/components/controlcenter/content/panel.inc.xul +++ b/browser/components/controlcenter/content/panel.inc.xul @@ -82,15 +82,16 @@ crop="end">&contentBlocking.notDetected; - + - - - + + + @@ -249,6 +250,25 @@ + + + + + + + + + +