diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 19:47:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 19:47:39 +0000 |
commit | 8d13bdc6cac0e20c43c6f909fc0208774b9c5c84 (patch) | |
tree | 5fd46925c6b4a881c9208772ed8e5cc0588bc164 /src/js/options.js | |
parent | Initial commit. (diff) | |
download | privacybadger-8d13bdc6cac0e20c43c6f909fc0208774b9c5c84.tar.xz privacybadger-8d13bdc6cac0e20c43c6f909fc0208774b9c5c84.zip |
Adding upstream version 2020.10.7.upstream/2020.10.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/js/options.js')
-rw-r--r-- | src/js/options.js | 976 |
1 files changed, 976 insertions, 0 deletions
diff --git a/src/js/options.js b/src/js/options.js new file mode 100644 index 0000000..7ca7008 --- /dev/null +++ b/src/js/options.js @@ -0,0 +1,976 @@ +/* + * This file is part of Adblock Plus <http://adblockplus.org/>, + * 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 <http://www.gnu.org/licenses/>. + */ + +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('<html><head></head><body></body></html>'); + 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++) { + $('<option>').text(sites[i]).appendTo($select); + } +} + +function addDisabledSite(event) { + event.preventDefault(); + + let domain = utils.getHostFromDomainInput( + document.getElementById("new-disabled-site-input").value.replace(/\s/g, "") + ); + + if (!domain) { + return confirm(i18n.getMessage("invalid_domain")); + } + + chrome.runtime.sendMessage({ + type: "disablePrivacyBadgerForOrigin", + domain + }, (response) => { + OPTIONS_DATA.settings.disabledSites = response.disabledSites; + reloadDisabledSites(); + document.getElementById("new-disabled-site-input").value = ""; + }); +} + +function removeDisabledSite(event) { + event.preventDefault(); + + let domains = []; + let $selected = $("#allowlist-select option:selected"); + for (let i = 0; i < $selected.length; i++) { + domains.push($selected[i].text); + } + + chrome.runtime.sendMessage({ + type: "enablePrivacyBadgerForOriginList", + domains + }, (response) => { + OPTIONS_DATA.settings.disabledSites = response.disabledSites; + reloadDisabledSites(); + }); +} + +// Tracking Domains slider functions + +/** + * Gets action for given origin. + * @param {String} origin - Origin to get action for. + */ +function getOriginAction(origin) { + return OPTIONS_DATA.origins[origin]; +} + +function revertDomainControl(event) { + event.preventDefault(); + + let origin = $(event.target).parent().data('origin'); + + chrome.runtime.sendMessage({ + type: "revertDomainControl", + origin + }, (response) => { + // update any sliders that changed as a result + updateSliders(response.origins); + // update cached domain data + OPTIONS_DATA.origins = response.origins; + }); +} + +/** + * Displays list of all tracking domains along with toggle controls. + */ +function updateSummary() { + // if there are no tracking domains + let allTrackingDomains = Object.keys(OPTIONS_DATA.origins); + if (!allTrackingDomains || !allTrackingDomains.length) { + // hide the number of trackers and slider instructions message + $("#options_domain_list_trackers").hide(); + + // show "no trackers" message + $("#options_domain_list_no_trackers").show(); + $("#blockedResources").html(''); + $("#tracking-domains-div").hide(); + + // activate tooltips + $('.tooltip:not(.tooltipstered)').tooltipster(TOOLTIP_CONF); + + return; + } + + // reloadTrackingDomainsTab can be called multiple times, needs to be reversible + $("#options_domain_list_no_trackers").hide(); + $("#tracking-domains-div").show(); + + // count unique (cookie)blocked tracking base domains + let blockedDomains = getOriginsArray(OPTIONS_DATA.origins, null, null, null, false); + let baseDomains = new Set(blockedDomains.map(d => window.getBaseDomain(d))); + $("#options_domain_list_trackers").html(i18n.getMessage( + "options_domain_list_trackers", [ + baseDomains.size, + "<a target='_blank' title='" + _.escape(i18n.getMessage("what_is_a_tracker")) + "' class='tooltip' href='https://privacybadger.org/#What-is-a-third-party-tracker'>" + ] + )).show(); +} + +/** + * Displays list of all tracking domains along with toggle controls. + */ +function reloadTrackingDomainsTab() { + updateSummary(); + + // Get containing HTML for domain list along with toggle legend icons. + $("#blockedResources")[0].innerHTML = htmlUtils.getTrackerContainerHtml(); + + // activate tooltips + $('.tooltip:not(.tooltipstered)').tooltipster(TOOLTIP_CONF); + + // Display tracking domains. + showTrackingDomains( + getOriginsArray( + OPTIONS_DATA.origins, + $("#trackingDomainSearch").val(), + $('#tracking-domains-type-filter').val(), + $('#tracking-domains-status-filter').val(), + $('#tracking-domains-show-not-yet-blocked').prop('checked') + ) + ); +} + +/** + * Displays filtered list of tracking domains based on user input. + */ +function filterTrackingDomains() { + const $searchFilter = $('#trackingDomainSearch'), + $typeFilter = $('#tracking-domains-type-filter'), + $statusFilter = $('#tracking-domains-status-filter'); + + if ($typeFilter.val() == "dnt") { + $statusFilter.prop("disabled", true).val(""); + } else { + $statusFilter.prop("disabled", false); + } + + let search_update = (this == $searchFilter[0]), + initial_search_text = $searchFilter.val().toLowerCase(), + time_to_wait = 0, + callback = function () {}; + + // If we are here because the search filter got updated, + // wait a short period of time and see if search text has changed. + // If so it means user is still typing so hold off on filtering. + if (search_update) { + time_to_wait = 500; + callback = function () { + $searchFilter.focus(); + }; + } + + setTimeout(function () { + // check search text + let search_text = $searchFilter.val().toLowerCase(); + if (search_text != initial_search_text) { + return; + } + + // show filtered origins + let filteredOrigins = getOriginsArray( + OPTIONS_DATA.origins, + search_text, + $typeFilter.val(), + $statusFilter.val(), + $('#tracking-domains-show-not-yet-blocked').prop('checked') + ); + showTrackingDomains(filteredOrigins, callback); + + }, time_to_wait); +} + +/** + * Renders the list of tracking domains. + * + * @param {Array} domains + * @param {Function} cb callback + */ +function showTrackingDomains(domains, cb) { + if (!cb) { + cb = function () {}; + } + + window.SLIDERS_DONE = false; + $('#tracking-domains-div').css('visibility', 'hidden'); + $('#tracking-domains-loader').show(); + + domains = htmlUtils.sortDomains(domains); + + let out = []; + for (let domain of domains) { + let action = getOriginAction(domain); + if (action) { + let show_breakage_warning = ( + action == constants.USER_BLOCK && + OPTIONS_DATA.cookieblocked.hasOwnProperty(domain) + ); + out.push(htmlUtils.getOriginHtml(domain, action, show_breakage_warning)); + } + } + + function renderDomains() { + const CHUNK = 100; + + let $printable = $(out.splice(0, CHUNK).join("")); + + $printable.appendTo('#blockedResourcesInner'); + + // activate tooltips + // TODO disabled for performance reasons + //$('#blockedResourcesInner .tooltip:not(.tooltipstered)').tooltipster( + // htmlUtils.DOMAIN_TOOLTIP_CONF); + + if (out.length) { + requestAnimationFrame(renderDomains); + } else { + $('#tracking-domains-loader').hide(); + $('#tracking-domains-div').css('visibility', 'visible'); + window.SLIDERS_DONE = true; + cb(); + } + } + + $('#blockedResourcesInner').empty(); + + if (out.length) { + requestAnimationFrame(renderDomains); + } else { + $('#tracking-domains-loader').hide(); + $('#tracking-domains-div').css('visibility', 'visible'); + window.SLIDERS_DONE = true; + cb(); + } +} + +/** + * https://tools.ietf.org/html/draft-ietf-rtcweb-ip-handling-01#page-5 + * + * Toggle WebRTC IP address leak protection setting. + * + * When enabled, policy is set to Mode 3 (default_public_interface_only). + */ +function toggleWebRTCIPProtection() { + // Return early with non-supporting browsers + if (!OPTIONS_DATA.webRTCAvailable) { + return; + } + + let cpn = chrome.privacy.network; + + cpn.webRTCIPHandlingPolicy.get({}, function (result) { + // Update new value to be opposite of current browser setting + if (result.value == 'default_public_interface_only') { + cpn.webRTCIPHandlingPolicy.clear({}); + } else { + cpn.webRTCIPHandlingPolicy.set({ + value: 'default_public_interface_only' + }); + } + }); +} + +// handles overriding the alternateErrorPagesEnabled setting +function overrideAlternateErrorPagesSetting() { + const checked = $("#disable-google-nav-error-service-checkbox").prop("checked"); + + // update Badger settings so that we know to reapply the browser setting on startup + chrome.runtime.sendMessage({ + type: "updateSettings", + data: { + disableGoogleNavErrorService: checked + } + }); + + // update the browser setting + if (checked) { + chrome.privacy.services.alternateErrorPagesEnabled.set({ + value: false + }); + } else { + chrome.privacy.services.alternateErrorPagesEnabled.clear({}); + } +} + +// handles overriding the hyperlinkAuditingEnabled setting +function overrideHyperlinkAuditingSetting() { + const checked = $("#disable-hyperlink-auditing-checkbox").prop("checked"); + + // update Badger settings so that we know to reapply the browser setting on startup + chrome.runtime.sendMessage({ + type: "updateSettings", + data: { + disableHyperlinkAuditing: checked + } + }); + + // update the browser setting + if (checked) { + chrome.privacy.websites.hyperlinkAuditingEnabled.set({ + value: false + }); + } else { + chrome.privacy.websites.hyperlinkAuditingEnabled.clear({}); + } +} + +/** + * Updates domain tooltip, slider color. + * Also toggles status indicators like breakage warnings. + */ +function updateOrigin(origin, action, userset) { + let $clicker = $('#blockedResourcesInner div.clicker[data-origin="' + origin + '"]'), + $switchContainer = $clicker.find('.switch-container').first(); + + // update slider color via CSS + $switchContainer.removeClass([ + constants.BLOCK, + constants.COOKIEBLOCK, + constants.ALLOW, + constants.NO_TRACKING].join(" ")).addClass(action); + + let show_breakage_warning = ( + action == constants.BLOCK && + OPTIONS_DATA.cookieblocked.hasOwnProperty(origin) + ); + + htmlUtils.toggleBlockedStatus($clicker, userset, show_breakage_warning); + + // reinitialize the domain tooltip + // TODO disabled for performance reasons + //$clicker.find('.origin-inner').tooltipster('destroy'); + //$clicker.find('.origin-inner').attr( + // 'title', htmlUtils.getActionDescription(action, origin)); + //$clicker.find('.origin-inner').tooltipster(htmlUtils.DOMAIN_TOOLTIP_CONF); +} + +/** + * Updates the list of tracking domains in response to user actions. + * + * For example, moving the slider for example.com should move the sliders + * for www.example.com and cdn.example.com + */ +function updateSliders(updatedOriginData) { + let updated_domains = Object.keys(updatedOriginData); + + // update any sliders that changed + for (let domain of updated_domains) { + let action = updatedOriginData[domain]; + if (action == OPTIONS_DATA.origins[domain]) { + continue; + } + + let userset = false; + if (action.startsWith('user')) { + userset = true; + action = action.slice(5); + } + + // update slider position + let $radios = $('#blockedResourcesInner div.clicker[data-origin="' + domain + '"] input'), + selected_val = (action == constants.DNT ? constants.ALLOW : action); + // update the radio group without triggering a change event + // https://stackoverflow.com/a/22635728 + $radios.val([selected_val]); + + // update domain slider row tooltip/status indicators + updateOrigin(domain, action, userset); + } + + // remove sliders that are no longer present + let removed = Object.keys(OPTIONS_DATA.origins).filter( + x => !updated_domains.includes(x)); + for (let domain of removed) { + let $clicker = $('#blockedResourcesInner div.clicker[data-origin="' + domain + '"]'); + $clicker.remove(); + } +} + +/** + * Save the user setting for a domain by messaging the background page. + */ +function saveToggle(origin, action) { + chrome.runtime.sendMessage({ + type: "saveOptionsToggle", + origin, + action + }, (response) => { + // first update the cache for the slider + // that was just changed by the user + // to avoid redundantly updating it below + OPTIONS_DATA.origins[origin] = response.origins[origin]; + // update any sliders that changed as a result + updateSliders(response.origins); + // update cached domain data + OPTIONS_DATA.origins = response.origins; + }); +} + +/** + * Remove origin from Privacy Badger. + * @param {Event} event Click event triggered by user. + */ +function removeOrigin(event) { + event.preventDefault(); + + // confirm removal before proceeding + if (!confirm(i18n.getMessage("options_remove_origin_confirm"))) { + return; + } + + let origin = $(event.target).parent().data('origin'); + + chrome.runtime.sendMessage({ + type: "removeOrigin", + origin + }, (response) => { + // remove rows that are no longer here + updateSliders(response.origins); + // update cached domain data + OPTIONS_DATA.origins = response.origins; + // if we removed domains, the summary text may have changed + updateSummary(); + }); +} + +/** + * Update which widgets should be blocked instead of replaced + * @param {Event} event The DOM event triggered by selecting an option + */ +function updateWidgetReplacementExceptions() { + const widgetReplacementExceptions = $('#hide-widgets-select').select2('data').map(({ id }) => id); + chrome.runtime.sendMessage({ + type: "updateSettings", + data: { widgetReplacementExceptions } + }); +} + +$(function () { + $.tooltipster.setDefaults(htmlUtils.TOOLTIPSTER_DEFAULTS); + + chrome.runtime.sendMessage({ + type: "getOptionsData", + }, (response) => { + OPTIONS_DATA = response; + loadOptions(); + }); +}); |