/*
* 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++) {
$('