/* * This file is part of Adblock Plus , * Copyright (C) 2006-2013 Eyeo GmbH * * Adblock Plus is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * Adblock Plus is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Adblock Plus. If not, see . */ window.OPTIONS_INITIALIZED = false; window.SLIDERS_DONE = false; const TOOLTIP_CONF = { maxWidth: 400 }; const USER_DATA_EXPORT_KEYS = ["action_map", "snitch_map", "settings_map"]; let i18n = chrome.i18n; let constants = require("constants"); let { getOriginsArray } = require("optionslib"); let htmlUtils = require("htmlutils").htmlUtils; let utils = require("utils"); let OPTIONS_DATA = {}; /* * Loads options from pb storage and sets UI elements accordingly. */ function loadOptions() { // Set page title to i18n version of "Privacy Badger Options" document.title = i18n.getMessage("options_title"); // Add event listeners $("#allowlist-form").on("submit", addDisabledSite); $("#remove-disabled-site").on("click", removeDisabledSite); $("#cloud-upload").on("click", uploadCloud); $("#cloud-download").on("click", downloadCloud); $('#importTrackerButton').on("click", loadFileChooser); $('#importTrackers').on("change", importTrackerList); $('#exportTrackers').on("click", exportUserData); $('#resetData').on("click", resetData); $('#removeAllData').on("click", removeAllData); if (OPTIONS_DATA.settings.showTrackingDomains) { $('#tracking-domains-overlay').hide(); } else { $('#blockedResourcesContainer').hide(); $('#show-tracking-domains-checkbox').on("click", () => { $('#tracking-domains-overlay').hide(); $('#blockedResourcesContainer').show(); chrome.runtime.sendMessage({ type: "updateSettings", data: { showTrackingDomains: true } }); }); } // Set up input for searching through tracking domains. $("#trackingDomainSearch").on("input", filterTrackingDomains); $("#tracking-domains-type-filter").on("change", filterTrackingDomains); $("#tracking-domains-status-filter").on("change", filterTrackingDomains); $("#tracking-domains-show-not-yet-blocked").on("change", filterTrackingDomains); // Add event listeners for origins container. $('#blockedResourcesContainer').on('change', 'input:radio', function () { let $radio = $(this), $clicker = $radio.parents('.clicker').first(), origin = $clicker.data('origin'), action = $radio.val(); // update domain slider row tooltip/status indicators updateOrigin(origin, action, true); // persist the change saveToggle(origin, action); }); $('#blockedResourcesContainer').on('click', '.userset .honeybadgerPowered', revertDomainControl); $('#blockedResourcesContainer').on('click', '.removeOrigin', removeOrigin); // Display jQuery UI elements $("#tabs").tabs({ activate: function (event, ui) { // update options page URL fragment identifier // to preserve selected tab on page reload history.replaceState(null, null, "#" + ui.newPanel.attr('id')); } }); $("button").button(); $("#add-disabled-site").button("option", "icons", {primary: "ui-icon-plus"}); $("#remove-disabled-site").button("option", "icons", {primary: "ui-icon-minus"}); $("#cloud-upload").button("option", "icons", {primary: "ui-icon-arrowreturnthick-1-n"}); $("#cloud-download").button("option", "icons", {primary: "ui-icon-arrowreturnthick-1-s"}); $(".importButton").button("option", "icons", {primary: "ui-icon-plus"}); $("#exportTrackers").button("option", "icons", {primary: "ui-icon-extlink"}); $("#resetData").button("option", "icons", {primary: "ui-icon-arrowrefresh-1-w"}); $("#removeAllData").button("option", "icons", {primary: "ui-icon-closethick"}); $("#show_counter_checkbox").on("click", updateShowCounter); $("#show_counter_checkbox").prop("checked", OPTIONS_DATA.settings.showCounter); $("#replace-widgets-checkbox") .on("click", updateWidgetReplacement) .prop("checked", OPTIONS_DATA.isWidgetReplacementEnabled); $("#enable_dnt_checkbox").on("click", updateDNTCheckboxClicked); $("#enable_dnt_checkbox").prop("checked", OPTIONS_DATA.settings.sendDNTSignal); $("#check_dnt_policy_checkbox").on("click", updateCheckingDNTPolicy); $("#check_dnt_policy_checkbox").prop("checked", OPTIONS_DATA.settings.checkForDNTPolicy).prop("disabled", !OPTIONS_DATA.settings.sendDNTSignal); // only show the alternateErrorPagesEnabled override if browser supports it if (chrome.privacy && chrome.privacy.services && chrome.privacy.services.alternateErrorPagesEnabled) { $("#privacy-settings-header").show(); $("#disable-google-nav-error-service").show(); $('#disable-google-nav-error-service-checkbox') .prop("checked", OPTIONS_DATA.settings.disableGoogleNavErrorService) .on("click", overrideAlternateErrorPagesSetting); } // only show the hyperlinkAuditingEnabled override if browser supports it if (chrome.privacy && chrome.privacy.websites && chrome.privacy.websites.hyperlinkAuditingEnabled) { $("#privacy-settings-header").show(); $("#disable-hyperlink-auditing").show(); $("#disable-hyperlink-auditing-checkbox") .prop("checked", OPTIONS_DATA.settings.disableHyperlinkAuditing) .on("click", overrideHyperlinkAuditingSetting); } if (OPTIONS_DATA.webRTCAvailable) { $("#webRTCToggle").show(); $("#toggle_webrtc_mode").on("click", toggleWebRTCIPProtection); chrome.privacy.network.webRTCIPHandlingPolicy.get({}, result => { // auto check the option box if ip leak is already protected at diff levels, via pb or another extension if (result.value == "default_public_interface_only" || result.value == "disable_non_proxied_udp") { $("#toggle_webrtc_mode").prop("checked", true); } }); } $('#local-learning-checkbox') .prop("checked", OPTIONS_DATA.settings.learnLocally) .on("click", (event) => { const enabled = $(event.currentTarget).prop("checked"); chrome.runtime.sendMessage({ type: "updateSettings", data: { learnLocally: enabled } }, function () { $("#learn-in-incognito-checkbox") .prop("disabled", (enabled ? false : "disabled")) .prop("checked", (enabled ? OPTIONS_DATA.settings.learnInIncognito : false)); $("#show-nontracking-domains-checkbox") .prop("disabled", (enabled ? false : "disabled")) .prop("checked", (enabled ? OPTIONS_DATA.settings.showNonTrackingDomains : false)); $("#learning-setting-divs").slideToggle(enabled); $("#not-yet-blocked-filter").toggle(enabled); }); }); if (OPTIONS_DATA.settings.learnLocally) { $("#learning-setting-divs").show(); $("#not-yet-blocked-filter").show(); } $("#learn-in-incognito-checkbox") .prop("disabled", OPTIONS_DATA.settings.learnLocally ? false : "disabled") .prop("checked", ( OPTIONS_DATA.settings.learnLocally ? OPTIONS_DATA.settings.learnInIncognito : false )) .on("click", (event) => { const enabled = $(event.currentTarget).prop("checked"); chrome.runtime.sendMessage({ type: "updateSettings", data: { learnInIncognito: enabled } }, function () { OPTIONS_DATA.settings.learnInIncognito = enabled; }); }); $('#show-nontracking-domains-checkbox') .prop("disabled", OPTIONS_DATA.settings.learnLocally ? false : "disabled") .prop("checked", ( OPTIONS_DATA.settings.learnLocally ? OPTIONS_DATA.settings.showNonTrackingDomains : false )) .on("click", (event) => { const enabled = $(event.currentTarget).prop("checked"); chrome.runtime.sendMessage({ type: "updateSettings", data: { showNonTrackingDomains: enabled } }, function () { OPTIONS_DATA.settings.showNonTrackingDomains = enabled; }); }); const widgetSelector = $("#hide-widgets-select"); widgetSelector.prop("disabled", OPTIONS_DATA.isWidgetReplacementEnabled ? false : "disabled"); $("#replace-widgets-checkbox").change(function () { if ($(this).is(":checked")) { widgetSelector.prop("disabled", false); } else { widgetSelector.prop("disabled", "disabled"); } }); // Initialize Select2 and populate options widgetSelector.select2(); OPTIONS_DATA.widgets.forEach(function (key) { const isSelected = OPTIONS_DATA.settings.widgetReplacementExceptions.includes(key); const option = new Option(key, key, false, isSelected); widgetSelector.append(option).trigger("change"); }); widgetSelector.on('select2:select', updateWidgetReplacementExceptions); widgetSelector.on('select2:unselect', updateWidgetReplacementExceptions); widgetSelector.on('select2:clear', updateWidgetReplacementExceptions); reloadDisabledSites(); reloadTrackingDomainsTab(); $('html').css({ overflow: 'visible', visibility: 'visible' }); window.OPTIONS_INITIALIZED = true; } /** * Opens the file chooser to allow a user to select * a file to import. */ function loadFileChooser() { var fileChooser = document.getElementById('importTrackers'); fileChooser.click(); } /** * Import a list of trackers supplied by the user * NOTE: list must be in JSON format to be parsable */ function importTrackerList() { var file = this.files[0]; if (file) { var reader = new FileReader(); reader.readAsText(file); reader.onload = function(e) { parseUserDataFile(e.target.result); }; } else { var selectFile = i18n.getMessage("import_select_file"); confirm(selectFile); } document.getElementById("importTrackers").value = ''; } /** * Parses Privacy Badger data uploaded by the user. * * @param {String} storageMapsList data from JSON file that user provided */ function parseUserDataFile(storageMapsList) { let lists; try { lists = JSON.parse(storageMapsList); } catch (e) { return confirm(i18n.getMessage("invalid_json")); } // validate by checking we have the same keys in the import as in the export if (!_.isEqual( Object.keys(lists).sort(), USER_DATA_EXPORT_KEYS.sort() )) { return confirm(i18n.getMessage("invalid_json")); } // check for webrtc setting in the imported settings map if (lists.settings_map.preventWebRTCIPLeak) { // verify that the user hasn't already enabled this option if (!$("#toggle_webrtc_mode").prop("checked")) { toggleWebRTCIPProtection(); } // this browser-controlled setting doesn't belong in Badger's settings object delete lists.settings_map.preventWebRTCIPLeak; } chrome.runtime.sendMessage({ type: "mergeUserData", data: lists }, (response) => { OPTIONS_DATA.settings.disabledSites = response.disabledSites; OPTIONS_DATA.origins = response.origins; reloadDisabledSites(); reloadTrackingDomainsTab(); // TODO general settings are not updated confirm(i18n.getMessage("import_successful")); }); } function resetData() { var resetWarn = i18n.getMessage("reset_data_confirm"); if (confirm(resetWarn)) { chrome.runtime.sendMessage({type: "resetData"}, () => { // reload page to refresh tracker list location.reload(); }); } } function removeAllData() { var removeWarn = i18n.getMessage("remove_all_data_confirm"); if (confirm(removeWarn)) { chrome.runtime.sendMessage({type: "removeAllData"}, () => { location.reload(); }); } } function downloadCloud() { chrome.runtime.sendMessage({type: "downloadCloud"}, function (response) { if (response.success) { alert(i18n.getMessage("download_cloud_success")); OPTIONS_DATA.settings.disabledSites = response.disabledSites; reloadDisabledSites(); } else { console.error("Cloud sync error:", response.message); if (response.message === i18n.getMessage("download_cloud_no_data")) { alert(response.message); } else { alert(i18n.getMessage("download_cloud_failure")); } } } ); } function uploadCloud() { chrome.runtime.sendMessage({type: "uploadCloud"}, function (status) { if (status.success) { alert(i18n.getMessage("upload_cloud_success")); } else { console.error("Cloud sync error:", status.message); alert(i18n.getMessage("upload_cloud_failure")); } } ); } /** * Export the user's data, including their list of trackers from * action_map and snitch_map, along with their settings. * List will be in JSON format that can be edited and reimported * in another instance of Privacy Badger. */ function exportUserData() { chrome.storage.local.get(USER_DATA_EXPORT_KEYS, function (maps) { // exports the user's prevent webrtc leak setting if it's checked if ($("#toggle_webrtc_mode").prop("checked")) { maps.settings_map.preventWebRTCIPLeak = true; } let mapJSON = JSON.stringify(maps); // Append the formatted date to the exported file name let currDate = new Date().toLocaleString(); let escapedDate = currDate // illegal filename charset regex from // https://github.com/parshap/node-sanitize-filename/blob/ef1e8ad58e95eb90f8a01f209edf55cd4176e9c8/index.js .replace(/[\/\?<>\\:\*\|"]/g, '_') /* eslint no-useless-escape:off */ // also collapse-replace commas and spaces .replace(/[, ]+/g, '_'); let filename = 'PrivacyBadger_user_data-' + escapedDate + '.json'; // Download workaround taken from uBlock Origin // https://github.com/gorhill/uBlock/blob/40a85f8c04840ae5f5875c1e8b5fa17578c5bd1a/platform/chromium/vapi-common.js let a = document.createElement('a'); a.setAttribute('download', filename || ''); let blob = new Blob([mapJSON], { type: 'application/json' }); // pass a useful mime type here a.href = URL.createObjectURL(blob); function clickBlobLink() { a.dispatchEvent(new MouseEvent('click')); URL.revokeObjectURL(blob); } /** * Firefox workaround to insert the blob link in an iFrame * https://bugzilla.mozilla.org/show_bug.cgi?id=1420419#c18 */ function addBlobWorkAroundForFirefox() { // Create or use existing iframe for the blob 'a' element let iframe = document.getElementById('exportUserDataIframe'); if (!iframe) { iframe = document.createElement('iframe'); iframe.id = "exportUserDataIframe"; iframe.setAttribute("style", "visibility: hidden; height: 0; width: 0"); document.getElementById('export').appendChild(iframe); iframe.contentWindow.document.open(); iframe.contentWindow.document.write(''); iframe.contentWindow.document.close(); } else { // Remove the old 'a' element from the iframe let oldElement = iframe.contentWindow.document.body.lastChild; iframe.contentWindow.document.body.removeChild(oldElement); } iframe.contentWindow.document.body.appendChild(a); } // TODO remove browser check and simplify code once Firefox 58 goes away // https://bugzilla.mozilla.org/show_bug.cgi?id=1420419 if (chrome.runtime.getBrowserInfo) { chrome.runtime.getBrowserInfo((info) => { if (info.name == "Firefox" || info.name == "Waterfox") { addBlobWorkAroundForFirefox(); } clickBlobLink(); }); } else { clickBlobLink(); } }); } /** * Update setting for whether or not to show counter on Privacy Badger badge. */ function updateShowCounter() { const showCounter = $("#show_counter_checkbox").prop("checked"); chrome.runtime.sendMessage({ type: "updateSettings", data: { showCounter } }, () => { // Refresh display for each tab's PB badge. chrome.tabs.query({}, function(tabs) { tabs.forEach(function(tab) { chrome.runtime.sendMessage({ type: "updateBadge", tab_id: tab.id }); }); }); }); } /** * Update setting for whether or not to replace * social buttons/video players/commenting widgets. */ function updateWidgetReplacement() { const socialWidgetReplacementEnabled = $("#replace-widgets-checkbox").prop("checked"); chrome.runtime.sendMessage({ type: "updateSettings", data: { socialWidgetReplacementEnabled } }); } /** * Update DNT checkbox clicked */ function updateDNTCheckboxClicked() { const enabled = $("#enable_dnt_checkbox").prop("checked"); chrome.runtime.sendMessage({ type: "updateSettings", data: { sendDNTSignal: enabled } }); $("#check_dnt_policy_checkbox").prop("checked", enabled).prop("disabled", !enabled); updateCheckingDNTPolicy(); } function updateCheckingDNTPolicy() { const enabled = $("#check_dnt_policy_checkbox").prop("checked"); chrome.runtime.sendMessage({ type: "updateSettings", data: { checkForDNTPolicy: enabled } }); } function reloadDisabledSites() { let sites = OPTIONS_DATA.settings.disabledSites, $select = $('#allowlist-select'); // sort disabled sites the same way blocked sites are sorted sites = htmlUtils.sortDomains(sites); $select.empty(); for (let i = 0; i < sites.length; i++) { $('