summaryrefslogtreecommitdiffstats
path: root/browser/components/protections/content/protections.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/protections/content/protections.mjs493
1 files changed, 493 insertions, 0 deletions
diff --git a/browser/components/protections/content/protections.mjs b/browser/components/protections/content/protections.mjs
new file mode 100644
index 0000000000..5f4b8dc8e9
--- /dev/null
+++ b/browser/components/protections/content/protections.mjs
@@ -0,0 +1,493 @@
+/* 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/remote-page */
+
+import LockwiseCard from "./lockwise-card.mjs";
+import MonitorCard from "./monitor-card.mjs";
+import ProxyCard from "./proxy-card.mjs";
+import VPNCard from "./vpn-card.mjs";
+
+let cbCategory = RPMGetStringPref("browser.contentblocking.category");
+document.sendTelemetryEvent = (action, object, value = "") => {
+ // eslint-disable-next-line no-undef
+ RPMRecordTelemetryEvent("security.ui.protections", action, object, value, {
+ category: cbCategory,
+ });
+};
+
+let { protocol, pathname, searchParams } = new URL(document.location);
+
+let searchParamsChanged = false;
+if (searchParams.has("entrypoint")) {
+ RPMSendAsyncMessage("RecordEntryPoint", {
+ entrypoint: searchParams.get("entrypoint"),
+ });
+ // Remove this parameter from the URL (after recording above) to make it
+ // cleaner for bookmarking and switch-to-tab and so that bookmarked values
+ // don't skew telemetry.
+ searchParams.delete("entrypoint");
+ searchParamsChanged = true;
+}
+
+document.addEventListener("DOMContentLoaded", e => {
+ if (searchParamsChanged) {
+ let newURL = protocol + pathname;
+ let params = searchParams.toString();
+ if (params) {
+ newURL += "?" + params;
+ }
+ window.location.replace(newURL);
+ return;
+ }
+
+ RPMSendQuery("FetchEntryPoint", {}).then(entrypoint => {
+ // Send telemetry on arriving on this page
+ document.sendTelemetryEvent("show", "protection_report", entrypoint);
+ });
+
+ // 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");
+ });
+
+ let todayInMs = Date.now();
+ let weekAgoInMs = todayInMs - 6 * 24 * 60 * 60 * 1000;
+
+ let dataTypes = [
+ "cryptominer",
+ "fingerprinter",
+ "tracker",
+ "cookie",
+ "social",
+ ];
+
+ let manageProtectionsLink = document.getElementById("protection-settings");
+ let manageProtections = document.getElementById("manage-protections");
+ let protectionSettingsEvtHandler = evt => {
+ if (evt.keyCode == evt.DOM_VK_RETURN || evt.type == "click") {
+ RPMSendAsyncMessage("OpenContentBlockingPreferences");
+ if (evt.target.id == "protection-settings") {
+ document.sendTelemetryEvent(
+ "click",
+ "settings_link",
+ "header-settings"
+ );
+ } else if (evt.target.id == "manage-protections") {
+ document.sendTelemetryEvent(
+ "click",
+ "settings_link",
+ "custom-card-settings"
+ );
+ }
+ }
+ };
+ manageProtectionsLink.addEventListener("click", protectionSettingsEvtHandler);
+ manageProtectionsLink.addEventListener(
+ "keypress",
+ protectionSettingsEvtHandler
+ );
+ manageProtections.addEventListener("click", protectionSettingsEvtHandler);
+ manageProtections.addEventListener("keypress", protectionSettingsEvtHandler);
+
+ let legend = document.getElementById("legend");
+ legend.style.gridTemplateAreas =
+ "'social cookie tracker fingerprinter cryptominer'";
+
+ let createGraph = data => {
+ let graph = document.getElementById("graph");
+ let summary = document.getElementById("graph-total-summary");
+ let weekSummary = document.getElementById("graph-week-summary");
+
+ // User is in private mode, show no data on the graph
+ if (data.isPrivate) {
+ graph.classList.add("private-window");
+ } else {
+ let earliestDate = data.earliestDate || Date.now();
+ summary.setAttribute(
+ "data-l10n-args",
+ JSON.stringify({ count: data.sumEvents, earliestDate })
+ );
+ summary.setAttribute("data-l10n-id", "graph-total-tracker-summary");
+ }
+
+ // 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 weekCount = 0;
+ let weekTypeCounts = {
+ social: 0,
+ cookie: 0,
+ tracker: 0,
+ fingerprinter: 0,
+ cryptominer: 0,
+ };
+
+ // For accessibility clients, we turn the graph into a fake table with annotated text.
+ // We use WAI-ARIA roles, properties, and states to mark up the table, rows and cells.
+ // Each day becomes one row in the table.
+ // Each row contains the day, total, and then one cell for each bar that we display.
+ // At most, a row can contain seven cells.
+ // But we need to caclulate the actual number of the most cells in a row to give accurate information.
+ let maxColumnCount = 0;
+ let date = new Date();
+ for (let i = 0; i <= 6; i++) {
+ let dateString = date.toISOString().split("T")[0];
+ let ariaOwnsString = ""; // Get the row's colummns in order
+ let currentColumnCount = 0;
+ let bar = document.createElement("div");
+ bar.className = "graph-bar";
+ bar.setAttribute("role", "row");
+ let innerBar = document.createElement("div");
+ innerBar.className = "graph-wrapper-bar";
+ if (data[dateString]) {
+ let content = data[dateString];
+ let count = document.createElement("div");
+ count.className = "bar-count";
+ count.id = "count" + i;
+ count.setAttribute("role", "cell");
+ count.textContent = content.total;
+ setTimeout(() => {
+ count.classList.add("animate");
+ }, 400);
+ bar.appendChild(count);
+ ariaOwnsString = count.id;
+ currentColumnCount += 1;
+ let barHeight = (content.total / largest) * 100;
+ weekCount += content.total;
+ // Add a short timeout to allow the elements to be added to the dom before triggering an animation.
+ setTimeout(() => {
+ bar.style.height = `${barHeight}%`;
+ }, 20);
+ for (let type of dataTypes) {
+ if (content[type]) {
+ let dataHeight = (content[type] / content.total) * 100;
+ // Since we are dealing with non-visual content, screen readers need a parent container to get the text
+ let cellSpan = document.createElement("span");
+ cellSpan.id = type + i;
+ cellSpan.setAttribute("role", "cell");
+ let div = document.createElement("div");
+ div.className = `${type}-bar inner-bar`;
+ div.setAttribute("role", "img");
+ div.setAttribute("data-type", type);
+ div.style.height = `${dataHeight}%`;
+ div.setAttribute(
+ "data-l10n-args",
+ JSON.stringify({ count: content[type], percentage: dataHeight })
+ );
+ div.setAttribute("data-l10n-id", `bar-tooltip-${type}`);
+ weekTypeCounts[type] += content[type];
+ cellSpan.appendChild(div);
+ innerBar.appendChild(cellSpan);
+ ariaOwnsString = ariaOwnsString + " " + cellSpan.id;
+ currentColumnCount += 1;
+ }
+ }
+ if (currentColumnCount > maxColumnCount) {
+ // The current row has more than any previous rows
+ maxColumnCount = currentColumnCount;
+ }
+ } else {
+ // There were no content blocking events on this day.
+ bar.classList.add("empty");
+ }
+ bar.appendChild(innerBar);
+ graph.prepend(bar);
+
+ if (data.isPrivate) {
+ weekSummary.setAttribute(
+ "data-l10n-id",
+ "graph-week-summary-private-window"
+ );
+ } else {
+ weekSummary.setAttribute(
+ "data-l10n-args",
+ JSON.stringify({ count: weekCount })
+ );
+ weekSummary.setAttribute("data-l10n-id", "graph-week-summary");
+ }
+
+ let label = document.createElement("span");
+ label.className = "column-label";
+ // While the graphs fill up from the right, the days fill up from the left, so match the IDs
+ label.id = "day" + (6 - i);
+ label.setAttribute("role", "rowheader");
+ if (i == 6) {
+ label.setAttribute("data-l10n-id", "graph-today");
+ } else {
+ label.textContent = data.weekdays[(i + 1 + new Date().getDay()) % 7];
+ }
+ graph.append(label);
+ // Make the day the first column in a row, making it the row header.
+ bar.setAttribute("aria-owns", "day" + i + " " + ariaOwnsString);
+ date.setDate(date.getDate() - 1);
+ }
+ maxColumnCount += 1; // Add the day column in the fake table
+ graph.setAttribute("aria-colCount", maxColumnCount);
+ // Set the total number of each type of tracker on the tabs as well as their
+ // "Learn More" links
+ for (let type of dataTypes) {
+ document.querySelector(`label[data-type=${type}] span`).textContent =
+ weekTypeCounts[type];
+ const learnMoreLink = document.getElementById(`${type}-link`);
+ learnMoreLink.href = RPMGetFormatURLPref(
+ `browser.contentblocking.report.${type}.url`
+ );
+ learnMoreLink.addEventListener("click", () => {
+ document.sendTelemetryEvent("click", "trackers_about_link", type);
+ });
+ }
+
+ let blockingCookies =
+ RPMGetIntPref("network.cookie.cookieBehavior", 0) != 0;
+ let cryptominingEnabled = RPMGetBoolPref(
+ "privacy.trackingprotection.cryptomining.enabled",
+ false
+ );
+ let fingerprintingEnabled = RPMGetBoolPref(
+ "privacy.trackingprotection.fingerprinting.enabled",
+ false
+ );
+ let tpEnabled = RPMGetBoolPref("privacy.trackingprotection.enabled", false);
+ let socialTracking = RPMGetBoolPref(
+ "privacy.trackingprotection.socialtracking.enabled",
+ false
+ );
+ let socialCookies = RPMGetBoolPref(
+ "privacy.socialtracking.block_cookies.enabled",
+ false
+ );
+ let socialEnabled =
+ socialCookies && (blockingCookies || (tpEnabled && socialTracking));
+ let notBlocking =
+ !blockingCookies &&
+ !cryptominingEnabled &&
+ !fingerprintingEnabled &&
+ !tpEnabled &&
+ !socialEnabled;
+
+ // User has turned off all blocking, show a different card.
+ if (notBlocking) {
+ document
+ .getElementById("etp-card-content")
+ .setAttribute(
+ "data-l10n-id",
+ "protection-report-etp-card-content-custom-not-blocking"
+ );
+ document
+ .querySelector(".etp-card .card-title")
+ .setAttribute("data-l10n-id", "etp-card-title-custom-not-blocking");
+ document
+ .getElementById("report-summary")
+ .setAttribute("data-l10n-id", "protection-report-page-summary");
+ document.querySelector(".etp-card").classList.add("custom-not-blocking");
+
+ // Hide the link to settings from the header, so we are not showing two links.
+ manageProtectionsLink.style.display = "none";
+ } else {
+ // Hide each type of tab if blocking of that type is off.
+ if (!tpEnabled) {
+ legend.style.gridTemplateAreas = legend.style.gridTemplateAreas.replace(
+ "tracker",
+ ""
+ );
+ let radio = document.getElementById("tab-tracker");
+ radio.setAttribute("disabled", true);
+ document.querySelector("#tab-tracker ~ label").style.display = "none";
+ }
+ if (!socialEnabled) {
+ legend.style.gridTemplateAreas = legend.style.gridTemplateAreas.replace(
+ "social",
+ ""
+ );
+ let radio = document.getElementById("tab-social");
+ radio.setAttribute("disabled", true);
+ document.querySelector("#tab-social ~ label").style.display = "none";
+ }
+ if (!blockingCookies) {
+ legend.style.gridTemplateAreas = legend.style.gridTemplateAreas.replace(
+ "cookie",
+ ""
+ );
+ let radio = document.getElementById("tab-cookie");
+ radio.setAttribute("disabled", true);
+ document.querySelector("#tab-cookie ~ label").style.display = "none";
+ }
+ if (!cryptominingEnabled) {
+ legend.style.gridTemplateAreas = legend.style.gridTemplateAreas.replace(
+ "cryptominer",
+ ""
+ );
+ let radio = document.getElementById("tab-cryptominer");
+ radio.setAttribute("disabled", true);
+ document.querySelector("#tab-cryptominer ~ label").style.display =
+ "none";
+ }
+ if (!fingerprintingEnabled) {
+ legend.style.gridTemplateAreas = legend.style.gridTemplateAreas.replace(
+ "fingerprinter",
+ ""
+ );
+ let radio = document.getElementById("tab-fingerprinter");
+ radio.setAttribute("disabled", true);
+ document.querySelector("#tab-fingerprinter ~ label").style.display =
+ "none";
+ }
+
+ let firstRadio = document.querySelector("input:enabled");
+ // There will be no radio options if we are showing the
+ firstRadio.checked = true;
+ document.body.setAttribute("focuseddatatype", firstRadio.dataset.type);
+
+ addListeners();
+ }
+ };
+
+ let addListeners = () => {
+ let wrapper = document.querySelector(".body-wrapper");
+ let triggerTabClick = ev => {
+ if (ev.originalTarget.dataset.type) {
+ document.getElementById(`tab-${ev.target.dataset.type}`).click();
+ }
+ };
+
+ let triggerTabFocus = ev => {
+ if (ev.originalTarget.dataset) {
+ wrapper.classList.add("hover-" + ev.originalTarget.dataset.type);
+ }
+ };
+
+ let triggerTabBlur = ev => {
+ if (ev.originalTarget.dataset) {
+ wrapper.classList.remove("hover-" + ev.originalTarget.dataset.type);
+ }
+ };
+ wrapper.addEventListener("mouseout", triggerTabBlur);
+ wrapper.addEventListener("mouseover", triggerTabFocus);
+ wrapper.addEventListener("click", triggerTabClick);
+
+ // 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);
+ });
+ radio.addEventListener("focus", ev => {
+ wrapper.classList.add("hover-" + ev.originalTarget.dataset.type);
+ document.body.setAttribute("focuseddatatype", ev.target.dataset.type);
+ });
+ radio.addEventListener("blur", ev => {
+ wrapper.classList.remove("hover-" + ev.originalTarget.dataset.type);
+ });
+ }
+ };
+
+ RPMSendQuery("FetchContentBlockingEvents", {
+ from: weekAgoInMs,
+ to: todayInMs,
+ }).then(createGraph);
+
+ let exitIcon = document.querySelector("#mobile-hanger .exit-icon");
+ // hide the mobile promotion and keep hidden with a pref.
+ exitIcon.addEventListener("click", () => {
+ RPMSetBoolPref("browser.contentblocking.report.show_mobile_app", false);
+ document.getElementById("mobile-hanger").classList.add("hidden");
+ });
+
+ let androidMobileAppLink = document.getElementById(
+ "android-mobile-inline-link"
+ );
+ androidMobileAppLink.href = RPMGetStringPref(
+ "browser.contentblocking.report.mobile-android.url"
+ );
+ androidMobileAppLink.addEventListener("click", () => {
+ document.sendTelemetryEvent("click", "mobile_app_link", "android");
+ });
+ let iosMobileAppLink = document.getElementById("ios-mobile-inline-link");
+ iosMobileAppLink.href = RPMGetStringPref(
+ "browser.contentblocking.report.mobile-ios.url"
+ );
+ iosMobileAppLink.addEventListener("click", () => {
+ document.sendTelemetryEvent("click", "mobile_app_link", "ios");
+ });
+
+ let lockwiseEnabled = RPMGetBoolPref(
+ "browser.contentblocking.report.lockwise.enabled",
+ true
+ );
+
+ let lockwiseCard;
+ if (lockwiseEnabled) {
+ const lockwiseUI = document.querySelector(".lockwise-card");
+ lockwiseUI.classList.remove("hidden");
+ lockwiseUI.classList.add("loading");
+
+ lockwiseCard = new LockwiseCard(document);
+ lockwiseCard.init();
+ }
+
+ RPMSendQuery("FetchUserLoginsData", {}).then(data => {
+ if (lockwiseCard) {
+ // Once data for the user is retrieved, display the lockwise card.
+ lockwiseCard.buildContent(data);
+ }
+
+ if (
+ RPMGetBoolPref("browser.contentblocking.report.show_mobile_app") &&
+ !data.mobileDeviceConnected
+ ) {
+ document
+ .getElementById("mobile-hanger")
+ .classList.toggle("hidden", false);
+ }
+ });
+
+ // For tests
+ const lockwiseUI = document.querySelector(".lockwise-card");
+ lockwiseUI.dataset.enabled = lockwiseEnabled;
+
+ let monitorEnabled = RPMGetBoolPref(
+ "browser.contentblocking.report.monitor.enabled",
+ true
+ );
+ if (monitorEnabled) {
+ // Show the Monitor card.
+ const monitorUI = document.querySelector(".card.monitor-card.hidden");
+ monitorUI.classList.remove("hidden");
+ monitorUI.classList.add("loading");
+
+ const monitorCard = new MonitorCard(document);
+ monitorCard.init();
+ }
+
+ // For tests
+ const monitorUI = document.querySelector(".monitor-card");
+ monitorUI.dataset.enabled = monitorEnabled;
+
+ const proxyEnabled = RPMGetBoolPref(
+ "browser.contentblocking.report.proxy.enabled",
+ true
+ );
+
+ if (proxyEnabled) {
+ const proxyCard = new ProxyCard(document);
+ proxyCard.init();
+ }
+
+ // For tests
+ const proxyUI = document.querySelector(".proxy-card");
+ proxyUI.dataset.enabled = proxyEnabled;
+
+ const VPNEnabled = RPMGetBoolPref("browser.vpn_promo.enabled", true);
+ if (VPNEnabled) {
+ const vpnCard = new VPNCard(document);
+ vpnCard.init();
+ }
+ // For tests
+ const vpnUI = document.querySelector(".vpn-card");
+ vpnUI.dataset.enabled = VPNEnabled;
+});