/* 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; });