| Index: js/desktop-options.js |
| =================================================================== |
| rename from desktop-options.js |
| rename to js/desktop-options.js |
| --- a/desktop-options.js |
| +++ b/js/desktop-options.js |
| @@ -15,1462 +15,1460 @@ |
| * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| /* globals checkShareResource, getDocLink, i18nFormatDateTime, openSharePopup, |
| setLinks, E */ |
| "use strict"; |
| +let subscriptionsMap = Object.create(null); |
| +let filtersMap = Object.create(null); |
| +let collections = Object.create(null); |
| +let acceptableAdsUrl = null; |
| +let acceptableAdsPrivacyUrl = null; |
| +let isCustomFiltersLoaded = false; |
| +let {getMessage} = browser.i18n; |
| +let {setElementText} = ext.i18n; |
| +let customFilters = []; |
| +let filterErrors = new Map([ |
| + ["synchronize_invalid_url", |
| + "options_filterList_lastDownload_invalidURL"], |
| + ["synchronize_connection_error", |
| + "options_filterList_lastDownload_connectionError"], |
| + ["synchronize_invalid_data", |
| + "options_filterList_lastDownload_invalidData"], |
| + ["synchronize_checksum_mismatch", |
| + "options_filterList_lastDownload_checksumMismatch"] |
| +]); |
| +const timestampUI = Symbol(); |
| +const whitelistedDomainRegexp = /^@@\|\|([^/:]+)\^\$document$/; |
| +// Period of time in milliseconds |
| +const minuteInMs = 60000; |
| +const hourInMs = 3600000; |
| +const fullDayInMs = 86400000; |
| + |
| +function Collection(details) |
| { |
| - let subscriptionsMap = Object.create(null); |
| - let filtersMap = Object.create(null); |
| - let collections = Object.create(null); |
| - let acceptableAdsUrl = null; |
| - let acceptableAdsPrivacyUrl = null; |
| - let isCustomFiltersLoaded = false; |
| - let {getMessage} = browser.i18n; |
| - let {setElementText} = ext.i18n; |
| - let customFilters = []; |
| - let filterErrors = new Map([ |
| - ["synchronize_invalid_url", |
| - "options_filterList_lastDownload_invalidURL"], |
| - ["synchronize_connection_error", |
| - "options_filterList_lastDownload_connectionError"], |
| - ["synchronize_invalid_data", |
| - "options_filterList_lastDownload_invalidData"], |
| - ["synchronize_checksum_mismatch", |
| - "options_filterList_lastDownload_checksumMismatch"] |
| - ]); |
| - const timestampUI = Symbol(); |
| - const whitelistedDomainRegexp = /^@@\|\|([^/:]+)\^\$document$/; |
| - // Period of time in milliseconds |
| - const minuteInMs = 60000; |
| - const hourInMs = 3600000; |
| - const fullDayInMs = 86400000; |
| + this.details = details; |
| + this.items = []; |
| +} |
| + |
| +Collection.prototype._setEmpty = function(table, detail, removeEmpty) |
| +{ |
| + if (removeEmpty) |
| + { |
| + let placeholders = table.querySelectorAll(".empty-placeholder"); |
| + for (let placeholder of placeholders) |
| + table.removeChild(placeholder); |
| + |
| + execAction(detail.removeEmptyAction, table); |
| + } |
| + else |
| + { |
| + let {emptyTexts = []} = detail; |
| + for (let text of emptyTexts) |
| + { |
| + let placeholder = document.createElement("li"); |
| + placeholder.className = "empty-placeholder"; |
| + placeholder.textContent = getMessage(text); |
| + table.appendChild(placeholder); |
| + } |
| + |
| + execAction(detail.setEmptyAction, table); |
| + } |
| +}; |
| + |
| +Collection.prototype._createElementQuery = function(item) |
| +{ |
| + let access = (item.url || item.text).replace(/'/g, "\\'"); |
| + return function(container) |
| + { |
| + return container.querySelector("[data-access='" + access + "']"); |
| + }; |
| +}; |
| + |
| +Collection.prototype._getItemTitle = function(item, i) |
| +{ |
| + if (this.details[i].getTitleFunction) |
| + return this.details[i].getTitleFunction(item); |
| + return item.title || item.url || item.text; |
| +}; |
| + |
| +Collection.prototype._sortItems = function() |
| +{ |
| + this.items.sort((a, b) => |
| + { |
| + // Make sure that Acceptable Ads is always last, since it cannot be |
| + // disabled, but only be removed. That way it's grouped together with |
| + // the "Own filter list" which cannot be disabled either at the bottom |
| + // of the filter lists in the Advanced tab. |
| + if (isAcceptableAds(a.url)) |
| + return 1; |
| + if (isAcceptableAds(b.url)) |
| + return -1; |
| + |
| + // Make sure that newly added entries always appear on top in descending |
| + // chronological order |
| + let aTimestamp = a[timestampUI] || 0; |
| + let bTimestamp = b[timestampUI] || 0; |
| + if (aTimestamp || bTimestamp) |
| + return bTimestamp - aTimestamp; |
| + |
| + let aTitle = this._getItemTitle(a, 0).toLowerCase(); |
| + let bTitle = this._getItemTitle(b, 0).toLowerCase(); |
| + return aTitle.localeCompare(bTitle); |
| + }); |
| +}; |
| + |
| +Collection.prototype.addItem = function(item) |
| +{ |
| + if (this.items.indexOf(item) >= 0) |
| + return; |
| + |
| + this.items.push(item); |
| + this._sortItems(); |
| + for (let j = 0; j < this.details.length; j++) |
| + { |
| + let detail = this.details[j]; |
| + let table = E(detail.id); |
| + let template = table.querySelector("template"); |
| + let listItem = document.createElement("li"); |
| + listItem.appendChild(document.importNode(template.content, true)); |
| + listItem.setAttribute("aria-label", this._getItemTitle(item, j)); |
| + listItem.setAttribute("data-access", item.url || item.text); |
| + listItem.setAttribute("role", "section"); |
| + |
| + let tooltip = listItem.querySelector("[data-tooltip]"); |
| + if (tooltip) |
| + { |
| + let tooltipId = tooltip.getAttribute("data-tooltip"); |
| + tooltipId = tooltipId.replace("%value%", item.recommended); |
| + if (getMessage(tooltipId)) |
| + { |
| + tooltip.setAttribute("data-tooltip", tooltipId); |
| + } |
| + } |
| + |
| + for (let control of listItem.querySelectorAll(".control")) |
| + { |
| + if (control.hasAttribute("title")) |
| + { |
| + let titleValue = getMessage(control.getAttribute("title")); |
| + control.setAttribute("title", titleValue); |
| + } |
| + } |
| + |
| + this._setEmpty(table, detail, true); |
| + if (table.children.length > 0) |
| + table.insertBefore(listItem, table.children[this.items.indexOf(item)]); |
| + else |
| + table.appendChild(listItem); |
| + |
| + this.updateItem(item); |
| + } |
| + return length; |
| +}; |
| + |
| +Collection.prototype.removeItem = function(item) |
| +{ |
| + let index = this.items.indexOf(item); |
| + if (index == -1) |
| + return; |
| + |
| + this.items.splice(index, 1); |
| + let getListElement = this._createElementQuery(item); |
| + for (let detail of this.details) |
| + { |
| + let table = E(detail.id); |
| + let element = getListElement(table); |
| + |
| + // Element gets removed so make sure to handle focus appropriately |
| + let control = element.querySelector(".control"); |
| + if (control && control == document.activeElement) |
| + { |
| + if (!focusNextElement(element.parentElement, control)) |
| + { |
| + // Fall back to next focusable element within same tab or dialog |
| + let focusableElement = element.parentElement; |
| + while (focusableElement) |
| + { |
| + if (focusableElement.classList.contains("tab-content") || |
| + focusableElement.classList.contains("dialog-content")) |
| + break; |
| + |
| + focusableElement = focusableElement.parentElement; |
| + } |
| + focusNextElement(focusableElement || document, control); |
| + } |
| + } |
| + |
| + element.parentElement.removeChild(element); |
| + if (this.items.length == 0) |
| + this._setEmpty(table, detail); |
| + } |
| +}; |
| + |
| +Collection.prototype.updateItem = function(item) |
| +{ |
| + let oldIndex = this.items.indexOf(item); |
| + this._sortItems(); |
| + let access = (item.url || item.text).replace(/'/g, "\\'"); |
| + for (let i = 0; i < this.details.length; i++) |
| + { |
| + let table = E(this.details[i].id); |
| + let element = table.querySelector("[data-access='" + access + "']"); |
| + if (!element) |
| + continue; |
| - function Collection(details) |
| + let title = this._getItemTitle(item, i); |
| + let displays = element.querySelectorAll("[data-display]"); |
| + for (let j = 0; j < displays.length; j++) |
| + { |
| + if (item[displays[j].dataset.display]) |
| + displays[j].textContent = item[displays[j].dataset.display]; |
| + else |
| + displays[j].textContent = title; |
| + } |
| + |
| + element.setAttribute("aria-label", title); |
| + if (this.details[i].searchable) |
| + element.setAttribute("data-search", title.toLowerCase()); |
| + let controls = element.querySelectorAll(".control[role='checkbox']"); |
| + for (let control of controls) |
| + { |
| + control.setAttribute("aria-checked", item.disabled == false); |
| + if (isAcceptableAds(item.url) && this == collections.filterLists) |
| + control.disabled = true; |
| + } |
| + |
| + let lastUpdateElement = element.querySelector(".last-update"); |
| + if (lastUpdateElement) |
| + { |
| + let message = element.querySelector(".message"); |
| + if (item.isDownloading) |
| + { |
| + let text = getMessage("options_filterList_lastDownload_inProgress"); |
| + message.textContent = text; |
| + element.classList.add("show-message"); |
| + } |
| + else if (item.downloadStatus != "synchronize_ok") |
| + { |
| + let error = filterErrors.get(item.downloadStatus); |
| + if (error) |
| + message.textContent = getMessage(error); |
| + else |
| + message.textContent = item.downloadStatus; |
| + element.classList.add("show-message"); |
| + } |
| + else if (item.lastDownload > 0) |
| + { |
| + let lastUpdate = item.lastDownload * 1000; |
| + let sinceUpdate = Date.now() - lastUpdate; |
| + if (sinceUpdate > fullDayInMs) |
| + { |
| + let lastUpdateDate = new Date(item.lastDownload * 1000); |
| + let monthName = lastUpdateDate.toLocaleString(undefined, |
| + {month: "short"}); |
| + let day = lastUpdateDate.getDate(); |
| + day = day < 10 ? "0" + day : day; |
| + lastUpdateElement.textContent = day + " " + monthName + " " + |
| + lastUpdateDate.getFullYear(); |
| + } |
| + else if (sinceUpdate > hourInMs) |
| + { |
| + lastUpdateElement.textContent = |
| + getMessage("options_filterList_hours"); |
| + } |
| + else if (sinceUpdate > minuteInMs) |
| + { |
| + lastUpdateElement.textContent = |
| + getMessage("options_filterList_minutes"); |
| + } |
| + else |
| + { |
| + lastUpdateElement.textContent = |
| + getMessage("options_filterList_now"); |
| + } |
| + element.classList.remove("show-message"); |
| + } |
| + } |
| + |
| + let websiteElement = element.querySelector(".context-menu .website"); |
| + if (websiteElement) |
| + { |
| + if (item.homepage) |
| + websiteElement.setAttribute("href", item.homepage); |
| + else |
| + websiteElement.setAttribute("aria-hidden", true); |
| + } |
| + |
| + let sourceElement = element.querySelector(".context-menu .source"); |
| + if (sourceElement) |
| + sourceElement.setAttribute("href", item.url); |
| + |
| + let newIndex = this.items.indexOf(item); |
| + if (oldIndex != newIndex) |
| + table.insertBefore(element, table.childNodes[newIndex]); |
| + } |
| +}; |
| + |
| +Collection.prototype.clearAll = function() |
| +{ |
| + this.items = []; |
| + for (let detail of this.details) |
| { |
| - this.details = details; |
| - this.items = []; |
| + let table = E(detail.id); |
| + let element = table.firstChild; |
| + while (element) |
| + { |
| + if (element.tagName == "LI" && !element.classList.contains("static")) |
| + table.removeChild(element); |
| + element = element.nextElementSibling; |
| + } |
| + |
| + this._setEmpty(table, detail); |
| + } |
| +}; |
| + |
| +function focusNextElement(container, currentElement) |
| +{ |
| + let focusables = container.querySelectorAll("a, button, input, .control"); |
| + focusables = Array.prototype.slice.call(focusables); |
| + let index = focusables.indexOf(currentElement); |
| + index += (index == focusables.length - 1) ? -1 : 1; |
| + |
| + let nextElement = focusables[index]; |
| + if (!nextElement) |
| + return false; |
| + |
| + nextElement.focus(); |
| + return true; |
| +} |
| + |
| +collections.protection = new Collection([ |
| + { |
| + id: "recommend-protection-list-table" |
| + } |
| +]); |
| +collections.langs = new Collection([ |
| + { |
| + id: "blocking-languages-table", |
| + emptyTexts: ["options_language_empty"], |
| + getTitleFunction: getLanguageTitle |
| + } |
| +]); |
| +collections.allLangs = new Collection([ |
| + { |
| + id: "all-lang-table-add", |
| + emptyTexts: ["options_dialog_language_other_empty"], |
| + getTitleFunction: getLanguageTitle |
| + } |
| +]); |
| +collections.more = new Collection([ |
| + { |
| + id: "more-list-table", |
| + setEmptyAction: "hide-more-filters-section", |
| + removeEmptyAction: "show-more-filters-section" |
| + } |
| +]); |
| +collections.whitelist = new Collection([ |
| + { |
| + id: "whitelisting-table", |
| + emptyTexts: ["options_whitelist_empty_1", "options_whitelist_empty_2"] |
| + } |
| +]); |
| +collections.filterLists = new Collection([ |
| + { |
| + id: "all-filter-lists-table", |
| + emptyTexts: ["options_filterList_empty"] |
| + } |
| +]); |
| + |
| +function addSubscription(subscription) |
| +{ |
| + let {disabled} = subscription; |
| + let collection = null; |
| + if (subscription.recommended) |
| + { |
| + if (subscription.recommended == "ads") |
| + { |
| + if (disabled == false) |
| + collection = collections.langs; |
| + |
| + collections.allLangs.addItem(subscription); |
| + } |
| + else |
| + { |
| + collection = collections.protection; |
| + } |
| + } |
| + else if (!isAcceptableAds(subscription.url) && disabled == false) |
| + { |
| + collection = collections.more; |
| } |
| - Collection.prototype._setEmpty = function(table, detail, removeEmpty) |
| + if (collection) |
| + collection.addItem(subscription); |
| + |
| + subscriptionsMap[subscription.url] = subscription; |
| + updateTooltips(); |
| +} |
| + |
| +function updateSubscription(subscription) |
| +{ |
| + for (let name in collections) |
| + collections[name].updateItem(subscription); |
| + |
| + if (subscription.recommended == "ads") |
| { |
| - if (removeEmpty) |
| + if (subscription.disabled) |
| + collections.langs.removeItem(subscription); |
| + else |
| + collections.langs.addItem(subscription); |
| + } |
| + else if (!subscription.recommended && !isAcceptableAds(subscription.url)) |
| + { |
| + if (subscription.disabled == false) |
| { |
| - let placeholders = table.querySelectorAll(".empty-placeholder"); |
| - for (let placeholder of placeholders) |
| - table.removeChild(placeholder); |
| - |
| - execAction(detail.removeEmptyAction, table); |
| + collections.more.addItem(subscription); |
| + updateTooltips(); |
| } |
| else |
| { |
| - let {emptyTexts = []} = detail; |
| - for (let text of emptyTexts) |
| - { |
| - let placeholder = document.createElement("li"); |
| - placeholder.className = "empty-placeholder"; |
| - placeholder.textContent = getMessage(text); |
| - table.appendChild(placeholder); |
| - } |
| + collections.more.removeItem(subscription); |
| + } |
| + } |
| +} |
| - execAction(detail.setEmptyAction, table); |
| +function updateFilter(filter) |
| +{ |
| + let match = filter.text.match(whitelistedDomainRegexp); |
| + if (match && !filtersMap[filter.text]) |
| + { |
| + filter.title = match[1]; |
| + collections.whitelist.addItem(filter); |
| + if (isCustomFiltersLoaded) |
| + { |
| + let text = getMessage("options_whitelist_notification", [filter.title]); |
| + showNotification(text); |
| } |
| - }; |
| - |
| - Collection.prototype._createElementQuery = function(item) |
| + } |
| + else |
| { |
| - let access = (item.url || item.text).replace(/'/g, "\\'"); |
| - return function(container) |
| - { |
| - return container.querySelector("[data-access='" + access + "']"); |
| - }; |
| - }; |
| + customFilters.push(filter.text); |
| + if (isCustomFiltersLoaded) |
| + updateCustomFiltersUi(); |
| + } |
| + |
| + filtersMap[filter.text] = filter; |
| +} |
| + |
| +function loadCustomFilters(filters) |
| +{ |
| + for (let filter of filters) |
| + updateFilter(filter); |
| + |
| + setCustomFiltersView("read"); |
| + isCustomFiltersLoaded = true; |
| +} |
| + |
| +function removeCustomFilter(text) |
| +{ |
| + let index = customFilters.indexOf(text); |
| + if (index >= 0) |
| + customFilters.splice(index, 1); |
| - Collection.prototype._getItemTitle = function(item, i) |
| - { |
| - if (this.details[i].getTitleFunction) |
| - return this.details[i].getTitleFunction(item); |
| - return item.title || item.url || item.text; |
| - }; |
| + updateCustomFiltersUi(); |
| +} |
| + |
| +function updateCustomFiltersUi() |
| +{ |
| + let customFiltersListElement = E("custom-filters-raw"); |
| + customFiltersListElement.value = customFilters.join("\n"); |
| +} |
| - Collection.prototype._sortItems = function() |
| - { |
| - this.items.sort((a, b) => |
| +function getLanguageTitle(item) |
| +{ |
| + let title = item.specialization; |
| + if (item.originalTitle && item.originalTitle.indexOf("+EasyList") > -1) |
| + title += " + " + getMessage("options_english"); |
| + return title; |
| +} |
| + |
| +function loadRecommendations() |
| +{ |
| + fetch("subscriptions.xml") |
| + .then((response) => |
| + { |
| + return response.text(); |
| + }) |
| + .then((text) => |
| { |
| - // Make sure that Acceptable Ads is always last, since it cannot be |
| - // disabled, but only be removed. That way it's grouped together with |
| - // the "Own filter list" which cannot be disabled either at the bottom |
| - // of the filter lists in the Advanced tab. |
| - if (isAcceptableAds(a.url)) |
| - return 1; |
| - if (isAcceptableAds(b.url)) |
| - return -1; |
| + let doc = new DOMParser().parseFromString(text, "application/xml"); |
| + let elements = doc.documentElement.getElementsByTagName("subscription"); |
| + for (let element of elements) |
| + { |
| + let type = element.getAttribute("type"); |
| + let subscription = { |
| + disabled: true, |
| + downloadStatus: null, |
| + homepage: null, |
| + specialization: element.getAttribute("specialization"), |
| + originalTitle: element.getAttribute("title"), |
| + recommended: type, |
| + url: element.getAttribute("url") |
| + }; |
| - // Make sure that newly added entries always appear on top in descending |
| - // chronological order |
| - let aTimestamp = a[timestampUI] || 0; |
| - let bTimestamp = b[timestampUI] || 0; |
| - if (aTimestamp || bTimestamp) |
| - return bTimestamp - aTimestamp; |
| + if (subscription.recommended != "ads") |
| + { |
| + type = type.replace(/\W/g, "_"); |
| + subscription.title = getMessage("common_feature_" + |
| + type + "_title"); |
| + } |
| + |
| + addSubscription(subscription); |
| + } |
| + }); |
| +} |
| - let aTitle = this._getItemTitle(a, 0).toLowerCase(); |
| - let bTitle = this._getItemTitle(b, 0).toLowerCase(); |
| - return aTitle.localeCompare(bTitle); |
| - }); |
| - }; |
| +function findParentData(element, dataName, returnElement) |
| +{ |
| + element = element.closest(`[data-${dataName}]`); |
| + if (!element) |
| + return null; |
| + if (returnElement) |
| + return element; |
| + return element.getAttribute(`data-${dataName}`); |
| +} |
| - Collection.prototype.addItem = function(item) |
| +function sendMessageHandleErrors(message, onSuccess) |
| +{ |
| + browser.runtime.sendMessage(message, (errors) => |
| { |
| - if (this.items.indexOf(item) >= 0) |
| - return; |
| + if (errors.length > 0) |
| + alert(errors.join("\n")); |
| + else if (onSuccess) |
| + onSuccess(); |
| + }); |
| +} |
| + |
| +function switchTab(id) |
| +{ |
| + location.hash = id; |
| +} |
| + |
| +function execAction(action, element) |
| +{ |
| + if (element.getAttribute("aria-disabled") == "true") |
| + return; |
| - this.items.push(item); |
| - this._sortItems(); |
| - for (let j = 0; j < this.details.length; j++) |
| - { |
| - let detail = this.details[j]; |
| - let table = E(detail.id); |
| - let template = table.querySelector("template"); |
| - let listItem = document.createElement("li"); |
| - listItem.appendChild(document.importNode(template.content, true)); |
| - listItem.setAttribute("aria-label", this._getItemTitle(item, j)); |
| - listItem.setAttribute("data-access", item.url || item.text); |
| - listItem.setAttribute("role", "section"); |
| - |
| - let tooltip = listItem.querySelector("[data-tooltip]"); |
| - if (tooltip) |
| + switch (action) |
| + { |
| + case "add-domain-exception": |
| + addWhitelistedDomain(); |
| + break; |
| + case "add-language-subscription": |
| + addEnableSubscription(findParentData(element, "access", false)); |
| + break; |
| + case "add-predefined-subscription": { |
| + let dialog = E("dialog-content-predefined"); |
| + let title = dialog.querySelector("h3").textContent; |
| + let url = dialog.querySelector(".url").textContent; |
| + addEnableSubscription(url, title); |
| + closeDialog(); |
| + break; |
| + } |
| + case "cancel-custom-filters": |
| + setCustomFiltersView("read"); |
| + break; |
| + case "change-language-subscription": |
| + for (let key in subscriptionsMap) |
| { |
| - let tooltipId = tooltip.getAttribute("data-tooltip"); |
| - tooltipId = tooltipId.replace("%value%", item.recommended); |
| - if (getMessage(tooltipId)) |
| + let subscription = subscriptionsMap[key]; |
| + let subscriptionType = subscription.recommended; |
| + if (subscriptionType == "ads" && subscription.disabled == false) |
| { |
| - tooltip.setAttribute("data-tooltip", tooltipId); |
| + browser.runtime.sendMessage({ |
| + type: "subscriptions.remove", |
| + url: subscription.url |
| + }); |
| + browser.runtime.sendMessage({ |
| + type: "subscriptions.add", |
| + url: findParentData(element, "access", false) |
| + }); |
| + break; |
| } |
| } |
| - |
| - for (let control of listItem.querySelectorAll(".control")) |
| - { |
| - if (control.hasAttribute("title")) |
| - { |
| - let titleValue = getMessage(control.getAttribute("title")); |
| - control.setAttribute("title", titleValue); |
| - } |
| - } |
| - |
| - this._setEmpty(table, detail, true); |
| - if (table.children.length > 0) |
| - table.insertBefore(listItem, table.children[this.items.indexOf(item)]); |
| - else |
| - table.appendChild(listItem); |
| - |
| - this.updateItem(item); |
| + break; |
| + case "close-dialog": |
| + closeDialog(); |
| + break; |
| + case "edit-custom-filters": |
| + setCustomFiltersView("write"); |
| + break; |
| + case "hide-more-filters-section": |
| + E("more-filters").setAttribute("aria-hidden", true); |
| + break; |
| + case "hide-notification": |
| + hideNotification(); |
| + break; |
| + case "import-subscription": { |
| + let url = E("blockingList-textbox").value; |
| + addEnableSubscription(url); |
| + closeDialog(); |
| + break; |
| + } |
| + case "open-context-menu": { |
| + let listItem = findParentData(element, "access", true); |
| + if (listItem && !listItem.classList.contains("show-context-menu")) |
| + listItem.classList.add("show-context-menu"); |
| + break; |
| } |
| - return length; |
| - }; |
| - |
| - Collection.prototype.removeItem = function(item) |
| - { |
| - let index = this.items.indexOf(item); |
| - if (index == -1) |
| - return; |
| - |
| - this.items.splice(index, 1); |
| - let getListElement = this._createElementQuery(item); |
| - for (let detail of this.details) |
| - { |
| - let table = E(detail.id); |
| - let element = getListElement(table); |
| - |
| - // Element gets removed so make sure to handle focus appropriately |
| - let control = element.querySelector(".control"); |
| - if (control && control == document.activeElement) |
| + case "open-dialog": { |
| + let dialog = findParentData(element, "dialog", false); |
| + openDialog(dialog); |
| + break; |
| + } |
| + case "remove-filter": |
| + browser.runtime.sendMessage({ |
| + type: "filters.remove", |
| + text: findParentData(element, "access", false) |
| + }); |
| + break; |
| + case "remove-subscription": |
| + browser.runtime.sendMessage({ |
| + type: "subscriptions.remove", |
| + url: findParentData(element, "access", false) |
| + }); |
| + break; |
| + case "save-custom-filters": |
| + sendMessageHandleErrors({ |
| + type: "filters.importRaw", |
| + text: E("custom-filters-raw").value, |
| + removeExisting: true |
| + }, |
| + () => |
| { |
| - if (!focusNextElement(element.parentElement, control)) |
| - { |
| - // Fall back to next focusable element within same tab or dialog |
| - let focusableElement = element.parentElement; |
| - while (focusableElement) |
| - { |
| - if (focusableElement.classList.contains("tab-content") || |
| - focusableElement.classList.contains("dialog-content")) |
| - break; |
| - |
| - focusableElement = focusableElement.parentElement; |
| - } |
| - focusNextElement(focusableElement || document, control); |
| - } |
| + setCustomFiltersView("read"); |
| + }); |
| + break; |
| + case "show-more-filters-section": |
| + E("more-filters").setAttribute("aria-hidden", false); |
| + break; |
| + case "switch-acceptable-ads": |
| + let value = element.value || element.dataset.value; |
| + // User check the checkbox |
| + let shouldCheck = element.getAttribute("aria-checked") != "true"; |
| + let installAcceptableAds = false; |
| + let installAcceptableAdsPrivacy = false; |
| + // Acceptable Ads checkbox clicked |
| + if (value == "ads") |
| + { |
| + installAcceptableAds = shouldCheck; |
| } |
| - |
| - element.parentElement.removeChild(element); |
| - if (this.items.length == 0) |
| - this._setEmpty(table, detail); |
| - } |
| - }; |
| - |
| - Collection.prototype.updateItem = function(item) |
| - { |
| - let oldIndex = this.items.indexOf(item); |
| - this._sortItems(); |
| - let access = (item.url || item.text).replace(/'/g, "\\'"); |
| - for (let i = 0; i < this.details.length; i++) |
| - { |
| - let table = E(this.details[i].id); |
| - let element = table.querySelector("[data-access='" + access + "']"); |
| - if (!element) |
| - continue; |
| - |
| - let title = this._getItemTitle(item, i); |
| - let displays = element.querySelectorAll("[data-display]"); |
| - for (let j = 0; j < displays.length; j++) |
| + // Privacy Friendly Acceptable Ads checkbox clicked |
| + else |
| { |
| - if (item[displays[j].dataset.display]) |
| - displays[j].textContent = item[displays[j].dataset.display]; |
| - else |
| - displays[j].textContent = title; |
| + installAcceptableAdsPrivacy = shouldCheck; |
| + installAcceptableAds = !shouldCheck; |
| } |
| - element.setAttribute("aria-label", title); |
| - if (this.details[i].searchable) |
| - element.setAttribute("data-search", title.toLowerCase()); |
| - let controls = element.querySelectorAll(".control[role='checkbox']"); |
| - for (let control of controls) |
| - { |
| - control.setAttribute("aria-checked", item.disabled == false); |
| - if (isAcceptableAds(item.url) && this == collections.filterLists) |
| - control.disabled = true; |
| - } |
| - |
| - let lastUpdateElement = element.querySelector(".last-update"); |
| - if (lastUpdateElement) |
| + browser.runtime.sendMessage({ |
| + type: installAcceptableAds ? "subscriptions.add" : |
| + "subscriptions.remove", |
| + url: acceptableAdsUrl |
| + }); |
| + browser.runtime.sendMessage({ |
| + type: installAcceptableAdsPrivacy ? "subscriptions.add" : |
| + "subscriptions.remove", |
| + url: acceptableAdsPrivacyUrl |
| + }); |
| + break; |
| + case "switch-tab": |
| + switchTab(element.getAttribute("href").substr(1)); |
| + break; |
| + case "toggle-disable-subscription": |
| + browser.runtime.sendMessage({ |
| + type: "subscriptions.toggle", |
| + keepInstalled: true, |
| + url: findParentData(element, "access", false) |
| + }); |
| + break; |
| + case "toggle-pref": |
| + browser.runtime.sendMessage({ |
| + type: "prefs.toggle", |
| + key: findParentData(element, "pref", false) |
| + }); |
| + break; |
| + case "toggle-remove-subscription": |
| + let subscriptionUrl = findParentData(element, "access", false); |
| + if (element.getAttribute("aria-checked") == "true") |
| { |
| - let message = element.querySelector(".message"); |
| - if (item.isDownloading) |
| - { |
| - let text = getMessage("options_filterList_lastDownload_inProgress"); |
| - message.textContent = text; |
| - element.classList.add("show-message"); |
| - } |
| - else if (item.downloadStatus != "synchronize_ok") |
| - { |
| - let error = filterErrors.get(item.downloadStatus); |
| - if (error) |
| - message.textContent = getMessage(error); |
| - else |
| - message.textContent = item.downloadStatus; |
| - element.classList.add("show-message"); |
| - } |
| - else if (item.lastDownload > 0) |
| - { |
| - let lastUpdate = item.lastDownload * 1000; |
| - let sinceUpdate = Date.now() - lastUpdate; |
| - if (sinceUpdate > fullDayInMs) |
| - { |
| - let lastUpdateDate = new Date(item.lastDownload * 1000); |
| - let monthName = lastUpdateDate.toLocaleString(undefined, |
| - {month: "short"}); |
| - let day = lastUpdateDate.getDate(); |
| - day = day < 10 ? "0" + day : day; |
| - lastUpdateElement.textContent = day + " " + monthName + " " + |
| - lastUpdateDate.getFullYear(); |
| - } |
| - else if (sinceUpdate > hourInMs) |
| - { |
| - lastUpdateElement.textContent = |
| - getMessage("options_filterList_hours"); |
| - } |
| - else if (sinceUpdate > minuteInMs) |
| - { |
| - lastUpdateElement.textContent = |
| - getMessage("options_filterList_minutes"); |
| - } |
| - else |
| - { |
| - lastUpdateElement.textContent = |
| - getMessage("options_filterList_now"); |
| - } |
| - element.classList.remove("show-message"); |
| - } |
| + browser.runtime.sendMessage({ |
| + type: "subscriptions.remove", |
| + url: subscriptionUrl |
| + }); |
| } |
| - |
| - let websiteElement = element.querySelector(".context-menu .website"); |
| - if (websiteElement) |
| - { |
| - if (item.homepage) |
| - websiteElement.setAttribute("href", item.homepage); |
| - else |
| - websiteElement.setAttribute("aria-hidden", true); |
| - } |
| - |
| - let sourceElement = element.querySelector(".context-menu .source"); |
| - if (sourceElement) |
| - sourceElement.setAttribute("href", item.url); |
| - |
| - let newIndex = this.items.indexOf(item); |
| - if (oldIndex != newIndex) |
| - table.insertBefore(element, table.childNodes[newIndex]); |
| - } |
| - }; |
| + else |
| + addEnableSubscription(subscriptionUrl); |
| + break; |
| + case "update-all-subscriptions": |
| + browser.runtime.sendMessage({ |
| + type: "subscriptions.update" |
| + }); |
| + break; |
| + case "update-subscription": |
| + browser.runtime.sendMessage({ |
| + type: "subscriptions.update", |
| + url: findParentData(element, "access", false) |
| + }); |
| + break; |
| + case "validate-import-subscription": |
| + let form = findParentData(element, "validation", true); |
| + if (!form) |
| + return; |
| - Collection.prototype.clearAll = function() |
| - { |
| - this.items = []; |
| - for (let detail of this.details) |
| - { |
| - let table = E(detail.id); |
| - let element = table.firstChild; |
| - while (element) |
| + if (form.checkValidity()) |
| { |
| - if (element.tagName == "LI" && !element.classList.contains("static")) |
| - table.removeChild(element); |
| - element = element.nextElementSibling; |
| - } |
| - |
| - this._setEmpty(table, detail); |
| - } |
| - }; |
| - |
| - function focusNextElement(container, currentElement) |
| - { |
| - let focusables = container.querySelectorAll("a, button, input, .control"); |
| - focusables = Array.prototype.slice.call(focusables); |
| - let index = focusables.indexOf(currentElement); |
| - index += (index == focusables.length - 1) ? -1 : 1; |
| - |
| - let nextElement = focusables[index]; |
| - if (!nextElement) |
| - return false; |
| - |
| - nextElement.focus(); |
| - return true; |
| - } |
| - |
| - collections.protection = new Collection([ |
| - { |
| - id: "recommend-protection-list-table" |
| - } |
| - ]); |
| - collections.langs = new Collection([ |
| - { |
| - id: "blocking-languages-table", |
| - emptyTexts: ["options_language_empty"], |
| - getTitleFunction: getLanguageTitle |
| - } |
| - ]); |
| - collections.allLangs = new Collection([ |
| - { |
| - id: "all-lang-table-add", |
| - emptyTexts: ["options_dialog_language_other_empty"], |
| - getTitleFunction: getLanguageTitle |
| - } |
| - ]); |
| - collections.more = new Collection([ |
| - { |
| - id: "more-list-table", |
| - setEmptyAction: "hide-more-filters-section", |
| - removeEmptyAction: "show-more-filters-section" |
| - } |
| - ]); |
| - collections.whitelist = new Collection([ |
| - { |
| - id: "whitelisting-table", |
| - emptyTexts: ["options_whitelist_empty_1", "options_whitelist_empty_2"] |
| - } |
| - ]); |
| - collections.filterLists = new Collection([ |
| - { |
| - id: "all-filter-lists-table", |
| - emptyTexts: ["options_filterList_empty"] |
| - } |
| - ]); |
| - |
| - function addSubscription(subscription) |
| - { |
| - let {disabled} = subscription; |
| - let collection = null; |
| - if (subscription.recommended) |
| - { |
| - if (subscription.recommended == "ads") |
| - { |
| - if (disabled == false) |
| - collection = collections.langs; |
| - |
| - collections.allLangs.addItem(subscription); |
| + addEnableSubscription(E("import-list-url").value, |
| + E("import-list-title").value); |
| + form.reset(); |
| + closeDialog(); |
| } |
| else |
| { |
| - collection = collections.protection; |
| + form.querySelector(":invalid").focus(); |
| } |
| - } |
| - else if (!isAcceptableAds(subscription.url) && disabled == false) |
| - { |
| - collection = collections.more; |
| - } |
| + break; |
| + } |
| +} |
| - if (collection) |
| - collection.addItem(subscription); |
| - |
| - subscriptionsMap[subscription.url] = subscription; |
| - updateTooltips(); |
| - } |
| - |
| - function updateSubscription(subscription) |
| +function setCustomFiltersView(mode) |
| +{ |
| + let customFiltersElement = E("custom-filters-raw"); |
| + updateCustomFiltersUi(); |
| + if (mode == "read") |
| { |
| - for (let name in collections) |
| - collections[name].updateItem(subscription); |
| - |
| - if (subscription.recommended == "ads") |
| + customFiltersElement.disabled = true; |
| + if (!customFiltersElement.value) |
| { |
| - if (subscription.disabled) |
| - collections.langs.removeItem(subscription); |
| - else |
| - collections.langs.addItem(subscription); |
| - } |
| - else if (!subscription.recommended && !isAcceptableAds(subscription.url)) |
| - { |
| - if (subscription.disabled == false) |
| - { |
| - collections.more.addItem(subscription); |
| - updateTooltips(); |
| - } |
| - else |
| - { |
| - collections.more.removeItem(subscription); |
| - } |
| + setCustomFiltersView("empty"); |
| + return; |
| } |
| } |
| - |
| - function updateFilter(filter) |
| + else if (mode == "write") |
| { |
| - let match = filter.text.match(whitelistedDomainRegexp); |
| - if (match && !filtersMap[filter.text]) |
| - { |
| - filter.title = match[1]; |
| - collections.whitelist.addItem(filter); |
| - if (isCustomFiltersLoaded) |
| - { |
| - let text = getMessage("options_whitelist_notification", [filter.title]); |
| - showNotification(text); |
| - } |
| - } |
| - else |
| - { |
| - customFilters.push(filter.text); |
| - if (isCustomFiltersLoaded) |
| - updateCustomFiltersUi(); |
| - } |
| - |
| - filtersMap[filter.text] = filter; |
| - } |
| - |
| - function loadCustomFilters(filters) |
| - { |
| - for (let filter of filters) |
| - updateFilter(filter); |
| - |
| - setCustomFiltersView("read"); |
| - isCustomFiltersLoaded = true; |
| + customFiltersElement.disabled = false; |
| } |
| - function removeCustomFilter(text) |
| - { |
| - let index = customFilters.indexOf(text); |
| - if (index >= 0) |
| - customFilters.splice(index, 1); |
| - |
| - updateCustomFiltersUi(); |
| - } |
| + E("custom-filters").dataset.mode = mode; |
| +} |
| - function updateCustomFiltersUi() |
| - { |
| - let customFiltersListElement = E("custom-filters-raw"); |
| - customFiltersListElement.value = customFilters.join("\n"); |
| - } |
| +function onClick(e) |
| +{ |
| + let context = document.querySelector(".show-context-menu"); |
| + if (context) |
| + context.classList.remove("show-context-menu"); |
| - function getLanguageTitle(item) |
| - { |
| - let title = item.specialization; |
| - if (item.originalTitle && item.originalTitle.indexOf("+EasyList") > -1) |
| - title += " + " + getMessage("options_english"); |
| - return title; |
| - } |
| + let actions = findParentData(e.target, "action", false); |
| + if (!actions) |
| + return; |
| - function loadRecommendations() |
| + actions = actions.split(","); |
| + for (let action of actions) |
| { |
| - fetch("subscriptions.xml") |
| - .then((response) => |
| - { |
| - return response.text(); |
| - }) |
| - .then((text) => |
| - { |
| - let doc = new DOMParser().parseFromString(text, "application/xml"); |
| - let elements = doc.documentElement.getElementsByTagName("subscription"); |
| - for (let element of elements) |
| - { |
| - let type = element.getAttribute("type"); |
| - let subscription = { |
| - disabled: true, |
| - downloadStatus: null, |
| - homepage: null, |
| - specialization: element.getAttribute("specialization"), |
| - originalTitle: element.getAttribute("title"), |
| - recommended: type, |
| - url: element.getAttribute("url") |
| - }; |
| + execAction(action, e.target); |
| + } |
| +} |
| - if (subscription.recommended != "ads") |
| - { |
| - type = type.replace(/\W/g, "_"); |
| - subscription.title = getMessage("common_feature_" + |
| - type + "_title"); |
| - } |
| +function getKey(e) |
| +{ |
| + // e.keyCode has been deprecated so we attempt to use e.key |
| + if ("key" in e) |
| + return e.key; |
| + return getKey.keys[e.keyCode]; |
| +} |
| +getKey.keys = { |
| + 9: "Tab", |
| + 13: "Enter", |
| + 27: "Escape", |
| + 37: "ArrowLeft", |
| + 38: "ArrowUp", |
| + 39: "ArrowRight", |
| + 40: "ArrowDown" |
| +}; |
| - addSubscription(subscription); |
| - } |
| - }); |
| - } |
| +function onKeyUp(e) |
| +{ |
| + let key = getKey(e); |
| + let element = document.activeElement; |
| + if (!key || !element) |
| + return; |
| - function findParentData(element, dataName, returnElement) |
| - { |
| - element = element.closest(`[data-${dataName}]`); |
| - if (!element) |
| - return null; |
| - if (returnElement) |
| - return element; |
| - return element.getAttribute(`data-${dataName}`); |
| - } |
| + let container = findParentData(element, "action", true); |
| + if (!container || !container.hasAttribute("data-keys")) |
| + return; |
| - function sendMessageHandleErrors(message, onSuccess) |
| + let keys = container.getAttribute("data-keys").split(" "); |
| + if (keys.indexOf(key) < 0) |
| + return; |
| + |
| + if (element.getAttribute("role") == "tab") |
| { |
| - browser.runtime.sendMessage(message, (errors) => |
| - { |
| - if (errors.length > 0) |
| - alert(errors.join("\n")); |
| - else if (onSuccess) |
| - onSuccess(); |
| - }); |
| - } |
| - |
| - function switchTab(id) |
| - { |
| - location.hash = id; |
| + let parent = element.parentElement; |
| + if (key == "ArrowLeft" || key == "ArrowUp") |
| + parent = parent.previousElementSibling || container.lastElementChild; |
| + else if (key == "ArrowRight" || key == "ArrowDown") |
| + parent = parent.nextElementSibling || container.firstElementChild; |
| + element = parent.firstElementChild; |
| } |
| - function execAction(action, element) |
| + let actions = container.getAttribute("data-action").split(","); |
| + for (let action of actions) |
| + { |
| + execAction(action, element); |
| + } |
| +} |
| + |
| +function selectTabItem(tabId, container, focus) |
| +{ |
| + // Show tab content |
| + document.body.setAttribute("data-tab", tabId); |
| + |
| + // Select tab |
| + let tabList = container.querySelector("[role='tablist']"); |
| + if (!tabList) |
| + return null; |
| + |
| + let previousTab = tabList.querySelector("[aria-selected]"); |
| + previousTab.removeAttribute("aria-selected"); |
| + previousTab.setAttribute("tabindex", -1); |
| + |
| + let tab = tabList.querySelector("a[href='#" + tabId + "']"); |
| + tab.setAttribute("aria-selected", true); |
| + tab.setAttribute("tabindex", 0); |
| + |
| + let tabContentId = tab.getAttribute("aria-controls"); |
| + let tabContent = document.getElementById(tabContentId); |
| + |
| + if (tab && focus) |
| + tab.focus(); |
| + |
| + return tabContent; |
| +} |
| + |
| +function onHashChange() |
| +{ |
| + let hash = location.hash.substr(1); |
| + if (!hash) |
| + return; |
| + |
| + // Select tab and parent tabs |
| + let tabIds = hash.split("-"); |
| + let tabContent = document.body; |
| + for (let i = 0; i < tabIds.length; i++) |
| { |
| - if (element.getAttribute("aria-disabled") == "true") |
| - return; |
| + let tabId = tabIds.slice(0, i + 1).join("-"); |
| + tabContent = selectTabItem(tabId, tabContent, true); |
| + if (!tabContent) |
| + break; |
| + } |
| +} |
| + |
| +function onDOMLoaded() |
| +{ |
| + populateLists(); |
| + |
| + // Initialize navigation sidebar |
| + browser.runtime.sendMessage({ |
| + type: "app.get", |
| + what: "addonVersion" |
| + }, |
| + (addonVersion) => |
| + { |
| + E("abp-version").textContent = getMessage("options_dialog_about_version", |
| + [addonVersion]); |
| + }); |
| + |
| + updateTooltips(); |
| + |
| + // Initialize interactive UI elements |
| + document.body.addEventListener("click", onClick, false); |
| + document.body.addEventListener("keyup", onKeyUp, false); |
| + let exampleValue = getMessage("options_whitelist_placeholder_example", |
| + ["www.example.com"]); |
| + E("whitelisting-textbox").setAttribute("placeholder", exampleValue); |
| + E("whitelisting-textbox").addEventListener("keyup", (e) => |
| + { |
| + E("whitelisting-add-button").disabled = !e.target.value; |
| + }, false); |
| - switch (action) |
| + // General tab |
| + getDocLink("contribute", (link) => |
| + { |
| + E("contribute").href = link; |
| + }); |
| + getDocLink("acceptable_ads_criteria", (link) => |
| + { |
| + setLinks("enable-acceptable-ads-description", link); |
| + }); |
| + setElementText(E("tracking-warning-1"), "options_tracking_warning_1", |
| + [getMessage("common_feature_privacy_title"), |
| + getMessage("options_acceptableAds_ads_label")]); |
| + setElementText(E("tracking-warning-3"), "options_tracking_warning_3", |
| + [getMessage("options_acceptableAds_privacy_label")]); |
| + |
| + getDocLink("privacy_friendly_ads", (link) => |
| + { |
| + E("enable-acceptable-ads-privacy-description").href = link; |
| + }); |
| + getDocLink("adblock_plus_{browser}_dnt", url => |
| + { |
| + setLinks("dnt", url); |
| + }); |
| + |
| + // Whitelisted tab |
| + getDocLink("whitelist", (link) => |
| + { |
| + E("whitelist-learn-more").href = link; |
| + }); |
| + |
| + // Advanced tab |
| + let customize = document.querySelectorAll("#customize li[data-pref]"); |
| + customize = Array.prototype.map.call(customize, (checkbox) => |
| + { |
| + return checkbox.getAttribute("data-pref"); |
| + }); |
| + for (let key of customize) |
| + { |
| + getPref(key, (value) => |
| { |
| - case "add-domain-exception": |
| - addWhitelistedDomain(); |
| - break; |
| - case "add-language-subscription": |
| - addEnableSubscription(findParentData(element, "access", false)); |
| - break; |
| - case "add-predefined-subscription": { |
| - let dialog = E("dialog-content-predefined"); |
| - let title = dialog.querySelector("h3").textContent; |
| - let url = dialog.querySelector(".url").textContent; |
| - addEnableSubscription(url, title); |
| - closeDialog(); |
| - break; |
| - } |
| - case "cancel-custom-filters": |
| - setCustomFiltersView("read"); |
| - break; |
| - case "change-language-subscription": |
| - for (let key in subscriptionsMap) |
| - { |
| - let subscription = subscriptionsMap[key]; |
| - let subscriptionType = subscription.recommended; |
| - if (subscriptionType == "ads" && subscription.disabled == false) |
| - { |
| - browser.runtime.sendMessage({ |
| - type: "subscriptions.remove", |
| - url: subscription.url |
| - }); |
| - browser.runtime.sendMessage({ |
| - type: "subscriptions.add", |
| - url: findParentData(element, "access", false) |
| - }); |
| - break; |
| - } |
| - } |
| - break; |
| - case "close-dialog": |
| - closeDialog(); |
| - break; |
| - case "edit-custom-filters": |
| - setCustomFiltersView("write"); |
| - break; |
| - case "hide-more-filters-section": |
| - E("more-filters").setAttribute("aria-hidden", true); |
| - break; |
| - case "hide-notification": |
| - hideNotification(); |
| - break; |
| - case "import-subscription": { |
| - let url = E("blockingList-textbox").value; |
| - addEnableSubscription(url); |
| + onPrefMessage(key, value, true); |
| + }); |
| + } |
| + browser.runtime.sendMessage({ |
| + type: "app.get", |
| + what: "features" |
| + }, |
| + (features) => |
| + { |
| + hidePref("show_devtools_panel", !features.devToolsPanel); |
| + }); |
| + |
| + getDocLink("filterdoc", (link) => |
| + { |
| + E("link-filters").setAttribute("href", link); |
| + }); |
| + |
| + getDocLink("subscriptions", (link) => |
| + { |
| + E("filter-lists-learn-more").setAttribute("href", link); |
| + }); |
| + |
| + E("custom-filters-raw").setAttribute("placeholder", |
| + getMessage("options_customFilters_edit_placeholder", ["/ads/track/*"])); |
| + |
| + // Help tab |
| + getDocLink("adblock_plus_report_bug", (link) => |
| + { |
| + setLinks("report-bug", link); |
| + }); |
| + getDocLink("{browser}_support", url => |
| + { |
| + setLinks("visit-forum", url); |
| + }); |
| + getDocLink("social_twitter", (link) => |
| + { |
| + E("twitter").setAttribute("href", link); |
| + }); |
| + getDocLink("social_facebook", (link) => |
| + { |
| + E("facebook").setAttribute("href", link); |
| + }); |
| + getDocLink("social_gplus", (link) => |
| + { |
| + E("google-plus").setAttribute("href", link); |
| + }); |
| + getDocLink("social_weibo", (link) => |
| + { |
| + E("weibo").setAttribute("href", link); |
| + }); |
| + |
| + E("dialog").addEventListener("keydown", function(e) |
| + { |
| + switch (getKey(e)) |
| + { |
| + case "Escape": |
| closeDialog(); |
| break; |
| - } |
| - case "open-context-menu": { |
| - let listItem = findParentData(element, "access", true); |
| - if (listItem && !listItem.classList.contains("show-context-menu")) |
| - listItem.classList.add("show-context-menu"); |
| - break; |
| - } |
| - case "open-dialog": { |
| - let dialog = findParentData(element, "dialog", false); |
| - openDialog(dialog); |
| - break; |
| - } |
| - case "remove-filter": |
| - browser.runtime.sendMessage({ |
| - type: "filters.remove", |
| - text: findParentData(element, "access", false) |
| - }); |
| - break; |
| - case "remove-subscription": |
| - browser.runtime.sendMessage({ |
| - type: "subscriptions.remove", |
| - url: findParentData(element, "access", false) |
| - }); |
| - break; |
| - case "save-custom-filters": |
| - sendMessageHandleErrors({ |
| - type: "filters.importRaw", |
| - text: E("custom-filters-raw").value, |
| - removeExisting: true |
| - }, |
| - () => |
| + case "Tab": |
| + if (e.shiftKey) |
| { |
| - setCustomFiltersView("read"); |
| - }); |
| - break; |
| - case "show-more-filters-section": |
| - E("more-filters").setAttribute("aria-hidden", false); |
| - break; |
| - case "switch-acceptable-ads": |
| - let value = element.value || element.dataset.value; |
| - // User check the checkbox |
| - let shouldCheck = element.getAttribute("aria-checked") != "true"; |
| - let installAcceptableAds = false; |
| - let installAcceptableAdsPrivacy = false; |
| - // Acceptable Ads checkbox clicked |
| - if (value == "ads") |
| - { |
| - installAcceptableAds = shouldCheck; |
| - } |
| - // Privacy Friendly Acceptable Ads checkbox clicked |
| - else |
| - { |
| - installAcceptableAdsPrivacy = shouldCheck; |
| - installAcceptableAds = !shouldCheck; |
| + if (e.target.classList.contains("focus-first")) |
| + { |
| + e.preventDefault(); |
| + this.querySelector(".focus-last").focus(); |
| + } |
| } |
| - |
| - browser.runtime.sendMessage({ |
| - type: installAcceptableAds ? "subscriptions.add" : |
| - "subscriptions.remove", |
| - url: acceptableAdsUrl |
| - }); |
| - browser.runtime.sendMessage({ |
| - type: installAcceptableAdsPrivacy ? "subscriptions.add" : |
| - "subscriptions.remove", |
| - url: acceptableAdsPrivacyUrl |
| - }); |
| - break; |
| - case "switch-tab": |
| - switchTab(element.getAttribute("href").substr(1)); |
| - break; |
| - case "toggle-disable-subscription": |
| - browser.runtime.sendMessage({ |
| - type: "subscriptions.toggle", |
| - keepInstalled: true, |
| - url: findParentData(element, "access", false) |
| - }); |
| - break; |
| - case "toggle-pref": |
| - browser.runtime.sendMessage({ |
| - type: "prefs.toggle", |
| - key: findParentData(element, "pref", false) |
| - }); |
| - break; |
| - case "toggle-remove-subscription": |
| - let subscriptionUrl = findParentData(element, "access", false); |
| - if (element.getAttribute("aria-checked") == "true") |
| + else if (e.target.classList.contains("focus-last")) |
| { |
| - browser.runtime.sendMessage({ |
| - type: "subscriptions.remove", |
| - url: subscriptionUrl |
| - }); |
| - } |
| - else |
| - addEnableSubscription(subscriptionUrl); |
| - break; |
| - case "update-all-subscriptions": |
| - browser.runtime.sendMessage({ |
| - type: "subscriptions.update" |
| - }); |
| - break; |
| - case "update-subscription": |
| - browser.runtime.sendMessage({ |
| - type: "subscriptions.update", |
| - url: findParentData(element, "access", false) |
| - }); |
| - break; |
| - case "validate-import-subscription": |
| - let form = findParentData(element, "validation", true); |
| - if (!form) |
| - return; |
| - |
| - if (form.checkValidity()) |
| - { |
| - addEnableSubscription(E("import-list-url").value, |
| - E("import-list-title").value); |
| - form.reset(); |
| - closeDialog(); |
| - } |
| - else |
| - { |
| - form.querySelector(":invalid").focus(); |
| + e.preventDefault(); |
| + this.querySelector(".focus-first").focus(); |
| } |
| break; |
| } |
| - } |
| + }, false); |
| + |
| + onHashChange(); |
| +} |
| - function setCustomFiltersView(mode) |
| - { |
| - let customFiltersElement = E("custom-filters-raw"); |
| - updateCustomFiltersUi(); |
| - if (mode == "read") |
| - { |
| - customFiltersElement.disabled = true; |
| - if (!customFiltersElement.value) |
| - { |
| - setCustomFiltersView("empty"); |
| - return; |
| - } |
| - } |
| - else if (mode == "write") |
| - { |
| - customFiltersElement.disabled = false; |
| - } |
| +let focusedBeforeDialog = null; |
| +function openDialog(name) |
| +{ |
| + let dialog = E("dialog"); |
| + dialog.setAttribute("aria-hidden", false); |
| + dialog.setAttribute("aria-labelledby", "dialog-title-" + name); |
| + document.body.setAttribute("data-dialog", name); |
| + |
| + let defaultFocus = document.querySelector( |
| + "#dialog-content-" + name + " .default-focus" |
| + ); |
| + if (!defaultFocus) |
| + defaultFocus = dialog.querySelector(".focus-first"); |
| + focusedBeforeDialog = document.activeElement; |
| + defaultFocus.focus(); |
| +} |
| - E("custom-filters").dataset.mode = mode; |
| - } |
| +function closeDialog() |
| +{ |
| + let dialog = E("dialog"); |
| + dialog.setAttribute("aria-hidden", true); |
| + dialog.removeAttribute("aria-labelledby"); |
| + document.body.removeAttribute("data-dialog"); |
| + focusedBeforeDialog.focus(); |
| +} |
| - function onClick(e) |
| - { |
| - let context = document.querySelector(".show-context-menu"); |
| - if (context) |
| - context.classList.remove("show-context-menu"); |
| +function showNotification(text) |
| +{ |
| + E("notification").setAttribute("aria-hidden", false); |
| + E("notification-text").textContent = text; |
| + setTimeout(hideNotification, 3000); |
| +} |
| - let actions = findParentData(e.target, "action", false); |
| - if (!actions) |
| - return; |
| +function hideNotification() |
| +{ |
| + E("notification").setAttribute("aria-hidden", true); |
| + E("notification-text").textContent = ""; |
| +} |
| - actions = actions.split(","); |
| - for (let action of actions) |
| - { |
| - execAction(action, e.target); |
| - } |
| - } |
| - |
| - function getKey(e) |
| +function setAcceptableAds() |
| +{ |
| + let acceptableAdsForm = E("acceptable-ads"); |
| + let acceptableAds = E("acceptable-ads-allow"); |
| + let acceptableAdsPrivacy = E("acceptable-ads-privacy-allow"); |
| + acceptableAdsForm.classList.remove("show-dnt-notification"); |
| + acceptableAds.setAttribute("aria-checked", false); |
| + acceptableAdsPrivacy.setAttribute("aria-checked", false); |
| + acceptableAdsPrivacy.setAttribute("tabindex", 0); |
| + if (acceptableAdsUrl in subscriptionsMap) |
| { |
| - // e.keyCode has been deprecated so we attempt to use e.key |
| - if ("key" in e) |
| - return e.key; |
| - return getKey.keys[e.keyCode]; |
| + acceptableAds.setAttribute("aria-checked", true); |
| + acceptableAdsPrivacy.setAttribute("aria-disabled", false); |
| } |
| - getKey.keys = { |
| - 9: "Tab", |
| - 13: "Enter", |
| - 27: "Escape", |
| - 37: "ArrowLeft", |
| - 38: "ArrowUp", |
| - 39: "ArrowRight", |
| - 40: "ArrowDown" |
| - }; |
| - |
| - function onKeyUp(e) |
| + else if (acceptableAdsPrivacyUrl in subscriptionsMap) |
| { |
| - let key = getKey(e); |
| - let element = document.activeElement; |
| - if (!key || !element) |
| - return; |
| + acceptableAds.setAttribute("aria-checked", true); |
| + acceptableAdsPrivacy.setAttribute("aria-checked", true); |
| + acceptableAdsPrivacy.setAttribute("aria-disabled", false); |
| - let container = findParentData(element, "action", true); |
| - if (!container || !container.hasAttribute("data-keys")) |
| - return; |
| + // Edge uses window instead of navigator. |
| + // Prefer navigator first since it's the standard. |
| + if ((navigator.doNotTrack || window.doNotTrack) != 1) |
| + acceptableAdsForm.classList.add("show-dnt-notification"); |
| + } |
| + else |
| + { |
| + // Using aria-disabled in order to keep the focus |
| + acceptableAdsPrivacy.setAttribute("aria-disabled", true); |
| + acceptableAdsPrivacy.setAttribute("tabindex", -1); |
| + } |
| +} |
| - let keys = container.getAttribute("data-keys").split(" "); |
| - if (keys.indexOf(key) < 0) |
| - return; |
| +function isAcceptableAds(url) |
| +{ |
| + return url == acceptableAdsUrl || url == acceptableAdsPrivacyUrl; |
| +} |
| - if (element.getAttribute("role") == "tab") |
| +function hasPrivacyConflict() |
| +{ |
| + let acceptableAdsList = subscriptionsMap[acceptableAdsUrl]; |
| + let privacyList = null; |
| + for (let url in subscriptionsMap) |
| + { |
| + let subscription = subscriptionsMap[url]; |
| + if (subscription.recommended == "privacy") |
| { |
| - let parent = element.parentElement; |
| - if (key == "ArrowLeft" || key == "ArrowUp") |
| - parent = parent.previousElementSibling || container.lastElementChild; |
| - else if (key == "ArrowRight" || key == "ArrowDown") |
| - parent = parent.nextElementSibling || container.firstElementChild; |
| - element = parent.firstElementChild; |
| - } |
| - |
| - let actions = container.getAttribute("data-action").split(","); |
| - for (let action of actions) |
| - { |
| - execAction(action, element); |
| + privacyList = subscription; |
| + break; |
| } |
| } |
| - |
| - function selectTabItem(tabId, container, focus) |
| - { |
| - // Show tab content |
| - document.body.setAttribute("data-tab", tabId); |
| - |
| - // Select tab |
| - let tabList = container.querySelector("[role='tablist']"); |
| - if (!tabList) |
| - return null; |
| - |
| - let previousTab = tabList.querySelector("[aria-selected]"); |
| - previousTab.removeAttribute("aria-selected"); |
| - previousTab.setAttribute("tabindex", -1); |
| - |
| - let tab = tabList.querySelector("a[href='#" + tabId + "']"); |
| - tab.setAttribute("aria-selected", true); |
| - tab.setAttribute("tabindex", 0); |
| - |
| - let tabContentId = tab.getAttribute("aria-controls"); |
| - let tabContent = document.getElementById(tabContentId); |
| - |
| - if (tab && focus) |
| - tab.focus(); |
| - |
| - return tabContent; |
| - } |
| + return acceptableAdsList && acceptableAdsList.disabled == false && |
| + privacyList && privacyList.disabled == false; |
| +} |
| - function onHashChange() |
| +function setPrivacyConflict() |
| +{ |
| + let acceptableAdsForm = E("acceptable-ads"); |
| + if (hasPrivacyConflict()) |
| { |
| - let hash = location.hash.substr(1); |
| - if (!hash) |
| - return; |
| - |
| - // Select tab and parent tabs |
| - let tabIds = hash.split("-"); |
| - let tabContent = document.body; |
| - for (let i = 0; i < tabIds.length; i++) |
| - { |
| - let tabId = tabIds.slice(0, i + 1).join("-"); |
| - tabContent = selectTabItem(tabId, tabContent, true); |
| - if (!tabContent) |
| - break; |
| - } |
| - } |
| - |
| - function onDOMLoaded() |
| - { |
| - populateLists(); |
| - |
| - // Initialize navigation sidebar |
| - browser.runtime.sendMessage({ |
| - type: "app.get", |
| - what: "addonVersion" |
| - }, |
| - (addonVersion) => |
| + getPref("ui_warn_tracking", (showTrackingWarning) => |
| { |
| - E("abp-version").textContent = getMessage("options_dialog_about_version", |
| - [addonVersion]); |
| - }); |
| - |
| - updateTooltips(); |
| - |
| - // Initialize interactive UI elements |
| - document.body.addEventListener("click", onClick, false); |
| - document.body.addEventListener("keyup", onKeyUp, false); |
| - let exampleValue = getMessage("options_whitelist_placeholder_example", |
| - ["www.example.com"]); |
| - E("whitelisting-textbox").setAttribute("placeholder", exampleValue); |
| - E("whitelisting-textbox").addEventListener("keyup", (e) => |
| - { |
| - E("whitelisting-add-button").disabled = !e.target.value; |
| - }, false); |
| - |
| - // General tab |
| - getDocLink("contribute", (link) => |
| - { |
| - E("contribute").href = link; |
| - }); |
| - getDocLink("acceptable_ads_criteria", (link) => |
| - { |
| - setLinks("enable-acceptable-ads-description", link); |
| + acceptableAdsForm.classList.toggle("show-warning", showTrackingWarning); |
| }); |
| - setElementText(E("tracking-warning-1"), "options_tracking_warning_1", |
| - [getMessage("common_feature_privacy_title"), |
| - getMessage("options_acceptableAds_ads_label")]); |
| - setElementText(E("tracking-warning-3"), "options_tracking_warning_3", |
| - [getMessage("options_acceptableAds_privacy_label")]); |
| + } |
| + else |
| + { |
| + acceptableAdsForm.classList.remove("show-warning"); |
| + } |
| +} |
| - getDocLink("privacy_friendly_ads", (link) => |
| - { |
| - E("enable-acceptable-ads-privacy-description").href = link; |
| - }); |
| - getDocLink("adblock_plus_{browser}_dnt", url => |
| - { |
| - setLinks("dnt", url); |
| - }); |
| +function populateLists() |
| +{ |
| + subscriptionsMap = Object.create(null); |
| + filtersMap = Object.create(null); |
| + |
| + // Empty collections and lists |
| + for (let property in collections) |
| + collections[property].clearAll(); |
| - // Whitelisted tab |
| - getDocLink("whitelist", (link) => |
| + setCustomFiltersView("empty"); |
| + browser.runtime.sendMessage({ |
| + type: "subscriptions.get", |
| + special: true |
| + }, |
| + (subscriptions) => |
| + { |
| + // Load filters |
| + for (let subscription of subscriptions) |
| { |
| - E("whitelist-learn-more").href = link; |
| - }); |
| - |
| - // Advanced tab |
| - let customize = document.querySelectorAll("#customize li[data-pref]"); |
| - customize = Array.prototype.map.call(customize, (checkbox) => |
| - { |
| - return checkbox.getAttribute("data-pref"); |
| - }); |
| - for (let key of customize) |
| - { |
| - getPref(key, (value) => |
| + browser.runtime.sendMessage({ |
| + type: "filters.get", |
| + subscriptionUrl: subscription.url |
| + }, |
| + (filters) => |
| { |
| - onPrefMessage(key, value, true); |
| + loadCustomFilters(filters); |
| }); |
| } |
| - browser.runtime.sendMessage({ |
| - type: "app.get", |
| - what: "features" |
| - }, |
| - (features) => |
| - { |
| - hidePref("show_devtools_panel", !features.devToolsPanel); |
| - }); |
| - |
| - getDocLink("filterdoc", (link) => |
| - { |
| - E("link-filters").setAttribute("href", link); |
| - }); |
| + }); |
| + loadRecommendations(); |
| + browser.runtime.sendMessage({ |
| + type: "prefs.get", |
| + key: "subscriptions_exceptionsurl" |
| + }, |
| + (url) => |
| + { |
| + acceptableAdsUrl = url; |
| - getDocLink("subscriptions", (link) => |
| - { |
| - E("filter-lists-learn-more").setAttribute("href", link); |
| - }); |
| - |
| - E("custom-filters-raw").setAttribute("placeholder", |
| - getMessage("options_customFilters_edit_placeholder", ["/ads/track/*"])); |
| - |
| - // Help tab |
| - getDocLink("adblock_plus_report_bug", (link) => |
| - { |
| - setLinks("report-bug", link); |
| - }); |
| - getDocLink("{browser}_support", url => |
| - { |
| - setLinks("visit-forum", url); |
| - }); |
| - getDocLink("social_twitter", (link) => |
| + browser.runtime.sendMessage({ |
| + type: "prefs.get", |
| + key: "subscriptions_exceptionsurl_privacy" |
| + }, |
| + (urlPrivacy) => |
| { |
| - E("twitter").setAttribute("href", link); |
| - }); |
| - getDocLink("social_facebook", (link) => |
| - { |
| - E("facebook").setAttribute("href", link); |
| - }); |
| - getDocLink("social_gplus", (link) => |
| - { |
| - E("google-plus").setAttribute("href", link); |
| - }); |
| - getDocLink("social_weibo", (link) => |
| - { |
| - E("weibo").setAttribute("href", link); |
| - }); |
| - |
| - E("dialog").addEventListener("keydown", function(e) |
| - { |
| - switch (getKey(e)) |
| - { |
| - case "Escape": |
| - closeDialog(); |
| - break; |
| - case "Tab": |
| - if (e.shiftKey) |
| - { |
| - if (e.target.classList.contains("focus-first")) |
| - { |
| - e.preventDefault(); |
| - this.querySelector(".focus-last").focus(); |
| - } |
| - } |
| - else if (e.target.classList.contains("focus-last")) |
| - { |
| - e.preventDefault(); |
| - this.querySelector(".focus-first").focus(); |
| - } |
| - break; |
| - } |
| - }, false); |
| + acceptableAdsPrivacyUrl = urlPrivacy; |
| - onHashChange(); |
| - } |
| - |
| - let focusedBeforeDialog = null; |
| - function openDialog(name) |
| - { |
| - let dialog = E("dialog"); |
| - dialog.setAttribute("aria-hidden", false); |
| - dialog.setAttribute("aria-labelledby", "dialog-title-" + name); |
| - document.body.setAttribute("data-dialog", name); |
| - |
| - let defaultFocus = document.querySelector( |
| - "#dialog-content-" + name + " .default-focus" |
| - ); |
| - if (!defaultFocus) |
| - defaultFocus = dialog.querySelector(".focus-first"); |
| - focusedBeforeDialog = document.activeElement; |
| - defaultFocus.focus(); |
| - } |
| - |
| - function closeDialog() |
| - { |
| - let dialog = E("dialog"); |
| - dialog.setAttribute("aria-hidden", true); |
| - dialog.removeAttribute("aria-labelledby"); |
| - document.body.removeAttribute("data-dialog"); |
| - focusedBeforeDialog.focus(); |
| - } |
| - |
| - function showNotification(text) |
| - { |
| - E("notification").setAttribute("aria-hidden", false); |
| - E("notification-text").textContent = text; |
| - setTimeout(hideNotification, 3000); |
| - } |
| + // Load user subscriptions |
| + browser.runtime.sendMessage({ |
| + type: "subscriptions.get", |
| + downloadable: true |
| + }, |
| + (subscriptions) => |
| + { |
| + for (let subscription of subscriptions) |
| + onSubscriptionMessage("added", subscription); |
| - function hideNotification() |
| - { |
| - E("notification").setAttribute("aria-hidden", true); |
| - E("notification-text").textContent = ""; |
| - } |
| + setAcceptableAds(); |
| + }); |
| + }); |
| + }); |
| +} |
| - function setAcceptableAds() |
| +function addWhitelistedDomain() |
| +{ |
| + let domain = E("whitelisting-textbox"); |
| + for (let whitelistItem of collections.whitelist.items) |
| { |
| - let acceptableAdsForm = E("acceptable-ads"); |
| - let acceptableAds = E("acceptable-ads-allow"); |
| - let acceptableAdsPrivacy = E("acceptable-ads-privacy-allow"); |
| - acceptableAdsForm.classList.remove("show-dnt-notification"); |
| - acceptableAds.setAttribute("aria-checked", false); |
| - acceptableAdsPrivacy.setAttribute("aria-checked", false); |
| - acceptableAdsPrivacy.setAttribute("tabindex", 0); |
| - if (acceptableAdsUrl in subscriptionsMap) |
| + if (whitelistItem.title == domain.value) |
| { |
| - acceptableAds.setAttribute("aria-checked", true); |
| - acceptableAdsPrivacy.setAttribute("aria-disabled", false); |
| - } |
| - else if (acceptableAdsPrivacyUrl in subscriptionsMap) |
| - { |
| - acceptableAds.setAttribute("aria-checked", true); |
| - acceptableAdsPrivacy.setAttribute("aria-checked", true); |
| - acceptableAdsPrivacy.setAttribute("aria-disabled", false); |
| - |
| - // Edge uses window instead of navigator. |
| - // Prefer navigator first since it's the standard. |
| - if ((navigator.doNotTrack || window.doNotTrack) != 1) |
| - acceptableAdsForm.classList.add("show-dnt-notification"); |
| - } |
| - else |
| - { |
| - // Using aria-disabled in order to keep the focus |
| - acceptableAdsPrivacy.setAttribute("aria-disabled", true); |
| - acceptableAdsPrivacy.setAttribute("tabindex", -1); |
| + whitelistItem[timestampUI] = Date.now(); |
| + collections.whitelist.updateItem(whitelistItem); |
| + domain.value = ""; |
| + break; |
| } |
| } |
| - |
| - function isAcceptableAds(url) |
| - { |
| - return url == acceptableAdsUrl || url == acceptableAdsPrivacyUrl; |
| - } |
| - |
| - function hasPrivacyConflict() |
| - { |
| - let acceptableAdsList = subscriptionsMap[acceptableAdsUrl]; |
| - let privacyList = null; |
| - for (let url in subscriptionsMap) |
| - { |
| - let subscription = subscriptionsMap[url]; |
| - if (subscription.recommended == "privacy") |
| - { |
| - privacyList = subscription; |
| - break; |
| - } |
| - } |
| - return acceptableAdsList && acceptableAdsList.disabled == false && |
| - privacyList && privacyList.disabled == false; |
| - } |
| - |
| - function setPrivacyConflict() |
| - { |
| - let acceptableAdsForm = E("acceptable-ads"); |
| - if (hasPrivacyConflict()) |
| - { |
| - getPref("ui_warn_tracking", (showTrackingWarning) => |
| - { |
| - acceptableAdsForm.classList.toggle("show-warning", showTrackingWarning); |
| - }); |
| - } |
| - else |
| - { |
| - acceptableAdsForm.classList.remove("show-warning"); |
| - } |
| - } |
| - |
| - function populateLists() |
| + const value = domain.value.trim(); |
| + if (value) |
| { |
| - subscriptionsMap = Object.create(null); |
| - filtersMap = Object.create(null); |
| - |
| - // Empty collections and lists |
| - for (let property in collections) |
| - collections[property].clearAll(); |
| - |
| - setCustomFiltersView("empty"); |
| - browser.runtime.sendMessage({ |
| - type: "subscriptions.get", |
| - special: true |
| - }, |
| - (subscriptions) => |
| - { |
| - // Load filters |
| - for (let subscription of subscriptions) |
| - { |
| - browser.runtime.sendMessage({ |
| - type: "filters.get", |
| - subscriptionUrl: subscription.url |
| - }, |
| - (filters) => |
| - { |
| - loadCustomFilters(filters); |
| - }); |
| - } |
| - }); |
| - loadRecommendations(); |
| - browser.runtime.sendMessage({ |
| - type: "prefs.get", |
| - key: "subscriptions_exceptionsurl" |
| - }, |
| - (url) => |
| - { |
| - acceptableAdsUrl = url; |
| - |
| - browser.runtime.sendMessage({ |
| - type: "prefs.get", |
| - key: "subscriptions_exceptionsurl_privacy" |
| - }, |
| - (urlPrivacy) => |
| - { |
| - acceptableAdsPrivacyUrl = urlPrivacy; |
| - |
| - // Load user subscriptions |
| - browser.runtime.sendMessage({ |
| - type: "subscriptions.get", |
| - downloadable: true |
| - }, |
| - (subscriptions) => |
| - { |
| - for (let subscription of subscriptions) |
| - onSubscriptionMessage("added", subscription); |
| - |
| - setAcceptableAds(); |
| - }); |
| - }); |
| + const host = /^https?:\/\//i.test(value) ? new URL(value).host : value; |
| + sendMessageHandleErrors({ |
| + type: "filters.add", |
| + text: "@@||" + host.toLowerCase() + "^$document" |
| }); |
| } |
| - function addWhitelistedDomain() |
| + domain.value = ""; |
| + E("whitelisting-add-button").disabled = true; |
| +} |
| + |
| +function addEnableSubscription(url, title, homepage) |
| +{ |
| + let messageType = null; |
| + let knownSubscription = subscriptionsMap[url]; |
| + if (knownSubscription && knownSubscription.disabled == true) |
| + messageType = "subscriptions.toggle"; |
| + else |
| + messageType = "subscriptions.add"; |
| + |
| + let message = { |
| + type: messageType, |
| + url |
| + }; |
| + if (title) |
| + message.title = title; |
| + if (homepage) |
| + message.homepage = homepage; |
| + |
| + browser.runtime.sendMessage(message); |
| +} |
| + |
| +function onFilterMessage(action, filter) |
| +{ |
| + switch (action) |
| { |
| - let domain = E("whitelisting-textbox"); |
| - for (let whitelistItem of collections.whitelist.items) |
| - { |
| - if (whitelistItem.title == domain.value) |
| - { |
| - whitelistItem[timestampUI] = Date.now(); |
| - collections.whitelist.updateItem(whitelistItem); |
| - domain.value = ""; |
| - break; |
| - } |
| - } |
| - const value = domain.value.trim(); |
| - if (value) |
| - { |
| - const host = /^https?:\/\//i.test(value) ? new URL(value).host : value; |
| - sendMessageHandleErrors({ |
| - type: "filters.add", |
| - text: "@@||" + host.toLowerCase() + "^$document" |
| - }); |
| - } |
| + case "added": |
| + filter[timestampUI] = Date.now(); |
| + updateFilter(filter); |
| + break; |
| + case "loaded": |
| + populateLists(); |
| + break; |
| + case "removed": |
| + let knownFilter = filtersMap[filter.text]; |
| + if (whitelistedDomainRegexp.test(knownFilter.text)) |
| + collections.whitelist.removeItem(knownFilter); |
| + else |
| + removeCustomFilter(filter.text); |
| - domain.value = ""; |
| - E("whitelisting-add-button").disabled = true; |
| + delete filtersMap[filter.text]; |
| + break; |
| } |
| - |
| - function addEnableSubscription(url, title, homepage) |
| - { |
| - let messageType = null; |
| - let knownSubscription = subscriptionsMap[url]; |
| - if (knownSubscription && knownSubscription.disabled == true) |
| - messageType = "subscriptions.toggle"; |
| - else |
| - messageType = "subscriptions.add"; |
| +} |
| - let message = { |
| - type: messageType, |
| - url |
| - }; |
| - if (title) |
| - message.title = title; |
| - if (homepage) |
| - message.homepage = homepage; |
| - |
| - browser.runtime.sendMessage(message); |
| - } |
| - |
| - function onFilterMessage(action, filter) |
| +function onSubscriptionMessage(action, subscription) |
| +{ |
| + if (subscription.url in subscriptionsMap) |
| { |
| - switch (action) |
| + let knownSubscription = subscriptionsMap[subscription.url]; |
| + for (let property in subscription) |
| { |
| - case "added": |
| - filter[timestampUI] = Date.now(); |
| - updateFilter(filter); |
| - break; |
| - case "loaded": |
| - populateLists(); |
| - break; |
| - case "removed": |
| - let knownFilter = filtersMap[filter.text]; |
| - if (whitelistedDomainRegexp.test(knownFilter.text)) |
| - collections.whitelist.removeItem(knownFilter); |
| - else |
| - removeCustomFilter(filter.text); |
| - |
| - delete filtersMap[filter.text]; |
| - break; |
| - } |
| - } |
| - |
| - function onSubscriptionMessage(action, subscription) |
| - { |
| - if (subscription.url in subscriptionsMap) |
| - { |
| - let knownSubscription = subscriptionsMap[subscription.url]; |
| - for (let property in subscription) |
| - { |
| - if (property == "title" && knownSubscription.recommended) |
| - knownSubscription.originalTitle = subscription.title; |
| - else |
| - knownSubscription[property] = subscription[property]; |
| - } |
| - subscription = knownSubscription; |
| + if (property == "title" && knownSubscription.recommended) |
| + knownSubscription.originalTitle = subscription.title; |
| + else |
| + knownSubscription[property] = subscription[property]; |
| } |
| - switch (action) |
| - { |
| - case "disabled": |
| + subscription = knownSubscription; |
| + } |
| + switch (action) |
| + { |
| + case "disabled": |
| + updateSubscription(subscription); |
| + setPrivacyConflict(); |
| + break; |
| + case "downloading": |
| + case "downloadStatus": |
| + case "homepage": |
| + case "lastDownload": |
| + case "title": |
| + updateSubscription(subscription); |
| + break; |
| + case "added": |
| + let {url} = subscription; |
| + // Handle custom subscription |
| + if (/^~user/.test(url)) |
| + { |
| + loadCustomFilters(subscription.filters); |
| + return; |
| + } |
| + else if (url in subscriptionsMap) |
| updateSubscription(subscription); |
| - setPrivacyConflict(); |
| - break; |
| - case "downloading": |
| - case "downloadStatus": |
| - case "homepage": |
| - case "lastDownload": |
| - case "title": |
| - updateSubscription(subscription); |
| - break; |
| - case "added": |
| - let {url} = subscription; |
| - // Handle custom subscription |
| - if (/^~user/.test(url)) |
| + else |
| + addSubscription(subscription); |
| + |
| + if (isAcceptableAds(url)) |
| + setAcceptableAds(); |
| + |
| + collections.filterLists.addItem(subscription); |
| + setPrivacyConflict(); |
| + break; |
| + case "removed": |
| + if (subscription.recommended) |
| + { |
| + subscription.disabled = true; |
| + onSubscriptionMessage("disabled", subscription); |
| + } |
| + else |
| + { |
| + delete subscriptionsMap[subscription.url]; |
| + if (isAcceptableAds(subscription.url)) |
| { |
| - loadCustomFilters(subscription.filters); |
| - return; |
| - } |
| - else if (url in subscriptionsMap) |
| - updateSubscription(subscription); |
| - else |
| - addSubscription(subscription); |
| - |
| - if (isAcceptableAds(url)) |
| setAcceptableAds(); |
| - |
| - collections.filterLists.addItem(subscription); |
| - setPrivacyConflict(); |
| - break; |
| - case "removed": |
| - if (subscription.recommended) |
| - { |
| - subscription.disabled = true; |
| - onSubscriptionMessage("disabled", subscription); |
| } |
| else |
| { |
| - delete subscriptionsMap[subscription.url]; |
| - if (isAcceptableAds(subscription.url)) |
| - { |
| - setAcceptableAds(); |
| - } |
| - else |
| - { |
| - collections.more.removeItem(subscription); |
| - } |
| + collections.more.removeItem(subscription); |
| } |
| - |
| - collections.filterLists.removeItem(subscription); |
| - setPrivacyConflict(); |
| - break; |
| - } |
| - } |
| - |
| - function hidePref(key, value) |
| - { |
| - let element = document.querySelector("[data-pref='" + key + "']"); |
| - if (element) |
| - element.setAttribute("aria-hidden", value); |
| - } |
| - |
| - function getPref(key, callback) |
| - { |
| - let checkPref = getPref.checks[key] || getPref.checkNone; |
| - checkPref((isActive) => |
| - { |
| - if (!isActive) |
| - { |
| - hidePref(key, !isActive); |
| - return; |
| } |
| - browser.runtime.sendMessage({ |
| - type: "prefs.get", |
| - key |
| - }, callback); |
| - }); |
| + collections.filterLists.removeItem(subscription); |
| + setPrivacyConflict(); |
| + break; |
| } |
| +} |
| - getPref.checkNone = function(callback) |
| +function hidePref(key, value) |
| +{ |
| + let element = document.querySelector("[data-pref='" + key + "']"); |
| + if (element) |
| + element.setAttribute("aria-hidden", value); |
| +} |
| + |
| +function getPref(key, callback) |
| +{ |
| + let checkPref = getPref.checks[key] || getPref.checkNone; |
| + checkPref((isActive) => |
| { |
| - callback(true); |
| - }; |
| - |
| - getPref.checks = |
| - { |
| - notifications_ignoredcategories(callback) |
| + if (!isActive) |
| { |
| - getPref("notifications_showui", callback); |
| - } |
| - }; |
| - |
| - function onPrefMessage(key, value, initial) |
| - { |
| - switch (key) |
| - { |
| - case "notifications_ignoredcategories": |
| - value = value.indexOf("*") == -1; |
| - break; |
| - |
| - case "notifications_showui": |
| - hidePref("notifications_ignoredcategories", !value); |
| - break; |
| - case "ui_warn_tracking": |
| - setPrivacyConflict(); |
| - break; |
| + hidePref(key, !isActive); |
| + return; |
| } |
| - let checkbox = document.querySelector( |
| - "[data-pref='" + key + "'] button[role='checkbox']" |
| - ); |
| - if (checkbox) |
| - checkbox.setAttribute("aria-checked", value); |
| + browser.runtime.sendMessage({ |
| + type: "prefs.get", |
| + key |
| + }, callback); |
| + }); |
| +} |
| + |
| +getPref.checkNone = function(callback) |
| +{ |
| + callback(true); |
| +}; |
| + |
| +getPref.checks = |
| +{ |
| + notifications_ignoredcategories(callback) |
| + { |
| + getPref("notifications_showui", callback); |
| + } |
| +}; |
| + |
| +function onPrefMessage(key, value, initial) |
| +{ |
| + switch (key) |
| + { |
| + case "notifications_ignoredcategories": |
| + value = value.indexOf("*") == -1; |
| + break; |
| + |
| + case "notifications_showui": |
| + hidePref("notifications_ignoredcategories", !value); |
| + break; |
| + case "ui_warn_tracking": |
| + setPrivacyConflict(); |
| + break; |
| } |
| - function updateTooltips() |
| + let checkbox = document.querySelector( |
| + "[data-pref='" + key + "'] button[role='checkbox']" |
| + ); |
| + if (checkbox) |
| + checkbox.setAttribute("aria-checked", value); |
| +} |
| + |
| +function updateTooltips() |
| +{ |
| + let anchors = document.querySelectorAll(":not(.tooltip) > [data-tooltip]"); |
| + for (let anchor of anchors) |
| { |
| - let anchors = document.querySelectorAll(":not(.tooltip) > [data-tooltip]"); |
| - for (let anchor of anchors) |
| - { |
| - let id = anchor.getAttribute("data-tooltip"); |
| - |
| - let wrapper = document.createElement("div"); |
| - wrapper.className = "icon tooltip"; |
| - anchor.parentNode.replaceChild(wrapper, anchor); |
| - wrapper.appendChild(anchor); |
| + let id = anchor.getAttribute("data-tooltip"); |
| - let tooltip = document.createElement("div"); |
| - tooltip.setAttribute("role", "tooltip"); |
| + let wrapper = document.createElement("div"); |
| + wrapper.className = "icon tooltip"; |
| + anchor.parentNode.replaceChild(wrapper, anchor); |
| + wrapper.appendChild(anchor); |
| + |
| + let tooltip = document.createElement("div"); |
| + tooltip.setAttribute("role", "tooltip"); |
| - let paragraph = document.createElement("p"); |
| - paragraph.textContent = getMessage(id); |
| - tooltip.appendChild(paragraph); |
| + let paragraph = document.createElement("p"); |
| + paragraph.textContent = getMessage(id); |
| + tooltip.appendChild(paragraph); |
| - wrapper.appendChild(tooltip); |
| - } |
| + wrapper.appendChild(tooltip); |
| } |
| +} |
| - ext.onMessage.addListener((message) => |
| +ext.onMessage.addListener((message) => |
| +{ |
| + switch (message.type) |
| { |
| - switch (message.type) |
| - { |
| - case "app.respond": |
| - switch (message.action) |
| - { |
| - case "addSubscription": |
| - let subscription = message.args[0]; |
| - let dialog = E("dialog-content-predefined"); |
| - dialog.querySelector("h3").textContent = subscription.title || ""; |
| - dialog.querySelector(".url").textContent = subscription.url; |
| - openDialog("predefined"); |
| - break; |
| - case "focusSection": |
| - document.body.setAttribute("data-tab", message.args[0]); |
| - break; |
| - } |
| - break; |
| - case "filters.respond": |
| - onFilterMessage(message.action, message.args[0]); |
| - break; |
| - case "prefs.respond": |
| - onPrefMessage(message.action, message.args[0], false); |
| - break; |
| - case "subscriptions.respond": |
| - onSubscriptionMessage(message.action, message.args[0]); |
| - break; |
| - } |
| - }); |
| + case "app.respond": |
| + switch (message.action) |
| + { |
| + case "addSubscription": |
| + let subscription = message.args[0]; |
| + let dialog = E("dialog-content-predefined"); |
| + dialog.querySelector("h3").textContent = subscription.title || ""; |
| + dialog.querySelector(".url").textContent = subscription.url; |
| + openDialog("predefined"); |
| + break; |
| + case "focusSection": |
| + document.body.setAttribute("data-tab", message.args[0]); |
| + break; |
| + } |
| + break; |
| + case "filters.respond": |
| + onFilterMessage(message.action, message.args[0]); |
| + break; |
| + case "prefs.respond": |
| + onPrefMessage(message.action, message.args[0], false); |
| + break; |
| + case "subscriptions.respond": |
| + onSubscriptionMessage(message.action, message.args[0]); |
| + break; |
| + } |
| +}); |
| - browser.runtime.sendMessage({ |
| - type: "app.listen", |
| - filter: ["addSubscription", "focusSection"] |
| - }); |
| - browser.runtime.sendMessage({ |
| - type: "filters.listen", |
| - filter: ["added", "loaded", "removed"] |
| - }); |
| - browser.runtime.sendMessage({ |
| - type: "prefs.listen", |
| - filter: ["notifications_ignoredcategories", "notifications_showui", |
| - "show_devtools_panel", "shouldShowBlockElementMenu", |
| - "ui_warn_tracking"] |
| - }); |
| - browser.runtime.sendMessage({ |
| - type: "subscriptions.listen", |
| - filter: ["added", "disabled", "homepage", "lastDownload", "removed", |
| - "title", "downloadStatus", "downloading"] |
| - }); |
| +browser.runtime.sendMessage({ |
| + type: "app.listen", |
| + filter: ["addSubscription", "focusSection"] |
| +}); |
| +browser.runtime.sendMessage({ |
| + type: "filters.listen", |
| + filter: ["added", "loaded", "removed"] |
| +}); |
| +browser.runtime.sendMessage({ |
| + type: "prefs.listen", |
| + filter: ["notifications_ignoredcategories", "notifications_showui", |
| + "show_devtools_panel", "shouldShowBlockElementMenu", |
| + "ui_warn_tracking"] |
| +}); |
| +browser.runtime.sendMessage({ |
| + type: "subscriptions.listen", |
| + filter: ["added", "disabled", "homepage", "lastDownload", "removed", |
| + "title", "downloadStatus", "downloading"] |
| +}); |
| - window.addEventListener("DOMContentLoaded", onDOMLoaded, false); |
| - window.addEventListener("hashchange", onHashChange, false); |
| -} |
| +window.addEventListener("DOMContentLoaded", onDOMLoaded, false); |
| +window.addEventListener("hashchange", onHashChange, false); |