| OLD | NEW |
| 1 /* | 1 /* |
| 2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
| 3 * Copyright (C) 2006-present eyeo GmbH | 3 * Copyright (C) 2006-present eyeo GmbH |
| 4 * | 4 * |
| 5 * Adblock Plus is free software: you can redistribute it and/or modify | 5 * Adblock Plus is free software: you can redistribute it and/or modify |
| 6 * it under the terms of the GNU General Public License version 3 as | 6 * it under the terms of the GNU General Public License version 3 as |
| 7 * published by the Free Software Foundation. | 7 * published by the Free Software Foundation. |
| 8 * | 8 * |
| 9 * Adblock Plus is distributed in the hope that it will be useful, | 9 * Adblock Plus is distributed in the hope that it will be useful, |
| 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 * GNU General Public License for more details. | 12 * GNU General Public License for more details. |
| 13 * | 13 * |
| 14 * You should have received a copy of the GNU General Public License | 14 * You should have received a copy of the GNU General Public License |
| 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| 16 */ | 16 */ |
| 17 | 17 |
| 18 /* globals checkShareResource, getDocLink, i18nFormatDateTime, openSharePopup, | 18 /* globals checkShareResource, getDocLink, i18nFormatDateTime, openSharePopup, |
| 19 setLinks, E */ | 19 setLinks, E */ |
| 20 | 20 |
| 21 "use strict"; | 21 "use strict"; |
| 22 | 22 |
| 23 { | 23 let subscriptionsMap = Object.create(null); |
| 24 let subscriptionsMap = Object.create(null); | 24 let filtersMap = Object.create(null); |
| 25 let filtersMap = Object.create(null); | 25 let collections = Object.create(null); |
| 26 let collections = Object.create(null); | 26 let acceptableAdsUrl = null; |
| 27 let acceptableAdsUrl = null; | 27 let acceptableAdsPrivacyUrl = null; |
| 28 let acceptableAdsPrivacyUrl = null; | 28 let isCustomFiltersLoaded = false; |
| 29 let isCustomFiltersLoaded = false; | 29 let {getMessage} = browser.i18n; |
| 30 let {getMessage} = browser.i18n; | 30 let {setElementText} = ext.i18n; |
| 31 let {setElementText} = ext.i18n; | 31 let customFilters = []; |
| 32 let customFilters = []; | 32 let filterErrors = new Map([ |
| 33 let filterErrors = new Map([ | 33 ["synchronize_invalid_url", |
| 34 ["synchronize_invalid_url", | 34 "options_filterList_lastDownload_invalidURL"], |
| 35 "options_filterList_lastDownload_invalidURL"], | 35 ["synchronize_connection_error", |
| 36 ["synchronize_connection_error", | 36 "options_filterList_lastDownload_connectionError"], |
| 37 "options_filterList_lastDownload_connectionError"], | 37 ["synchronize_invalid_data", |
| 38 ["synchronize_invalid_data", | 38 "options_filterList_lastDownload_invalidData"], |
| 39 "options_filterList_lastDownload_invalidData"], | 39 ["synchronize_checksum_mismatch", |
| 40 ["synchronize_checksum_mismatch", | 40 "options_filterList_lastDownload_checksumMismatch"] |
| 41 "options_filterList_lastDownload_checksumMismatch"] | 41 ]); |
| 42 ]); | 42 const timestampUI = Symbol(); |
| 43 const timestampUI = Symbol(); | 43 const whitelistedDomainRegexp = /^@@\|\|([^/:]+)\^\$document$/; |
| 44 const whitelistedDomainRegexp = /^@@\|\|([^/:]+)\^\$document$/; | 44 // Period of time in milliseconds |
| 45 // Period of time in milliseconds | 45 const minuteInMs = 60000; |
| 46 const minuteInMs = 60000; | 46 const hourInMs = 3600000; |
| 47 const hourInMs = 3600000; | 47 const fullDayInMs = 86400000; |
| 48 const fullDayInMs = 86400000; | 48 |
| 49 | 49 function Collection(details) |
| 50 function Collection(details) | 50 { |
| 51 { | 51 this.details = details; |
| 52 this.details = details; | 52 this.items = []; |
| 53 this.items = []; | 53 } |
| 54 } | 54 |
| 55 | 55 Collection.prototype._setEmpty = function(table, detail, removeEmpty) |
| 56 Collection.prototype._setEmpty = function(table, detail, removeEmpty) | 56 { |
| 57 { | 57 if (removeEmpty) |
| 58 if (removeEmpty) | 58 { |
| 59 { | 59 let placeholders = table.querySelectorAll(".empty-placeholder"); |
| 60 let placeholders = table.querySelectorAll(".empty-placeholder"); | 60 for (let placeholder of placeholders) |
| 61 for (let placeholder of placeholders) | 61 table.removeChild(placeholder); |
| 62 table.removeChild(placeholder); | 62 |
| 63 | 63 execAction(detail.removeEmptyAction, table); |
| 64 execAction(detail.removeEmptyAction, table); | 64 } |
| 65 } | 65 else |
| 66 { |
| 67 let {emptyTexts = []} = detail; |
| 68 for (let text of emptyTexts) |
| 69 { |
| 70 let placeholder = document.createElement("li"); |
| 71 placeholder.className = "empty-placeholder"; |
| 72 placeholder.textContent = getMessage(text); |
| 73 table.appendChild(placeholder); |
| 74 } |
| 75 |
| 76 execAction(detail.setEmptyAction, table); |
| 77 } |
| 78 }; |
| 79 |
| 80 Collection.prototype._createElementQuery = function(item) |
| 81 { |
| 82 let access = (item.url || item.text).replace(/'/g, "\\'"); |
| 83 return function(container) |
| 84 { |
| 85 return container.querySelector("[data-access='" + access + "']"); |
| 86 }; |
| 87 }; |
| 88 |
| 89 Collection.prototype._getItemTitle = function(item, i) |
| 90 { |
| 91 if (this.details[i].getTitleFunction) |
| 92 return this.details[i].getTitleFunction(item); |
| 93 return item.title || item.url || item.text; |
| 94 }; |
| 95 |
| 96 Collection.prototype._sortItems = function() |
| 97 { |
| 98 this.items.sort((a, b) => |
| 99 { |
| 100 // Make sure that Acceptable Ads is always last, since it cannot be |
| 101 // disabled, but only be removed. That way it's grouped together with |
| 102 // the "Own filter list" which cannot be disabled either at the bottom |
| 103 // of the filter lists in the Advanced tab. |
| 104 if (isAcceptableAds(a.url)) |
| 105 return 1; |
| 106 if (isAcceptableAds(b.url)) |
| 107 return -1; |
| 108 |
| 109 // Make sure that newly added entries always appear on top in descending |
| 110 // chronological order |
| 111 let aTimestamp = a[timestampUI] || 0; |
| 112 let bTimestamp = b[timestampUI] || 0; |
| 113 if (aTimestamp || bTimestamp) |
| 114 return bTimestamp - aTimestamp; |
| 115 |
| 116 let aTitle = this._getItemTitle(a, 0).toLowerCase(); |
| 117 let bTitle = this._getItemTitle(b, 0).toLowerCase(); |
| 118 return aTitle.localeCompare(bTitle); |
| 119 }); |
| 120 }; |
| 121 |
| 122 Collection.prototype.addItem = function(item) |
| 123 { |
| 124 if (this.items.indexOf(item) >= 0) |
| 125 return; |
| 126 |
| 127 this.items.push(item); |
| 128 this._sortItems(); |
| 129 for (let j = 0; j < this.details.length; j++) |
| 130 { |
| 131 let detail = this.details[j]; |
| 132 let table = E(detail.id); |
| 133 let template = table.querySelector("template"); |
| 134 let listItem = document.createElement("li"); |
| 135 listItem.appendChild(document.importNode(template.content, true)); |
| 136 listItem.setAttribute("aria-label", this._getItemTitle(item, j)); |
| 137 listItem.setAttribute("data-access", item.url || item.text); |
| 138 listItem.setAttribute("role", "section"); |
| 139 |
| 140 let tooltip = listItem.querySelector("[data-tooltip]"); |
| 141 if (tooltip) |
| 142 { |
| 143 let tooltipId = tooltip.getAttribute("data-tooltip"); |
| 144 tooltipId = tooltipId.replace("%value%", item.recommended); |
| 145 if (getMessage(tooltipId)) |
| 146 { |
| 147 tooltip.setAttribute("data-tooltip", tooltipId); |
| 148 } |
| 149 } |
| 150 |
| 151 for (let control of listItem.querySelectorAll(".control")) |
| 152 { |
| 153 if (control.hasAttribute("title")) |
| 154 { |
| 155 let titleValue = getMessage(control.getAttribute("title")); |
| 156 control.setAttribute("title", titleValue); |
| 157 } |
| 158 } |
| 159 |
| 160 this._setEmpty(table, detail, true); |
| 161 if (table.children.length > 0) |
| 162 table.insertBefore(listItem, table.children[this.items.indexOf(item)]); |
| 66 else | 163 else |
| 67 { | 164 table.appendChild(listItem); |
| 68 let {emptyTexts = []} = detail; | 165 |
| 69 for (let text of emptyTexts) | 166 this.updateItem(item); |
| 70 { | 167 } |
| 71 let placeholder = document.createElement("li"); | 168 return length; |
| 72 placeholder.className = "empty-placeholder"; | 169 }; |
| 73 placeholder.textContent = getMessage(text); | 170 |
| 74 table.appendChild(placeholder); | 171 Collection.prototype.removeItem = function(item) |
| 75 } | 172 { |
| 76 | 173 let index = this.items.indexOf(item); |
| 77 execAction(detail.setEmptyAction, table); | 174 if (index == -1) |
| 78 } | 175 return; |
| 79 }; | 176 |
| 80 | 177 this.items.splice(index, 1); |
| 81 Collection.prototype._createElementQuery = function(item) | 178 let getListElement = this._createElementQuery(item); |
| 82 { | 179 for (let detail of this.details) |
| 83 let access = (item.url || item.text).replace(/'/g, "\\'"); | 180 { |
| 84 return function(container) | 181 let table = E(detail.id); |
| 85 { | 182 let element = getListElement(table); |
| 86 return container.querySelector("[data-access='" + access + "']"); | 183 |
| 87 }; | 184 // Element gets removed so make sure to handle focus appropriately |
| 88 }; | 185 let control = element.querySelector(".control"); |
| 89 | 186 if (control && control == document.activeElement) |
| 90 Collection.prototype._getItemTitle = function(item, i) | 187 { |
| 91 { | 188 if (!focusNextElement(element.parentElement, control)) |
| 92 if (this.details[i].getTitleFunction) | 189 { |
| 93 return this.details[i].getTitleFunction(item); | 190 // Fall back to next focusable element within same tab or dialog |
| 94 return item.title || item.url || item.text; | 191 let focusableElement = element.parentElement; |
| 95 }; | 192 while (focusableElement) |
| 96 | |
| 97 Collection.prototype._sortItems = function() | |
| 98 { | |
| 99 this.items.sort((a, b) => | |
| 100 { | |
| 101 // Make sure that Acceptable Ads is always last, since it cannot be | |
| 102 // disabled, but only be removed. That way it's grouped together with | |
| 103 // the "Own filter list" which cannot be disabled either at the bottom | |
| 104 // of the filter lists in the Advanced tab. | |
| 105 if (isAcceptableAds(a.url)) | |
| 106 return 1; | |
| 107 if (isAcceptableAds(b.url)) | |
| 108 return -1; | |
| 109 | |
| 110 // Make sure that newly added entries always appear on top in descending | |
| 111 // chronological order | |
| 112 let aTimestamp = a[timestampUI] || 0; | |
| 113 let bTimestamp = b[timestampUI] || 0; | |
| 114 if (aTimestamp || bTimestamp) | |
| 115 return bTimestamp - aTimestamp; | |
| 116 | |
| 117 let aTitle = this._getItemTitle(a, 0).toLowerCase(); | |
| 118 let bTitle = this._getItemTitle(b, 0).toLowerCase(); | |
| 119 return aTitle.localeCompare(bTitle); | |
| 120 }); | |
| 121 }; | |
| 122 | |
| 123 Collection.prototype.addItem = function(item) | |
| 124 { | |
| 125 if (this.items.indexOf(item) >= 0) | |
| 126 return; | |
| 127 | |
| 128 this.items.push(item); | |
| 129 this._sortItems(); | |
| 130 for (let j = 0; j < this.details.length; j++) | |
| 131 { | |
| 132 let detail = this.details[j]; | |
| 133 let table = E(detail.id); | |
| 134 let template = table.querySelector("template"); | |
| 135 let listItem = document.createElement("li"); | |
| 136 listItem.appendChild(document.importNode(template.content, true)); | |
| 137 listItem.setAttribute("aria-label", this._getItemTitle(item, j)); | |
| 138 listItem.setAttribute("data-access", item.url || item.text); | |
| 139 listItem.setAttribute("role", "section"); | |
| 140 | |
| 141 let tooltip = listItem.querySelector("[data-tooltip]"); | |
| 142 if (tooltip) | |
| 143 { | |
| 144 let tooltipId = tooltip.getAttribute("data-tooltip"); | |
| 145 tooltipId = tooltipId.replace("%value%", item.recommended); | |
| 146 if (getMessage(tooltipId)) | |
| 147 { | 193 { |
| 148 tooltip.setAttribute("data-tooltip", tooltipId); | 194 if (focusableElement.classList.contains("tab-content") || |
| 195 focusableElement.classList.contains("dialog-content")) |
| 196 break; |
| 197 |
| 198 focusableElement = focusableElement.parentElement; |
| 149 } | 199 } |
| 150 } | 200 focusNextElement(focusableElement || document, control); |
| 151 | 201 } |
| 152 for (let control of listItem.querySelectorAll(".control")) | 202 } |
| 153 { | 203 |
| 154 if (control.hasAttribute("title")) | 204 element.parentElement.removeChild(element); |
| 205 if (this.items.length == 0) |
| 206 this._setEmpty(table, detail); |
| 207 } |
| 208 }; |
| 209 |
| 210 Collection.prototype.updateItem = function(item) |
| 211 { |
| 212 let oldIndex = this.items.indexOf(item); |
| 213 this._sortItems(); |
| 214 let access = (item.url || item.text).replace(/'/g, "\\'"); |
| 215 for (let i = 0; i < this.details.length; i++) |
| 216 { |
| 217 let table = E(this.details[i].id); |
| 218 let element = table.querySelector("[data-access='" + access + "']"); |
| 219 if (!element) |
| 220 continue; |
| 221 |
| 222 let title = this._getItemTitle(item, i); |
| 223 let displays = element.querySelectorAll("[data-display]"); |
| 224 for (let j = 0; j < displays.length; j++) |
| 225 { |
| 226 if (item[displays[j].dataset.display]) |
| 227 displays[j].textContent = item[displays[j].dataset.display]; |
| 228 else |
| 229 displays[j].textContent = title; |
| 230 } |
| 231 |
| 232 element.setAttribute("aria-label", title); |
| 233 if (this.details[i].searchable) |
| 234 element.setAttribute("data-search", title.toLowerCase()); |
| 235 let controls = element.querySelectorAll(".control[role='checkbox']"); |
| 236 for (let control of controls) |
| 237 { |
| 238 control.setAttribute("aria-checked", item.disabled == false); |
| 239 if (isAcceptableAds(item.url) && this == collections.filterLists) |
| 240 control.disabled = true; |
| 241 } |
| 242 |
| 243 let lastUpdateElement = element.querySelector(".last-update"); |
| 244 if (lastUpdateElement) |
| 245 { |
| 246 let message = element.querySelector(".message"); |
| 247 if (item.isDownloading) |
| 248 { |
| 249 let text = getMessage("options_filterList_lastDownload_inProgress"); |
| 250 message.textContent = text; |
| 251 element.classList.add("show-message"); |
| 252 } |
| 253 else if (item.downloadStatus != "synchronize_ok") |
| 254 { |
| 255 let error = filterErrors.get(item.downloadStatus); |
| 256 if (error) |
| 257 message.textContent = getMessage(error); |
| 258 else |
| 259 message.textContent = item.downloadStatus; |
| 260 element.classList.add("show-message"); |
| 261 } |
| 262 else if (item.lastDownload > 0) |
| 263 { |
| 264 let lastUpdate = item.lastDownload * 1000; |
| 265 let sinceUpdate = Date.now() - lastUpdate; |
| 266 if (sinceUpdate > fullDayInMs) |
| 155 { | 267 { |
| 156 let titleValue = getMessage(control.getAttribute("title")); | 268 let lastUpdateDate = new Date(item.lastDownload * 1000); |
| 157 control.setAttribute("title", titleValue); | 269 let monthName = lastUpdateDate.toLocaleString(undefined, |
| 270 {month: "short"}); |
| 271 let day = lastUpdateDate.getDate(); |
| 272 day = day < 10 ? "0" + day : day; |
| 273 lastUpdateElement.textContent = day + " " + monthName + " " + |
| 274 lastUpdateDate.getFullYear(); |
| 158 } | 275 } |
| 159 } | 276 else if (sinceUpdate > hourInMs) |
| 160 | |
| 161 this._setEmpty(table, detail, true); | |
| 162 if (table.children.length > 0) | |
| 163 table.insertBefore(listItem, table.children[this.items.indexOf(item)]); | |
| 164 else | |
| 165 table.appendChild(listItem); | |
| 166 | |
| 167 this.updateItem(item); | |
| 168 } | |
| 169 return length; | |
| 170 }; | |
| 171 | |
| 172 Collection.prototype.removeItem = function(item) | |
| 173 { | |
| 174 let index = this.items.indexOf(item); | |
| 175 if (index == -1) | |
| 176 return; | |
| 177 | |
| 178 this.items.splice(index, 1); | |
| 179 let getListElement = this._createElementQuery(item); | |
| 180 for (let detail of this.details) | |
| 181 { | |
| 182 let table = E(detail.id); | |
| 183 let element = getListElement(table); | |
| 184 | |
| 185 // Element gets removed so make sure to handle focus appropriately | |
| 186 let control = element.querySelector(".control"); | |
| 187 if (control && control == document.activeElement) | |
| 188 { | |
| 189 if (!focusNextElement(element.parentElement, control)) | |
| 190 { | 277 { |
| 191 // Fall back to next focusable element within same tab or dialog | 278 lastUpdateElement.textContent = |
| 192 let focusableElement = element.parentElement; | 279 getMessage("options_filterList_hours"); |
| 193 while (focusableElement) | |
| 194 { | |
| 195 if (focusableElement.classList.contains("tab-content") || | |
| 196 focusableElement.classList.contains("dialog-content")) | |
| 197 break; | |
| 198 | |
| 199 focusableElement = focusableElement.parentElement; | |
| 200 } | |
| 201 focusNextElement(focusableElement || document, control); | |
| 202 } | 280 } |
| 203 } | 281 else if (sinceUpdate > minuteInMs) |
| 204 | |
| 205 element.parentElement.removeChild(element); | |
| 206 if (this.items.length == 0) | |
| 207 this._setEmpty(table, detail); | |
| 208 } | |
| 209 }; | |
| 210 | |
| 211 Collection.prototype.updateItem = function(item) | |
| 212 { | |
| 213 let oldIndex = this.items.indexOf(item); | |
| 214 this._sortItems(); | |
| 215 let access = (item.url || item.text).replace(/'/g, "\\'"); | |
| 216 for (let i = 0; i < this.details.length; i++) | |
| 217 { | |
| 218 let table = E(this.details[i].id); | |
| 219 let element = table.querySelector("[data-access='" + access + "']"); | |
| 220 if (!element) | |
| 221 continue; | |
| 222 | |
| 223 let title = this._getItemTitle(item, i); | |
| 224 let displays = element.querySelectorAll("[data-display]"); | |
| 225 for (let j = 0; j < displays.length; j++) | |
| 226 { | |
| 227 if (item[displays[j].dataset.display]) | |
| 228 displays[j].textContent = item[displays[j].dataset.display]; | |
| 229 else | |
| 230 displays[j].textContent = title; | |
| 231 } | |
| 232 | |
| 233 element.setAttribute("aria-label", title); | |
| 234 if (this.details[i].searchable) | |
| 235 element.setAttribute("data-search", title.toLowerCase()); | |
| 236 let controls = element.querySelectorAll(".control[role='checkbox']"); | |
| 237 for (let control of controls) | |
| 238 { | |
| 239 control.setAttribute("aria-checked", item.disabled == false); | |
| 240 if (isAcceptableAds(item.url) && this == collections.filterLists) | |
| 241 control.disabled = true; | |
| 242 } | |
| 243 | |
| 244 let lastUpdateElement = element.querySelector(".last-update"); | |
| 245 if (lastUpdateElement) | |
| 246 { | |
| 247 let message = element.querySelector(".message"); | |
| 248 if (item.isDownloading) | |
| 249 { | 282 { |
| 250 let text = getMessage("options_filterList_lastDownload_inProgress"); | 283 lastUpdateElement.textContent = |
| 251 message.textContent = text; | 284 getMessage("options_filterList_minutes"); |
| 252 element.classList.add("show-message"); | |
| 253 } | 285 } |
| 254 else if (item.downloadStatus != "synchronize_ok") | |
| 255 { | |
| 256 let error = filterErrors.get(item.downloadStatus); | |
| 257 if (error) | |
| 258 message.textContent = getMessage(error); | |
| 259 else | |
| 260 message.textContent = item.downloadStatus; | |
| 261 element.classList.add("show-message"); | |
| 262 } | |
| 263 else if (item.lastDownload > 0) | |
| 264 { | |
| 265 let lastUpdate = item.lastDownload * 1000; | |
| 266 let sinceUpdate = Date.now() - lastUpdate; | |
| 267 if (sinceUpdate > fullDayInMs) | |
| 268 { | |
| 269 let lastUpdateDate = new Date(item.lastDownload * 1000); | |
| 270 let monthName = lastUpdateDate.toLocaleString(undefined, | |
| 271 {month: "short"}); | |
| 272 let day = lastUpdateDate.getDate(); | |
| 273 day = day < 10 ? "0" + day : day; | |
| 274 lastUpdateElement.textContent = day + " " + monthName + " " + | |
| 275 lastUpdateDate.getFullYear(); | |
| 276 } | |
| 277 else if (sinceUpdate > hourInMs) | |
| 278 { | |
| 279 lastUpdateElement.textContent = | |
| 280 getMessage("options_filterList_hours"); | |
| 281 } | |
| 282 else if (sinceUpdate > minuteInMs) | |
| 283 { | |
| 284 lastUpdateElement.textContent = | |
| 285 getMessage("options_filterList_minutes"); | |
| 286 } | |
| 287 else | |
| 288 { | |
| 289 lastUpdateElement.textContent = | |
| 290 getMessage("options_filterList_now"); | |
| 291 } | |
| 292 element.classList.remove("show-message"); | |
| 293 } | |
| 294 } | |
| 295 | |
| 296 let websiteElement = element.querySelector(".context-menu .website"); | |
| 297 if (websiteElement) | |
| 298 { | |
| 299 if (item.homepage) | |
| 300 websiteElement.setAttribute("href", item.homepage); | |
| 301 else | |
| 302 websiteElement.setAttribute("aria-hidden", true); | |
| 303 } | |
| 304 | |
| 305 let sourceElement = element.querySelector(".context-menu .source"); | |
| 306 if (sourceElement) | |
| 307 sourceElement.setAttribute("href", item.url); | |
| 308 | |
| 309 let newIndex = this.items.indexOf(item); | |
| 310 if (oldIndex != newIndex) | |
| 311 table.insertBefore(element, table.childNodes[newIndex]); | |
| 312 } | |
| 313 }; | |
| 314 | |
| 315 Collection.prototype.clearAll = function() | |
| 316 { | |
| 317 this.items = []; | |
| 318 for (let detail of this.details) | |
| 319 { | |
| 320 let table = E(detail.id); | |
| 321 let element = table.firstChild; | |
| 322 while (element) | |
| 323 { | |
| 324 if (element.tagName == "LI" && !element.classList.contains("static")) | |
| 325 table.removeChild(element); | |
| 326 element = element.nextElementSibling; | |
| 327 } | |
| 328 | |
| 329 this._setEmpty(table, detail); | |
| 330 } | |
| 331 }; | |
| 332 | |
| 333 function focusNextElement(container, currentElement) | |
| 334 { | |
| 335 let focusables = container.querySelectorAll("a, button, input, .control"); | |
| 336 focusables = Array.prototype.slice.call(focusables); | |
| 337 let index = focusables.indexOf(currentElement); | |
| 338 index += (index == focusables.length - 1) ? -1 : 1; | |
| 339 | |
| 340 let nextElement = focusables[index]; | |
| 341 if (!nextElement) | |
| 342 return false; | |
| 343 | |
| 344 nextElement.focus(); | |
| 345 return true; | |
| 346 } | |
| 347 | |
| 348 collections.protection = new Collection([ | |
| 349 { | |
| 350 id: "recommend-protection-list-table" | |
| 351 } | |
| 352 ]); | |
| 353 collections.langs = new Collection([ | |
| 354 { | |
| 355 id: "blocking-languages-table", | |
| 356 emptyTexts: ["options_language_empty"], | |
| 357 getTitleFunction: getLanguageTitle | |
| 358 } | |
| 359 ]); | |
| 360 collections.allLangs = new Collection([ | |
| 361 { | |
| 362 id: "all-lang-table-add", | |
| 363 emptyTexts: ["options_dialog_language_other_empty"], | |
| 364 getTitleFunction: getLanguageTitle | |
| 365 } | |
| 366 ]); | |
| 367 collections.more = new Collection([ | |
| 368 { | |
| 369 id: "more-list-table", | |
| 370 setEmptyAction: "hide-more-filters-section", | |
| 371 removeEmptyAction: "show-more-filters-section" | |
| 372 } | |
| 373 ]); | |
| 374 collections.whitelist = new Collection([ | |
| 375 { | |
| 376 id: "whitelisting-table", | |
| 377 emptyTexts: ["options_whitelist_empty_1", "options_whitelist_empty_2"] | |
| 378 } | |
| 379 ]); | |
| 380 collections.filterLists = new Collection([ | |
| 381 { | |
| 382 id: "all-filter-lists-table", | |
| 383 emptyTexts: ["options_filterList_empty"] | |
| 384 } | |
| 385 ]); | |
| 386 | |
| 387 function addSubscription(subscription) | |
| 388 { | |
| 389 let {disabled} = subscription; | |
| 390 let collection = null; | |
| 391 if (subscription.recommended) | |
| 392 { | |
| 393 if (subscription.recommended == "ads") | |
| 394 { | |
| 395 if (disabled == false) | |
| 396 collection = collections.langs; | |
| 397 | |
| 398 collections.allLangs.addItem(subscription); | |
| 399 } | |
| 400 else | |
| 401 { | |
| 402 collection = collections.protection; | |
| 403 } | |
| 404 } | |
| 405 else if (!isAcceptableAds(subscription.url) && disabled == false) | |
| 406 { | |
| 407 collection = collections.more; | |
| 408 } | |
| 409 | |
| 410 if (collection) | |
| 411 collection.addItem(subscription); | |
| 412 | |
| 413 subscriptionsMap[subscription.url] = subscription; | |
| 414 updateTooltips(); | |
| 415 } | |
| 416 | |
| 417 function updateSubscription(subscription) | |
| 418 { | |
| 419 for (let name in collections) | |
| 420 collections[name].updateItem(subscription); | |
| 421 | |
| 422 if (subscription.recommended == "ads") | |
| 423 { | |
| 424 if (subscription.disabled) | |
| 425 collections.langs.removeItem(subscription); | |
| 426 else | |
| 427 collections.langs.addItem(subscription); | |
| 428 } | |
| 429 else if (!subscription.recommended && !isAcceptableAds(subscription.url)) | |
| 430 { | |
| 431 if (subscription.disabled == false) | |
| 432 { | |
| 433 collections.more.addItem(subscription); | |
| 434 updateTooltips(); | |
| 435 } | |
| 436 else | |
| 437 { | |
| 438 collections.more.removeItem(subscription); | |
| 439 } | |
| 440 } | |
| 441 } | |
| 442 | |
| 443 function updateFilter(filter) | |
| 444 { | |
| 445 let match = filter.text.match(whitelistedDomainRegexp); | |
| 446 if (match && !filtersMap[filter.text]) | |
| 447 { | |
| 448 filter.title = match[1]; | |
| 449 collections.whitelist.addItem(filter); | |
| 450 if (isCustomFiltersLoaded) | |
| 451 { | |
| 452 let text = getMessage("options_whitelist_notification", [filter.title]); | |
| 453 showNotification(text); | |
| 454 } | |
| 455 } | |
| 456 else | |
| 457 { | |
| 458 customFilters.push(filter.text); | |
| 459 if (isCustomFiltersLoaded) | |
| 460 updateCustomFiltersUi(); | |
| 461 } | |
| 462 | |
| 463 filtersMap[filter.text] = filter; | |
| 464 } | |
| 465 | |
| 466 function loadCustomFilters(filters) | |
| 467 { | |
| 468 for (let filter of filters) | |
| 469 updateFilter(filter); | |
| 470 | |
| 471 setCustomFiltersView("read"); | |
| 472 isCustomFiltersLoaded = true; | |
| 473 } | |
| 474 | |
| 475 function removeCustomFilter(text) | |
| 476 { | |
| 477 let index = customFilters.indexOf(text); | |
| 478 if (index >= 0) | |
| 479 customFilters.splice(index, 1); | |
| 480 | |
| 481 updateCustomFiltersUi(); | |
| 482 } | |
| 483 | |
| 484 function updateCustomFiltersUi() | |
| 485 { | |
| 486 let customFiltersListElement = E("custom-filters-raw"); | |
| 487 customFiltersListElement.value = customFilters.join("\n"); | |
| 488 } | |
| 489 | |
| 490 function getLanguageTitle(item) | |
| 491 { | |
| 492 let title = item.specialization; | |
| 493 if (item.originalTitle && item.originalTitle.indexOf("+EasyList") > -1) | |
| 494 title += " + " + getMessage("options_english"); | |
| 495 return title; | |
| 496 } | |
| 497 | |
| 498 function loadRecommendations() | |
| 499 { | |
| 500 fetch("subscriptions.xml") | |
| 501 .then((response) => | |
| 502 { | |
| 503 return response.text(); | |
| 504 }) | |
| 505 .then((text) => | |
| 506 { | |
| 507 let doc = new DOMParser().parseFromString(text, "application/xml"); | |
| 508 let elements = doc.documentElement.getElementsByTagName("subscription"); | |
| 509 for (let element of elements) | |
| 510 { | |
| 511 let type = element.getAttribute("type"); | |
| 512 let subscription = { | |
| 513 disabled: true, | |
| 514 downloadStatus: null, | |
| 515 homepage: null, | |
| 516 specialization: element.getAttribute("specialization"), | |
| 517 originalTitle: element.getAttribute("title"), | |
| 518 recommended: type, | |
| 519 url: element.getAttribute("url") | |
| 520 }; | |
| 521 | |
| 522 if (subscription.recommended != "ads") | |
| 523 { | |
| 524 type = type.replace(/\W/g, "_"); | |
| 525 subscription.title = getMessage("common_feature_" + | |
| 526 type + "_title"); | |
| 527 } | |
| 528 | |
| 529 addSubscription(subscription); | |
| 530 } | |
| 531 }); | |
| 532 } | |
| 533 | |
| 534 function findParentData(element, dataName, returnElement) | |
| 535 { | |
| 536 element = element.closest(`[data-${dataName}]`); | |
| 537 if (!element) | |
| 538 return null; | |
| 539 if (returnElement) | |
| 540 return element; | |
| 541 return element.getAttribute(`data-${dataName}`); | |
| 542 } | |
| 543 | |
| 544 function sendMessageHandleErrors(message, onSuccess) | |
| 545 { | |
| 546 browser.runtime.sendMessage(message, (errors) => | |
| 547 { | |
| 548 if (errors.length > 0) | |
| 549 alert(errors.join("\n")); | |
| 550 else if (onSuccess) | |
| 551 onSuccess(); | |
| 552 }); | |
| 553 } | |
| 554 | |
| 555 function switchTab(id) | |
| 556 { | |
| 557 location.hash = id; | |
| 558 } | |
| 559 | |
| 560 function execAction(action, element) | |
| 561 { | |
| 562 if (element.getAttribute("aria-disabled") == "true") | |
| 563 return; | |
| 564 | |
| 565 switch (action) | |
| 566 { | |
| 567 case "add-domain-exception": | |
| 568 addWhitelistedDomain(); | |
| 569 break; | |
| 570 case "add-language-subscription": | |
| 571 addEnableSubscription(findParentData(element, "access", false)); | |
| 572 break; | |
| 573 case "add-predefined-subscription": { | |
| 574 let dialog = E("dialog-content-predefined"); | |
| 575 let title = dialog.querySelector("h3").textContent; | |
| 576 let url = dialog.querySelector(".url").textContent; | |
| 577 addEnableSubscription(url, title); | |
| 578 closeDialog(); | |
| 579 break; | |
| 580 } | |
| 581 case "cancel-custom-filters": | |
| 582 setCustomFiltersView("read"); | |
| 583 break; | |
| 584 case "change-language-subscription": | |
| 585 for (let key in subscriptionsMap) | |
| 586 { | |
| 587 let subscription = subscriptionsMap[key]; | |
| 588 let subscriptionType = subscription.recommended; | |
| 589 if (subscriptionType == "ads" && subscription.disabled == false) | |
| 590 { | |
| 591 browser.runtime.sendMessage({ | |
| 592 type: "subscriptions.remove", | |
| 593 url: subscription.url | |
| 594 }); | |
| 595 browser.runtime.sendMessage({ | |
| 596 type: "subscriptions.add", | |
| 597 url: findParentData(element, "access", false) | |
| 598 }); | |
| 599 break; | |
| 600 } | |
| 601 } | |
| 602 break; | |
| 603 case "close-dialog": | |
| 604 closeDialog(); | |
| 605 break; | |
| 606 case "edit-custom-filters": | |
| 607 setCustomFiltersView("write"); | |
| 608 break; | |
| 609 case "hide-more-filters-section": | |
| 610 E("more-filters").setAttribute("aria-hidden", true); | |
| 611 break; | |
| 612 case "hide-notification": | |
| 613 hideNotification(); | |
| 614 break; | |
| 615 case "import-subscription": { | |
| 616 let url = E("blockingList-textbox").value; | |
| 617 addEnableSubscription(url); | |
| 618 closeDialog(); | |
| 619 break; | |
| 620 } | |
| 621 case "open-context-menu": { | |
| 622 let listItem = findParentData(element, "access", true); | |
| 623 if (listItem && !listItem.classList.contains("show-context-menu")) | |
| 624 listItem.classList.add("show-context-menu"); | |
| 625 break; | |
| 626 } | |
| 627 case "open-dialog": { | |
| 628 let dialog = findParentData(element, "dialog", false); | |
| 629 openDialog(dialog); | |
| 630 break; | |
| 631 } | |
| 632 case "remove-filter": | |
| 633 browser.runtime.sendMessage({ | |
| 634 type: "filters.remove", | |
| 635 text: findParentData(element, "access", false) | |
| 636 }); | |
| 637 break; | |
| 638 case "remove-subscription": | |
| 639 browser.runtime.sendMessage({ | |
| 640 type: "subscriptions.remove", | |
| 641 url: findParentData(element, "access", false) | |
| 642 }); | |
| 643 break; | |
| 644 case "save-custom-filters": | |
| 645 sendMessageHandleErrors({ | |
| 646 type: "filters.importRaw", | |
| 647 text: E("custom-filters-raw").value, | |
| 648 removeExisting: true | |
| 649 }, | |
| 650 () => | |
| 651 { | |
| 652 setCustomFiltersView("read"); | |
| 653 }); | |
| 654 break; | |
| 655 case "show-more-filters-section": | |
| 656 E("more-filters").setAttribute("aria-hidden", false); | |
| 657 break; | |
| 658 case "switch-acceptable-ads": | |
| 659 let value = element.value || element.dataset.value; | |
| 660 // User check the checkbox | |
| 661 let shouldCheck = element.getAttribute("aria-checked") != "true"; | |
| 662 let installAcceptableAds = false; | |
| 663 let installAcceptableAdsPrivacy = false; | |
| 664 // Acceptable Ads checkbox clicked | |
| 665 if (value == "ads") | |
| 666 { | |
| 667 installAcceptableAds = shouldCheck; | |
| 668 } | |
| 669 // Privacy Friendly Acceptable Ads checkbox clicked | |
| 670 else | 286 else |
| 671 { | 287 { |
| 672 installAcceptableAdsPrivacy = shouldCheck; | 288 lastUpdateElement.textContent = |
| 673 installAcceptableAds = !shouldCheck; | 289 getMessage("options_filterList_now"); |
| 674 } | 290 } |
| 675 | 291 element.classList.remove("show-message"); |
| 676 browser.runtime.sendMessage({ | 292 } |
| 677 type: installAcceptableAds ? "subscriptions.add" : | 293 } |
| 678 "subscriptions.remove", | 294 |
| 679 url: acceptableAdsUrl | 295 let websiteElement = element.querySelector(".context-menu .website"); |
| 680 }); | 296 if (websiteElement) |
| 681 browser.runtime.sendMessage({ | 297 { |
| 682 type: installAcceptableAdsPrivacy ? "subscriptions.add" : | 298 if (item.homepage) |
| 683 "subscriptions.remove", | 299 websiteElement.setAttribute("href", item.homepage); |
| 684 url: acceptableAdsPrivacyUrl | 300 else |
| 685 }); | 301 websiteElement.setAttribute("aria-hidden", true); |
| 686 break; | 302 } |
| 687 case "switch-tab": | 303 |
| 688 switchTab(element.getAttribute("href").substr(1)); | 304 let sourceElement = element.querySelector(".context-menu .source"); |
| 689 break; | 305 if (sourceElement) |
| 690 case "toggle-disable-subscription": | 306 sourceElement.setAttribute("href", item.url); |
| 691 browser.runtime.sendMessage({ | 307 |
| 692 type: "subscriptions.toggle", | 308 let newIndex = this.items.indexOf(item); |
| 693 keepInstalled: true, | 309 if (oldIndex != newIndex) |
| 694 url: findParentData(element, "access", false) | 310 table.insertBefore(element, table.childNodes[newIndex]); |
| 695 }); | 311 } |
| 696 break; | 312 }; |
| 697 case "toggle-pref": | 313 |
| 698 browser.runtime.sendMessage({ | 314 Collection.prototype.clearAll = function() |
| 699 type: "prefs.toggle", | 315 { |
| 700 key: findParentData(element, "pref", false) | 316 this.items = []; |
| 701 }); | 317 for (let detail of this.details) |
| 702 break; | 318 { |
| 703 case "toggle-remove-subscription": | 319 let table = E(detail.id); |
| 704 let subscriptionUrl = findParentData(element, "access", false); | 320 let element = table.firstChild; |
| 705 if (element.getAttribute("aria-checked") == "true") | 321 while (element) |
| 322 { |
| 323 if (element.tagName == "LI" && !element.classList.contains("static")) |
| 324 table.removeChild(element); |
| 325 element = element.nextElementSibling; |
| 326 } |
| 327 |
| 328 this._setEmpty(table, detail); |
| 329 } |
| 330 }; |
| 331 |
| 332 function focusNextElement(container, currentElement) |
| 333 { |
| 334 let focusables = container.querySelectorAll("a, button, input, .control"); |
| 335 focusables = Array.prototype.slice.call(focusables); |
| 336 let index = focusables.indexOf(currentElement); |
| 337 index += (index == focusables.length - 1) ? -1 : 1; |
| 338 |
| 339 let nextElement = focusables[index]; |
| 340 if (!nextElement) |
| 341 return false; |
| 342 |
| 343 nextElement.focus(); |
| 344 return true; |
| 345 } |
| 346 |
| 347 collections.protection = new Collection([ |
| 348 { |
| 349 id: "recommend-protection-list-table" |
| 350 } |
| 351 ]); |
| 352 collections.langs = new Collection([ |
| 353 { |
| 354 id: "blocking-languages-table", |
| 355 emptyTexts: ["options_language_empty"], |
| 356 getTitleFunction: getLanguageTitle |
| 357 } |
| 358 ]); |
| 359 collections.allLangs = new Collection([ |
| 360 { |
| 361 id: "all-lang-table-add", |
| 362 emptyTexts: ["options_dialog_language_other_empty"], |
| 363 getTitleFunction: getLanguageTitle |
| 364 } |
| 365 ]); |
| 366 collections.more = new Collection([ |
| 367 { |
| 368 id: "more-list-table", |
| 369 setEmptyAction: "hide-more-filters-section", |
| 370 removeEmptyAction: "show-more-filters-section" |
| 371 } |
| 372 ]); |
| 373 collections.whitelist = new Collection([ |
| 374 { |
| 375 id: "whitelisting-table", |
| 376 emptyTexts: ["options_whitelist_empty_1", "options_whitelist_empty_2"] |
| 377 } |
| 378 ]); |
| 379 collections.filterLists = new Collection([ |
| 380 { |
| 381 id: "all-filter-lists-table", |
| 382 emptyTexts: ["options_filterList_empty"] |
| 383 } |
| 384 ]); |
| 385 |
| 386 function addSubscription(subscription) |
| 387 { |
| 388 let {disabled} = subscription; |
| 389 let collection = null; |
| 390 if (subscription.recommended) |
| 391 { |
| 392 if (subscription.recommended == "ads") |
| 393 { |
| 394 if (disabled == false) |
| 395 collection = collections.langs; |
| 396 |
| 397 collections.allLangs.addItem(subscription); |
| 398 } |
| 399 else |
| 400 { |
| 401 collection = collections.protection; |
| 402 } |
| 403 } |
| 404 else if (!isAcceptableAds(subscription.url) && disabled == false) |
| 405 { |
| 406 collection = collections.more; |
| 407 } |
| 408 |
| 409 if (collection) |
| 410 collection.addItem(subscription); |
| 411 |
| 412 subscriptionsMap[subscription.url] = subscription; |
| 413 updateTooltips(); |
| 414 } |
| 415 |
| 416 function updateSubscription(subscription) |
| 417 { |
| 418 for (let name in collections) |
| 419 collections[name].updateItem(subscription); |
| 420 |
| 421 if (subscription.recommended == "ads") |
| 422 { |
| 423 if (subscription.disabled) |
| 424 collections.langs.removeItem(subscription); |
| 425 else |
| 426 collections.langs.addItem(subscription); |
| 427 } |
| 428 else if (!subscription.recommended && !isAcceptableAds(subscription.url)) |
| 429 { |
| 430 if (subscription.disabled == false) |
| 431 { |
| 432 collections.more.addItem(subscription); |
| 433 updateTooltips(); |
| 434 } |
| 435 else |
| 436 { |
| 437 collections.more.removeItem(subscription); |
| 438 } |
| 439 } |
| 440 } |
| 441 |
| 442 function updateFilter(filter) |
| 443 { |
| 444 let match = filter.text.match(whitelistedDomainRegexp); |
| 445 if (match && !filtersMap[filter.text]) |
| 446 { |
| 447 filter.title = match[1]; |
| 448 collections.whitelist.addItem(filter); |
| 449 if (isCustomFiltersLoaded) |
| 450 { |
| 451 let text = getMessage("options_whitelist_notification", [filter.title]); |
| 452 showNotification(text); |
| 453 } |
| 454 } |
| 455 else |
| 456 { |
| 457 customFilters.push(filter.text); |
| 458 if (isCustomFiltersLoaded) |
| 459 updateCustomFiltersUi(); |
| 460 } |
| 461 |
| 462 filtersMap[filter.text] = filter; |
| 463 } |
| 464 |
| 465 function loadCustomFilters(filters) |
| 466 { |
| 467 for (let filter of filters) |
| 468 updateFilter(filter); |
| 469 |
| 470 setCustomFiltersView("read"); |
| 471 isCustomFiltersLoaded = true; |
| 472 } |
| 473 |
| 474 function removeCustomFilter(text) |
| 475 { |
| 476 let index = customFilters.indexOf(text); |
| 477 if (index >= 0) |
| 478 customFilters.splice(index, 1); |
| 479 |
| 480 updateCustomFiltersUi(); |
| 481 } |
| 482 |
| 483 function updateCustomFiltersUi() |
| 484 { |
| 485 let customFiltersListElement = E("custom-filters-raw"); |
| 486 customFiltersListElement.value = customFilters.join("\n"); |
| 487 } |
| 488 |
| 489 function getLanguageTitle(item) |
| 490 { |
| 491 let title = item.specialization; |
| 492 if (item.originalTitle && item.originalTitle.indexOf("+EasyList") > -1) |
| 493 title += " + " + getMessage("options_english"); |
| 494 return title; |
| 495 } |
| 496 |
| 497 function loadRecommendations() |
| 498 { |
| 499 fetch("subscriptions.xml") |
| 500 .then((response) => |
| 501 { |
| 502 return response.text(); |
| 503 }) |
| 504 .then((text) => |
| 505 { |
| 506 let doc = new DOMParser().parseFromString(text, "application/xml"); |
| 507 let elements = doc.documentElement.getElementsByTagName("subscription"); |
| 508 for (let element of elements) |
| 509 { |
| 510 let type = element.getAttribute("type"); |
| 511 let subscription = { |
| 512 disabled: true, |
| 513 downloadStatus: null, |
| 514 homepage: null, |
| 515 specialization: element.getAttribute("specialization"), |
| 516 originalTitle: element.getAttribute("title"), |
| 517 recommended: type, |
| 518 url: element.getAttribute("url") |
| 519 }; |
| 520 |
| 521 if (subscription.recommended != "ads") |
| 522 { |
| 523 type = type.replace(/\W/g, "_"); |
| 524 subscription.title = getMessage("common_feature_" + |
| 525 type + "_title"); |
| 526 } |
| 527 |
| 528 addSubscription(subscription); |
| 529 } |
| 530 }); |
| 531 } |
| 532 |
| 533 function findParentData(element, dataName, returnElement) |
| 534 { |
| 535 element = element.closest(`[data-${dataName}]`); |
| 536 if (!element) |
| 537 return null; |
| 538 if (returnElement) |
| 539 return element; |
| 540 return element.getAttribute(`data-${dataName}`); |
| 541 } |
| 542 |
| 543 function sendMessageHandleErrors(message, onSuccess) |
| 544 { |
| 545 browser.runtime.sendMessage(message, (errors) => |
| 546 { |
| 547 if (errors.length > 0) |
| 548 alert(errors.join("\n")); |
| 549 else if (onSuccess) |
| 550 onSuccess(); |
| 551 }); |
| 552 } |
| 553 |
| 554 function switchTab(id) |
| 555 { |
| 556 location.hash = id; |
| 557 } |
| 558 |
| 559 function execAction(action, element) |
| 560 { |
| 561 if (element.getAttribute("aria-disabled") == "true") |
| 562 return; |
| 563 |
| 564 switch (action) |
| 565 { |
| 566 case "add-domain-exception": |
| 567 addWhitelistedDomain(); |
| 568 break; |
| 569 case "add-language-subscription": |
| 570 addEnableSubscription(findParentData(element, "access", false)); |
| 571 break; |
| 572 case "add-predefined-subscription": { |
| 573 let dialog = E("dialog-content-predefined"); |
| 574 let title = dialog.querySelector("h3").textContent; |
| 575 let url = dialog.querySelector(".url").textContent; |
| 576 addEnableSubscription(url, title); |
| 577 closeDialog(); |
| 578 break; |
| 579 } |
| 580 case "cancel-custom-filters": |
| 581 setCustomFiltersView("read"); |
| 582 break; |
| 583 case "change-language-subscription": |
| 584 for (let key in subscriptionsMap) |
| 585 { |
| 586 let subscription = subscriptionsMap[key]; |
| 587 let subscriptionType = subscription.recommended; |
| 588 if (subscriptionType == "ads" && subscription.disabled == false) |
| 706 { | 589 { |
| 707 browser.runtime.sendMessage({ | 590 browser.runtime.sendMessage({ |
| 708 type: "subscriptions.remove", | 591 type: "subscriptions.remove", |
| 709 url: subscriptionUrl | 592 url: subscription.url |
| 710 }); | 593 }); |
| 594 browser.runtime.sendMessage({ |
| 595 type: "subscriptions.add", |
| 596 url: findParentData(element, "access", false) |
| 597 }); |
| 598 break; |
| 711 } | 599 } |
| 712 else | 600 } |
| 713 addEnableSubscription(subscriptionUrl); | 601 break; |
| 602 case "close-dialog": |
| 603 closeDialog(); |
| 604 break; |
| 605 case "edit-custom-filters": |
| 606 setCustomFiltersView("write"); |
| 607 break; |
| 608 case "hide-more-filters-section": |
| 609 E("more-filters").setAttribute("aria-hidden", true); |
| 610 break; |
| 611 case "hide-notification": |
| 612 hideNotification(); |
| 613 break; |
| 614 case "import-subscription": { |
| 615 let url = E("blockingList-textbox").value; |
| 616 addEnableSubscription(url); |
| 617 closeDialog(); |
| 618 break; |
| 619 } |
| 620 case "open-context-menu": { |
| 621 let listItem = findParentData(element, "access", true); |
| 622 if (listItem && !listItem.classList.contains("show-context-menu")) |
| 623 listItem.classList.add("show-context-menu"); |
| 624 break; |
| 625 } |
| 626 case "open-dialog": { |
| 627 let dialog = findParentData(element, "dialog", false); |
| 628 openDialog(dialog); |
| 629 break; |
| 630 } |
| 631 case "remove-filter": |
| 632 browser.runtime.sendMessage({ |
| 633 type: "filters.remove", |
| 634 text: findParentData(element, "access", false) |
| 635 }); |
| 636 break; |
| 637 case "remove-subscription": |
| 638 browser.runtime.sendMessage({ |
| 639 type: "subscriptions.remove", |
| 640 url: findParentData(element, "access", false) |
| 641 }); |
| 642 break; |
| 643 case "save-custom-filters": |
| 644 sendMessageHandleErrors({ |
| 645 type: "filters.importRaw", |
| 646 text: E("custom-filters-raw").value, |
| 647 removeExisting: true |
| 648 }, |
| 649 () => |
| 650 { |
| 651 setCustomFiltersView("read"); |
| 652 }); |
| 653 break; |
| 654 case "show-more-filters-section": |
| 655 E("more-filters").setAttribute("aria-hidden", false); |
| 656 break; |
| 657 case "switch-acceptable-ads": |
| 658 let value = element.value || element.dataset.value; |
| 659 // User check the checkbox |
| 660 let shouldCheck = element.getAttribute("aria-checked") != "true"; |
| 661 let installAcceptableAds = false; |
| 662 let installAcceptableAdsPrivacy = false; |
| 663 // Acceptable Ads checkbox clicked |
| 664 if (value == "ads") |
| 665 { |
| 666 installAcceptableAds = shouldCheck; |
| 667 } |
| 668 // Privacy Friendly Acceptable Ads checkbox clicked |
| 669 else |
| 670 { |
| 671 installAcceptableAdsPrivacy = shouldCheck; |
| 672 installAcceptableAds = !shouldCheck; |
| 673 } |
| 674 |
| 675 browser.runtime.sendMessage({ |
| 676 type: installAcceptableAds ? "subscriptions.add" : |
| 677 "subscriptions.remove", |
| 678 url: acceptableAdsUrl |
| 679 }); |
| 680 browser.runtime.sendMessage({ |
| 681 type: installAcceptableAdsPrivacy ? "subscriptions.add" : |
| 682 "subscriptions.remove", |
| 683 url: acceptableAdsPrivacyUrl |
| 684 }); |
| 685 break; |
| 686 case "switch-tab": |
| 687 switchTab(element.getAttribute("href").substr(1)); |
| 688 break; |
| 689 case "toggle-disable-subscription": |
| 690 browser.runtime.sendMessage({ |
| 691 type: "subscriptions.toggle", |
| 692 keepInstalled: true, |
| 693 url: findParentData(element, "access", false) |
| 694 }); |
| 695 break; |
| 696 case "toggle-pref": |
| 697 browser.runtime.sendMessage({ |
| 698 type: "prefs.toggle", |
| 699 key: findParentData(element, "pref", false) |
| 700 }); |
| 701 break; |
| 702 case "toggle-remove-subscription": |
| 703 let subscriptionUrl = findParentData(element, "access", false); |
| 704 if (element.getAttribute("aria-checked") == "true") |
| 705 { |
| 706 browser.runtime.sendMessage({ |
| 707 type: "subscriptions.remove", |
| 708 url: subscriptionUrl |
| 709 }); |
| 710 } |
| 711 else |
| 712 addEnableSubscription(subscriptionUrl); |
| 713 break; |
| 714 case "update-all-subscriptions": |
| 715 browser.runtime.sendMessage({ |
| 716 type: "subscriptions.update" |
| 717 }); |
| 718 break; |
| 719 case "update-subscription": |
| 720 browser.runtime.sendMessage({ |
| 721 type: "subscriptions.update", |
| 722 url: findParentData(element, "access", false) |
| 723 }); |
| 724 break; |
| 725 case "validate-import-subscription": |
| 726 let form = findParentData(element, "validation", true); |
| 727 if (!form) |
| 728 return; |
| 729 |
| 730 if (form.checkValidity()) |
| 731 { |
| 732 addEnableSubscription(E("import-list-url").value, |
| 733 E("import-list-title").value); |
| 734 form.reset(); |
| 735 closeDialog(); |
| 736 } |
| 737 else |
| 738 { |
| 739 form.querySelector(":invalid").focus(); |
| 740 } |
| 741 break; |
| 742 } |
| 743 } |
| 744 |
| 745 function setCustomFiltersView(mode) |
| 746 { |
| 747 let customFiltersElement = E("custom-filters-raw"); |
| 748 updateCustomFiltersUi(); |
| 749 if (mode == "read") |
| 750 { |
| 751 customFiltersElement.disabled = true; |
| 752 if (!customFiltersElement.value) |
| 753 { |
| 754 setCustomFiltersView("empty"); |
| 755 return; |
| 756 } |
| 757 } |
| 758 else if (mode == "write") |
| 759 { |
| 760 customFiltersElement.disabled = false; |
| 761 } |
| 762 |
| 763 E("custom-filters").dataset.mode = mode; |
| 764 } |
| 765 |
| 766 function onClick(e) |
| 767 { |
| 768 let context = document.querySelector(".show-context-menu"); |
| 769 if (context) |
| 770 context.classList.remove("show-context-menu"); |
| 771 |
| 772 let actions = findParentData(e.target, "action", false); |
| 773 if (!actions) |
| 774 return; |
| 775 |
| 776 actions = actions.split(","); |
| 777 for (let action of actions) |
| 778 { |
| 779 execAction(action, e.target); |
| 780 } |
| 781 } |
| 782 |
| 783 function getKey(e) |
| 784 { |
| 785 // e.keyCode has been deprecated so we attempt to use e.key |
| 786 if ("key" in e) |
| 787 return e.key; |
| 788 return getKey.keys[e.keyCode]; |
| 789 } |
| 790 getKey.keys = { |
| 791 9: "Tab", |
| 792 13: "Enter", |
| 793 27: "Escape", |
| 794 37: "ArrowLeft", |
| 795 38: "ArrowUp", |
| 796 39: "ArrowRight", |
| 797 40: "ArrowDown" |
| 798 }; |
| 799 |
| 800 function onKeyUp(e) |
| 801 { |
| 802 let key = getKey(e); |
| 803 let element = document.activeElement; |
| 804 if (!key || !element) |
| 805 return; |
| 806 |
| 807 let container = findParentData(element, "action", true); |
| 808 if (!container || !container.hasAttribute("data-keys")) |
| 809 return; |
| 810 |
| 811 let keys = container.getAttribute("data-keys").split(" "); |
| 812 if (keys.indexOf(key) < 0) |
| 813 return; |
| 814 |
| 815 if (element.getAttribute("role") == "tab") |
| 816 { |
| 817 let parent = element.parentElement; |
| 818 if (key == "ArrowLeft" || key == "ArrowUp") |
| 819 parent = parent.previousElementSibling || container.lastElementChild; |
| 820 else if (key == "ArrowRight" || key == "ArrowDown") |
| 821 parent = parent.nextElementSibling || container.firstElementChild; |
| 822 element = parent.firstElementChild; |
| 823 } |
| 824 |
| 825 let actions = container.getAttribute("data-action").split(","); |
| 826 for (let action of actions) |
| 827 { |
| 828 execAction(action, element); |
| 829 } |
| 830 } |
| 831 |
| 832 function selectTabItem(tabId, container, focus) |
| 833 { |
| 834 // Show tab content |
| 835 document.body.setAttribute("data-tab", tabId); |
| 836 |
| 837 // Select tab |
| 838 let tabList = container.querySelector("[role='tablist']"); |
| 839 if (!tabList) |
| 840 return null; |
| 841 |
| 842 let previousTab = tabList.querySelector("[aria-selected]"); |
| 843 previousTab.removeAttribute("aria-selected"); |
| 844 previousTab.setAttribute("tabindex", -1); |
| 845 |
| 846 let tab = tabList.querySelector("a[href='#" + tabId + "']"); |
| 847 tab.setAttribute("aria-selected", true); |
| 848 tab.setAttribute("tabindex", 0); |
| 849 |
| 850 let tabContentId = tab.getAttribute("aria-controls"); |
| 851 let tabContent = document.getElementById(tabContentId); |
| 852 |
| 853 if (tab && focus) |
| 854 tab.focus(); |
| 855 |
| 856 return tabContent; |
| 857 } |
| 858 |
| 859 function onHashChange() |
| 860 { |
| 861 let hash = location.hash.substr(1); |
| 862 if (!hash) |
| 863 return; |
| 864 |
| 865 // Select tab and parent tabs |
| 866 let tabIds = hash.split("-"); |
| 867 let tabContent = document.body; |
| 868 for (let i = 0; i < tabIds.length; i++) |
| 869 { |
| 870 let tabId = tabIds.slice(0, i + 1).join("-"); |
| 871 tabContent = selectTabItem(tabId, tabContent, true); |
| 872 if (!tabContent) |
| 873 break; |
| 874 } |
| 875 } |
| 876 |
| 877 function onDOMLoaded() |
| 878 { |
| 879 populateLists(); |
| 880 |
| 881 // Initialize navigation sidebar |
| 882 browser.runtime.sendMessage({ |
| 883 type: "app.get", |
| 884 what: "addonVersion" |
| 885 }, |
| 886 (addonVersion) => |
| 887 { |
| 888 E("abp-version").textContent = getMessage("options_dialog_about_version", |
| 889 [addonVersion]); |
| 890 }); |
| 891 |
| 892 updateTooltips(); |
| 893 |
| 894 // Initialize interactive UI elements |
| 895 document.body.addEventListener("click", onClick, false); |
| 896 document.body.addEventListener("keyup", onKeyUp, false); |
| 897 let exampleValue = getMessage("options_whitelist_placeholder_example", |
| 898 ["www.example.com"]); |
| 899 E("whitelisting-textbox").setAttribute("placeholder", exampleValue); |
| 900 E("whitelisting-textbox").addEventListener("keyup", (e) => |
| 901 { |
| 902 E("whitelisting-add-button").disabled = !e.target.value; |
| 903 }, false); |
| 904 |
| 905 // General tab |
| 906 getDocLink("contribute", (link) => |
| 907 { |
| 908 E("contribute").href = link; |
| 909 }); |
| 910 getDocLink("acceptable_ads_criteria", (link) => |
| 911 { |
| 912 setLinks("enable-acceptable-ads-description", link); |
| 913 }); |
| 914 setElementText(E("tracking-warning-1"), "options_tracking_warning_1", |
| 915 [getMessage("common_feature_privacy_title"), |
| 916 getMessage("options_acceptableAds_ads_label")]); |
| 917 setElementText(E("tracking-warning-3"), "options_tracking_warning_3", |
| 918 [getMessage("options_acceptableAds_privacy_label")]); |
| 919 |
| 920 getDocLink("privacy_friendly_ads", (link) => |
| 921 { |
| 922 E("enable-acceptable-ads-privacy-description").href = link; |
| 923 }); |
| 924 getDocLink("adblock_plus_{browser}_dnt", url => |
| 925 { |
| 926 setLinks("dnt", url); |
| 927 }); |
| 928 |
| 929 // Whitelisted tab |
| 930 getDocLink("whitelist", (link) => |
| 931 { |
| 932 E("whitelist-learn-more").href = link; |
| 933 }); |
| 934 |
| 935 // Advanced tab |
| 936 let customize = document.querySelectorAll("#customize li[data-pref]"); |
| 937 customize = Array.prototype.map.call(customize, (checkbox) => |
| 938 { |
| 939 return checkbox.getAttribute("data-pref"); |
| 940 }); |
| 941 for (let key of customize) |
| 942 { |
| 943 getPref(key, (value) => |
| 944 { |
| 945 onPrefMessage(key, value, true); |
| 946 }); |
| 947 } |
| 948 browser.runtime.sendMessage({ |
| 949 type: "app.get", |
| 950 what: "features" |
| 951 }, |
| 952 (features) => |
| 953 { |
| 954 hidePref("show_devtools_panel", !features.devToolsPanel); |
| 955 }); |
| 956 |
| 957 getDocLink("filterdoc", (link) => |
| 958 { |
| 959 E("link-filters").setAttribute("href", link); |
| 960 }); |
| 961 |
| 962 getDocLink("subscriptions", (link) => |
| 963 { |
| 964 E("filter-lists-learn-more").setAttribute("href", link); |
| 965 }); |
| 966 |
| 967 E("custom-filters-raw").setAttribute("placeholder", |
| 968 getMessage("options_customFilters_edit_placeholder", ["/ads/track/*"])); |
| 969 |
| 970 // Help tab |
| 971 getDocLink("adblock_plus_report_bug", (link) => |
| 972 { |
| 973 setLinks("report-bug", link); |
| 974 }); |
| 975 getDocLink("{browser}_support", url => |
| 976 { |
| 977 setLinks("visit-forum", url); |
| 978 }); |
| 979 getDocLink("social_twitter", (link) => |
| 980 { |
| 981 E("twitter").setAttribute("href", link); |
| 982 }); |
| 983 getDocLink("social_facebook", (link) => |
| 984 { |
| 985 E("facebook").setAttribute("href", link); |
| 986 }); |
| 987 getDocLink("social_gplus", (link) => |
| 988 { |
| 989 E("google-plus").setAttribute("href", link); |
| 990 }); |
| 991 getDocLink("social_weibo", (link) => |
| 992 { |
| 993 E("weibo").setAttribute("href", link); |
| 994 }); |
| 995 |
| 996 E("dialog").addEventListener("keydown", function(e) |
| 997 { |
| 998 switch (getKey(e)) |
| 999 { |
| 1000 case "Escape": |
| 1001 closeDialog(); |
| 714 break; | 1002 break; |
| 715 case "update-all-subscriptions": | 1003 case "Tab": |
| 716 browser.runtime.sendMessage({ | 1004 if (e.shiftKey) |
| 717 type: "subscriptions.update" | 1005 { |
| 718 }); | 1006 if (e.target.classList.contains("focus-first")) |
| 1007 { |
| 1008 e.preventDefault(); |
| 1009 this.querySelector(".focus-last").focus(); |
| 1010 } |
| 1011 } |
| 1012 else if (e.target.classList.contains("focus-last")) |
| 1013 { |
| 1014 e.preventDefault(); |
| 1015 this.querySelector(".focus-first").focus(); |
| 1016 } |
| 719 break; | 1017 break; |
| 720 case "update-subscription": | 1018 } |
| 721 browser.runtime.sendMessage({ | 1019 }, false); |
| 722 type: "subscriptions.update", | 1020 |
| 723 url: findParentData(element, "access", false) | 1021 onHashChange(); |
| 724 }); | 1022 } |
| 725 break; | 1023 |
| 726 case "validate-import-subscription": | 1024 let focusedBeforeDialog = null; |
| 727 let form = findParentData(element, "validation", true); | 1025 function openDialog(name) |
| 728 if (!form) | 1026 { |
| 729 return; | 1027 let dialog = E("dialog"); |
| 730 | 1028 dialog.setAttribute("aria-hidden", false); |
| 731 if (form.checkValidity()) | 1029 dialog.setAttribute("aria-labelledby", "dialog-title-" + name); |
| 1030 document.body.setAttribute("data-dialog", name); |
| 1031 |
| 1032 let defaultFocus = document.querySelector( |
| 1033 "#dialog-content-" + name + " .default-focus" |
| 1034 ); |
| 1035 if (!defaultFocus) |
| 1036 defaultFocus = dialog.querySelector(".focus-first"); |
| 1037 focusedBeforeDialog = document.activeElement; |
| 1038 defaultFocus.focus(); |
| 1039 } |
| 1040 |
| 1041 function closeDialog() |
| 1042 { |
| 1043 let dialog = E("dialog"); |
| 1044 dialog.setAttribute("aria-hidden", true); |
| 1045 dialog.removeAttribute("aria-labelledby"); |
| 1046 document.body.removeAttribute("data-dialog"); |
| 1047 focusedBeforeDialog.focus(); |
| 1048 } |
| 1049 |
| 1050 function showNotification(text) |
| 1051 { |
| 1052 E("notification").setAttribute("aria-hidden", false); |
| 1053 E("notification-text").textContent = text; |
| 1054 setTimeout(hideNotification, 3000); |
| 1055 } |
| 1056 |
| 1057 function hideNotification() |
| 1058 { |
| 1059 E("notification").setAttribute("aria-hidden", true); |
| 1060 E("notification-text").textContent = ""; |
| 1061 } |
| 1062 |
| 1063 function setAcceptableAds() |
| 1064 { |
| 1065 let acceptableAdsForm = E("acceptable-ads"); |
| 1066 let acceptableAds = E("acceptable-ads-allow"); |
| 1067 let acceptableAdsPrivacy = E("acceptable-ads-privacy-allow"); |
| 1068 acceptableAdsForm.classList.remove("show-dnt-notification"); |
| 1069 acceptableAds.setAttribute("aria-checked", false); |
| 1070 acceptableAdsPrivacy.setAttribute("aria-checked", false); |
| 1071 acceptableAdsPrivacy.setAttribute("tabindex", 0); |
| 1072 if (acceptableAdsUrl in subscriptionsMap) |
| 1073 { |
| 1074 acceptableAds.setAttribute("aria-checked", true); |
| 1075 acceptableAdsPrivacy.setAttribute("aria-disabled", false); |
| 1076 } |
| 1077 else if (acceptableAdsPrivacyUrl in subscriptionsMap) |
| 1078 { |
| 1079 acceptableAds.setAttribute("aria-checked", true); |
| 1080 acceptableAdsPrivacy.setAttribute("aria-checked", true); |
| 1081 acceptableAdsPrivacy.setAttribute("aria-disabled", false); |
| 1082 |
| 1083 // Edge uses window instead of navigator. |
| 1084 // Prefer navigator first since it's the standard. |
| 1085 if ((navigator.doNotTrack || window.doNotTrack) != 1) |
| 1086 acceptableAdsForm.classList.add("show-dnt-notification"); |
| 1087 } |
| 1088 else |
| 1089 { |
| 1090 // Using aria-disabled in order to keep the focus |
| 1091 acceptableAdsPrivacy.setAttribute("aria-disabled", true); |
| 1092 acceptableAdsPrivacy.setAttribute("tabindex", -1); |
| 1093 } |
| 1094 } |
| 1095 |
| 1096 function isAcceptableAds(url) |
| 1097 { |
| 1098 return url == acceptableAdsUrl || url == acceptableAdsPrivacyUrl; |
| 1099 } |
| 1100 |
| 1101 function hasPrivacyConflict() |
| 1102 { |
| 1103 let acceptableAdsList = subscriptionsMap[acceptableAdsUrl]; |
| 1104 let privacyList = null; |
| 1105 for (let url in subscriptionsMap) |
| 1106 { |
| 1107 let subscription = subscriptionsMap[url]; |
| 1108 if (subscription.recommended == "privacy") |
| 1109 { |
| 1110 privacyList = subscription; |
| 1111 break; |
| 1112 } |
| 1113 } |
| 1114 return acceptableAdsList && acceptableAdsList.disabled == false && |
| 1115 privacyList && privacyList.disabled == false; |
| 1116 } |
| 1117 |
| 1118 function setPrivacyConflict() |
| 1119 { |
| 1120 let acceptableAdsForm = E("acceptable-ads"); |
| 1121 if (hasPrivacyConflict()) |
| 1122 { |
| 1123 getPref("ui_warn_tracking", (showTrackingWarning) => |
| 1124 { |
| 1125 acceptableAdsForm.classList.toggle("show-warning", showTrackingWarning); |
| 1126 }); |
| 1127 } |
| 1128 else |
| 1129 { |
| 1130 acceptableAdsForm.classList.remove("show-warning"); |
| 1131 } |
| 1132 } |
| 1133 |
| 1134 function populateLists() |
| 1135 { |
| 1136 subscriptionsMap = Object.create(null); |
| 1137 filtersMap = Object.create(null); |
| 1138 |
| 1139 // Empty collections and lists |
| 1140 for (let property in collections) |
| 1141 collections[property].clearAll(); |
| 1142 |
| 1143 setCustomFiltersView("empty"); |
| 1144 browser.runtime.sendMessage({ |
| 1145 type: "subscriptions.get", |
| 1146 special: true |
| 1147 }, |
| 1148 (subscriptions) => |
| 1149 { |
| 1150 // Load filters |
| 1151 for (let subscription of subscriptions) |
| 1152 { |
| 1153 browser.runtime.sendMessage({ |
| 1154 type: "filters.get", |
| 1155 subscriptionUrl: subscription.url |
| 1156 }, |
| 1157 (filters) => |
| 1158 { |
| 1159 loadCustomFilters(filters); |
| 1160 }); |
| 1161 } |
| 1162 }); |
| 1163 loadRecommendations(); |
| 1164 browser.runtime.sendMessage({ |
| 1165 type: "prefs.get", |
| 1166 key: "subscriptions_exceptionsurl" |
| 1167 }, |
| 1168 (url) => |
| 1169 { |
| 1170 acceptableAdsUrl = url; |
| 1171 |
| 1172 browser.runtime.sendMessage({ |
| 1173 type: "prefs.get", |
| 1174 key: "subscriptions_exceptionsurl_privacy" |
| 1175 }, |
| 1176 (urlPrivacy) => |
| 1177 { |
| 1178 acceptableAdsPrivacyUrl = urlPrivacy; |
| 1179 |
| 1180 // Load user subscriptions |
| 1181 browser.runtime.sendMessage({ |
| 1182 type: "subscriptions.get", |
| 1183 downloadable: true |
| 1184 }, |
| 1185 (subscriptions) => |
| 1186 { |
| 1187 for (let subscription of subscriptions) |
| 1188 onSubscriptionMessage("added", subscription); |
| 1189 |
| 1190 setAcceptableAds(); |
| 1191 }); |
| 1192 }); |
| 1193 }); |
| 1194 } |
| 1195 |
| 1196 function addWhitelistedDomain() |
| 1197 { |
| 1198 let domain = E("whitelisting-textbox"); |
| 1199 for (let whitelistItem of collections.whitelist.items) |
| 1200 { |
| 1201 if (whitelistItem.title == domain.value) |
| 1202 { |
| 1203 whitelistItem[timestampUI] = Date.now(); |
| 1204 collections.whitelist.updateItem(whitelistItem); |
| 1205 domain.value = ""; |
| 1206 break; |
| 1207 } |
| 1208 } |
| 1209 const value = domain.value.trim(); |
| 1210 if (value) |
| 1211 { |
| 1212 const host = /^https?:\/\//i.test(value) ? new URL(value).host : value; |
| 1213 sendMessageHandleErrors({ |
| 1214 type: "filters.add", |
| 1215 text: "@@||" + host.toLowerCase() + "^$document" |
| 1216 }); |
| 1217 } |
| 1218 |
| 1219 domain.value = ""; |
| 1220 E("whitelisting-add-button").disabled = true; |
| 1221 } |
| 1222 |
| 1223 function addEnableSubscription(url, title, homepage) |
| 1224 { |
| 1225 let messageType = null; |
| 1226 let knownSubscription = subscriptionsMap[url]; |
| 1227 if (knownSubscription && knownSubscription.disabled == true) |
| 1228 messageType = "subscriptions.toggle"; |
| 1229 else |
| 1230 messageType = "subscriptions.add"; |
| 1231 |
| 1232 let message = { |
| 1233 type: messageType, |
| 1234 url |
| 1235 }; |
| 1236 if (title) |
| 1237 message.title = title; |
| 1238 if (homepage) |
| 1239 message.homepage = homepage; |
| 1240 |
| 1241 browser.runtime.sendMessage(message); |
| 1242 } |
| 1243 |
| 1244 function onFilterMessage(action, filter) |
| 1245 { |
| 1246 switch (action) |
| 1247 { |
| 1248 case "added": |
| 1249 filter[timestampUI] = Date.now(); |
| 1250 updateFilter(filter); |
| 1251 break; |
| 1252 case "loaded": |
| 1253 populateLists(); |
| 1254 break; |
| 1255 case "removed": |
| 1256 let knownFilter = filtersMap[filter.text]; |
| 1257 if (whitelistedDomainRegexp.test(knownFilter.text)) |
| 1258 collections.whitelist.removeItem(knownFilter); |
| 1259 else |
| 1260 removeCustomFilter(filter.text); |
| 1261 |
| 1262 delete filtersMap[filter.text]; |
| 1263 break; |
| 1264 } |
| 1265 } |
| 1266 |
| 1267 function onSubscriptionMessage(action, subscription) |
| 1268 { |
| 1269 if (subscription.url in subscriptionsMap) |
| 1270 { |
| 1271 let knownSubscription = subscriptionsMap[subscription.url]; |
| 1272 for (let property in subscription) |
| 1273 { |
| 1274 if (property == "title" && knownSubscription.recommended) |
| 1275 knownSubscription.originalTitle = subscription.title; |
| 1276 else |
| 1277 knownSubscription[property] = subscription[property]; |
| 1278 } |
| 1279 subscription = knownSubscription; |
| 1280 } |
| 1281 switch (action) |
| 1282 { |
| 1283 case "disabled": |
| 1284 updateSubscription(subscription); |
| 1285 setPrivacyConflict(); |
| 1286 break; |
| 1287 case "downloading": |
| 1288 case "downloadStatus": |
| 1289 case "homepage": |
| 1290 case "lastDownload": |
| 1291 case "title": |
| 1292 updateSubscription(subscription); |
| 1293 break; |
| 1294 case "added": |
| 1295 let {url} = subscription; |
| 1296 // Handle custom subscription |
| 1297 if (/^~user/.test(url)) |
| 1298 { |
| 1299 loadCustomFilters(subscription.filters); |
| 1300 return; |
| 1301 } |
| 1302 else if (url in subscriptionsMap) |
| 1303 updateSubscription(subscription); |
| 1304 else |
| 1305 addSubscription(subscription); |
| 1306 |
| 1307 if (isAcceptableAds(url)) |
| 1308 setAcceptableAds(); |
| 1309 |
| 1310 collections.filterLists.addItem(subscription); |
| 1311 setPrivacyConflict(); |
| 1312 break; |
| 1313 case "removed": |
| 1314 if (subscription.recommended) |
| 1315 { |
| 1316 subscription.disabled = true; |
| 1317 onSubscriptionMessage("disabled", subscription); |
| 1318 } |
| 1319 else |
| 1320 { |
| 1321 delete subscriptionsMap[subscription.url]; |
| 1322 if (isAcceptableAds(subscription.url)) |
| 732 { | 1323 { |
| 733 addEnableSubscription(E("import-list-url").value, | 1324 setAcceptableAds(); |
| 734 E("import-list-title").value); | |
| 735 form.reset(); | |
| 736 closeDialog(); | |
| 737 } | 1325 } |
| 738 else | 1326 else |
| 739 { | 1327 { |
| 740 form.querySelector(":invalid").focus(); | 1328 collections.more.removeItem(subscription); |
| 741 } | 1329 } |
| 742 break; | 1330 } |
| 743 } | 1331 |
| 744 } | 1332 collections.filterLists.removeItem(subscription); |
| 745 | 1333 setPrivacyConflict(); |
| 746 function setCustomFiltersView(mode) | 1334 break; |
| 747 { | 1335 } |
| 748 let customFiltersElement = E("custom-filters-raw"); | 1336 } |
| 749 updateCustomFiltersUi(); | 1337 |
| 750 if (mode == "read") | 1338 function hidePref(key, value) |
| 751 { | 1339 { |
| 752 customFiltersElement.disabled = true; | 1340 let element = document.querySelector("[data-pref='" + key + "']"); |
| 753 if (!customFiltersElement.value) | 1341 if (element) |
| 754 { | 1342 element.setAttribute("aria-hidden", value); |
| 755 setCustomFiltersView("empty"); | 1343 } |
| 756 return; | 1344 |
| 757 } | 1345 function getPref(key, callback) |
| 758 } | 1346 { |
| 759 else if (mode == "write") | 1347 let checkPref = getPref.checks[key] || getPref.checkNone; |
| 760 { | 1348 checkPref((isActive) => |
| 761 customFiltersElement.disabled = false; | 1349 { |
| 762 } | 1350 if (!isActive) |
| 763 | 1351 { |
| 764 E("custom-filters").dataset.mode = mode; | 1352 hidePref(key, !isActive); |
| 765 } | |
| 766 | |
| 767 function onClick(e) | |
| 768 { | |
| 769 let context = document.querySelector(".show-context-menu"); | |
| 770 if (context) | |
| 771 context.classList.remove("show-context-menu"); | |
| 772 | |
| 773 let actions = findParentData(e.target, "action", false); | |
| 774 if (!actions) | |
| 775 return; | 1353 return; |
| 776 | 1354 } |
| 777 actions = actions.split(","); | 1355 |
| 778 for (let action of actions) | |
| 779 { | |
| 780 execAction(action, e.target); | |
| 781 } | |
| 782 } | |
| 783 | |
| 784 function getKey(e) | |
| 785 { | |
| 786 // e.keyCode has been deprecated so we attempt to use e.key | |
| 787 if ("key" in e) | |
| 788 return e.key; | |
| 789 return getKey.keys[e.keyCode]; | |
| 790 } | |
| 791 getKey.keys = { | |
| 792 9: "Tab", | |
| 793 13: "Enter", | |
| 794 27: "Escape", | |
| 795 37: "ArrowLeft", | |
| 796 38: "ArrowUp", | |
| 797 39: "ArrowRight", | |
| 798 40: "ArrowDown" | |
| 799 }; | |
| 800 | |
| 801 function onKeyUp(e) | |
| 802 { | |
| 803 let key = getKey(e); | |
| 804 let element = document.activeElement; | |
| 805 if (!key || !element) | |
| 806 return; | |
| 807 | |
| 808 let container = findParentData(element, "action", true); | |
| 809 if (!container || !container.hasAttribute("data-keys")) | |
| 810 return; | |
| 811 | |
| 812 let keys = container.getAttribute("data-keys").split(" "); | |
| 813 if (keys.indexOf(key) < 0) | |
| 814 return; | |
| 815 | |
| 816 if (element.getAttribute("role") == "tab") | |
| 817 { | |
| 818 let parent = element.parentElement; | |
| 819 if (key == "ArrowLeft" || key == "ArrowUp") | |
| 820 parent = parent.previousElementSibling || container.lastElementChild; | |
| 821 else if (key == "ArrowRight" || key == "ArrowDown") | |
| 822 parent = parent.nextElementSibling || container.firstElementChild; | |
| 823 element = parent.firstElementChild; | |
| 824 } | |
| 825 | |
| 826 let actions = container.getAttribute("data-action").split(","); | |
| 827 for (let action of actions) | |
| 828 { | |
| 829 execAction(action, element); | |
| 830 } | |
| 831 } | |
| 832 | |
| 833 function selectTabItem(tabId, container, focus) | |
| 834 { | |
| 835 // Show tab content | |
| 836 document.body.setAttribute("data-tab", tabId); | |
| 837 | |
| 838 // Select tab | |
| 839 let tabList = container.querySelector("[role='tablist']"); | |
| 840 if (!tabList) | |
| 841 return null; | |
| 842 | |
| 843 let previousTab = tabList.querySelector("[aria-selected]"); | |
| 844 previousTab.removeAttribute("aria-selected"); | |
| 845 previousTab.setAttribute("tabindex", -1); | |
| 846 | |
| 847 let tab = tabList.querySelector("a[href='#" + tabId + "']"); | |
| 848 tab.setAttribute("aria-selected", true); | |
| 849 tab.setAttribute("tabindex", 0); | |
| 850 | |
| 851 let tabContentId = tab.getAttribute("aria-controls"); | |
| 852 let tabContent = document.getElementById(tabContentId); | |
| 853 | |
| 854 if (tab && focus) | |
| 855 tab.focus(); | |
| 856 | |
| 857 return tabContent; | |
| 858 } | |
| 859 | |
| 860 function onHashChange() | |
| 861 { | |
| 862 let hash = location.hash.substr(1); | |
| 863 if (!hash) | |
| 864 return; | |
| 865 | |
| 866 // Select tab and parent tabs | |
| 867 let tabIds = hash.split("-"); | |
| 868 let tabContent = document.body; | |
| 869 for (let i = 0; i < tabIds.length; i++) | |
| 870 { | |
| 871 let tabId = tabIds.slice(0, i + 1).join("-"); | |
| 872 tabContent = selectTabItem(tabId, tabContent, true); | |
| 873 if (!tabContent) | |
| 874 break; | |
| 875 } | |
| 876 } | |
| 877 | |
| 878 function onDOMLoaded() | |
| 879 { | |
| 880 populateLists(); | |
| 881 | |
| 882 // Initialize navigation sidebar | |
| 883 browser.runtime.sendMessage({ | |
| 884 type: "app.get", | |
| 885 what: "addonVersion" | |
| 886 }, | |
| 887 (addonVersion) => | |
| 888 { | |
| 889 E("abp-version").textContent = getMessage("options_dialog_about_version", | |
| 890 [addonVersion]); | |
| 891 }); | |
| 892 | |
| 893 updateTooltips(); | |
| 894 | |
| 895 // Initialize interactive UI elements | |
| 896 document.body.addEventListener("click", onClick, false); | |
| 897 document.body.addEventListener("keyup", onKeyUp, false); | |
| 898 let exampleValue = getMessage("options_whitelist_placeholder_example", | |
| 899 ["www.example.com"]); | |
| 900 E("whitelisting-textbox").setAttribute("placeholder", exampleValue); | |
| 901 E("whitelisting-textbox").addEventListener("keyup", (e) => | |
| 902 { | |
| 903 E("whitelisting-add-button").disabled = !e.target.value; | |
| 904 }, false); | |
| 905 | |
| 906 // General tab | |
| 907 getDocLink("contribute", (link) => | |
| 908 { | |
| 909 E("contribute").href = link; | |
| 910 }); | |
| 911 getDocLink("acceptable_ads_criteria", (link) => | |
| 912 { | |
| 913 setLinks("enable-acceptable-ads-description", link); | |
| 914 }); | |
| 915 setElementText(E("tracking-warning-1"), "options_tracking_warning_1", | |
| 916 [getMessage("common_feature_privacy_title"), | |
| 917 getMessage("options_acceptableAds_ads_label")]); | |
| 918 setElementText(E("tracking-warning-3"), "options_tracking_warning_3", | |
| 919 [getMessage("options_acceptableAds_privacy_label")]); | |
| 920 | |
| 921 getDocLink("privacy_friendly_ads", (link) => | |
| 922 { | |
| 923 E("enable-acceptable-ads-privacy-description").href = link; | |
| 924 }); | |
| 925 getDocLink("adblock_plus_{browser}_dnt", url => | |
| 926 { | |
| 927 setLinks("dnt", url); | |
| 928 }); | |
| 929 | |
| 930 // Whitelisted tab | |
| 931 getDocLink("whitelist", (link) => | |
| 932 { | |
| 933 E("whitelist-learn-more").href = link; | |
| 934 }); | |
| 935 | |
| 936 // Advanced tab | |
| 937 let customize = document.querySelectorAll("#customize li[data-pref]"); | |
| 938 customize = Array.prototype.map.call(customize, (checkbox) => | |
| 939 { | |
| 940 return checkbox.getAttribute("data-pref"); | |
| 941 }); | |
| 942 for (let key of customize) | |
| 943 { | |
| 944 getPref(key, (value) => | |
| 945 { | |
| 946 onPrefMessage(key, value, true); | |
| 947 }); | |
| 948 } | |
| 949 browser.runtime.sendMessage({ | |
| 950 type: "app.get", | |
| 951 what: "features" | |
| 952 }, | |
| 953 (features) => | |
| 954 { | |
| 955 hidePref("show_devtools_panel", !features.devToolsPanel); | |
| 956 }); | |
| 957 | |
| 958 getDocLink("filterdoc", (link) => | |
| 959 { | |
| 960 E("link-filters").setAttribute("href", link); | |
| 961 }); | |
| 962 | |
| 963 getDocLink("subscriptions", (link) => | |
| 964 { | |
| 965 E("filter-lists-learn-more").setAttribute("href", link); | |
| 966 }); | |
| 967 | |
| 968 E("custom-filters-raw").setAttribute("placeholder", | |
| 969 getMessage("options_customFilters_edit_placeholder", ["/ads/track/*"])); | |
| 970 | |
| 971 // Help tab | |
| 972 getDocLink("adblock_plus_report_bug", (link) => | |
| 973 { | |
| 974 setLinks("report-bug", link); | |
| 975 }); | |
| 976 getDocLink("{browser}_support", url => | |
| 977 { | |
| 978 setLinks("visit-forum", url); | |
| 979 }); | |
| 980 getDocLink("social_twitter", (link) => | |
| 981 { | |
| 982 E("twitter").setAttribute("href", link); | |
| 983 }); | |
| 984 getDocLink("social_facebook", (link) => | |
| 985 { | |
| 986 E("facebook").setAttribute("href", link); | |
| 987 }); | |
| 988 getDocLink("social_gplus", (link) => | |
| 989 { | |
| 990 E("google-plus").setAttribute("href", link); | |
| 991 }); | |
| 992 getDocLink("social_weibo", (link) => | |
| 993 { | |
| 994 E("weibo").setAttribute("href", link); | |
| 995 }); | |
| 996 | |
| 997 E("dialog").addEventListener("keydown", function(e) | |
| 998 { | |
| 999 switch (getKey(e)) | |
| 1000 { | |
| 1001 case "Escape": | |
| 1002 closeDialog(); | |
| 1003 break; | |
| 1004 case "Tab": | |
| 1005 if (e.shiftKey) | |
| 1006 { | |
| 1007 if (e.target.classList.contains("focus-first")) | |
| 1008 { | |
| 1009 e.preventDefault(); | |
| 1010 this.querySelector(".focus-last").focus(); | |
| 1011 } | |
| 1012 } | |
| 1013 else if (e.target.classList.contains("focus-last")) | |
| 1014 { | |
| 1015 e.preventDefault(); | |
| 1016 this.querySelector(".focus-first").focus(); | |
| 1017 } | |
| 1018 break; | |
| 1019 } | |
| 1020 }, false); | |
| 1021 | |
| 1022 onHashChange(); | |
| 1023 } | |
| 1024 | |
| 1025 let focusedBeforeDialog = null; | |
| 1026 function openDialog(name) | |
| 1027 { | |
| 1028 let dialog = E("dialog"); | |
| 1029 dialog.setAttribute("aria-hidden", false); | |
| 1030 dialog.setAttribute("aria-labelledby", "dialog-title-" + name); | |
| 1031 document.body.setAttribute("data-dialog", name); | |
| 1032 | |
| 1033 let defaultFocus = document.querySelector( | |
| 1034 "#dialog-content-" + name + " .default-focus" | |
| 1035 ); | |
| 1036 if (!defaultFocus) | |
| 1037 defaultFocus = dialog.querySelector(".focus-first"); | |
| 1038 focusedBeforeDialog = document.activeElement; | |
| 1039 defaultFocus.focus(); | |
| 1040 } | |
| 1041 | |
| 1042 function closeDialog() | |
| 1043 { | |
| 1044 let dialog = E("dialog"); | |
| 1045 dialog.setAttribute("aria-hidden", true); | |
| 1046 dialog.removeAttribute("aria-labelledby"); | |
| 1047 document.body.removeAttribute("data-dialog"); | |
| 1048 focusedBeforeDialog.focus(); | |
| 1049 } | |
| 1050 | |
| 1051 function showNotification(text) | |
| 1052 { | |
| 1053 E("notification").setAttribute("aria-hidden", false); | |
| 1054 E("notification-text").textContent = text; | |
| 1055 setTimeout(hideNotification, 3000); | |
| 1056 } | |
| 1057 | |
| 1058 function hideNotification() | |
| 1059 { | |
| 1060 E("notification").setAttribute("aria-hidden", true); | |
| 1061 E("notification-text").textContent = ""; | |
| 1062 } | |
| 1063 | |
| 1064 function setAcceptableAds() | |
| 1065 { | |
| 1066 let acceptableAdsForm = E("acceptable-ads"); | |
| 1067 let acceptableAds = E("acceptable-ads-allow"); | |
| 1068 let acceptableAdsPrivacy = E("acceptable-ads-privacy-allow"); | |
| 1069 acceptableAdsForm.classList.remove("show-dnt-notification"); | |
| 1070 acceptableAds.setAttribute("aria-checked", false); | |
| 1071 acceptableAdsPrivacy.setAttribute("aria-checked", false); | |
| 1072 acceptableAdsPrivacy.setAttribute("tabindex", 0); | |
| 1073 if (acceptableAdsUrl in subscriptionsMap) | |
| 1074 { | |
| 1075 acceptableAds.setAttribute("aria-checked", true); | |
| 1076 acceptableAdsPrivacy.setAttribute("aria-disabled", false); | |
| 1077 } | |
| 1078 else if (acceptableAdsPrivacyUrl in subscriptionsMap) | |
| 1079 { | |
| 1080 acceptableAds.setAttribute("aria-checked", true); | |
| 1081 acceptableAdsPrivacy.setAttribute("aria-checked", true); | |
| 1082 acceptableAdsPrivacy.setAttribute("aria-disabled", false); | |
| 1083 | |
| 1084 // Edge uses window instead of navigator. | |
| 1085 // Prefer navigator first since it's the standard. | |
| 1086 if ((navigator.doNotTrack || window.doNotTrack) != 1) | |
| 1087 acceptableAdsForm.classList.add("show-dnt-notification"); | |
| 1088 } | |
| 1089 else | |
| 1090 { | |
| 1091 // Using aria-disabled in order to keep the focus | |
| 1092 acceptableAdsPrivacy.setAttribute("aria-disabled", true); | |
| 1093 acceptableAdsPrivacy.setAttribute("tabindex", -1); | |
| 1094 } | |
| 1095 } | |
| 1096 | |
| 1097 function isAcceptableAds(url) | |
| 1098 { | |
| 1099 return url == acceptableAdsUrl || url == acceptableAdsPrivacyUrl; | |
| 1100 } | |
| 1101 | |
| 1102 function hasPrivacyConflict() | |
| 1103 { | |
| 1104 let acceptableAdsList = subscriptionsMap[acceptableAdsUrl]; | |
| 1105 let privacyList = null; | |
| 1106 for (let url in subscriptionsMap) | |
| 1107 { | |
| 1108 let subscription = subscriptionsMap[url]; | |
| 1109 if (subscription.recommended == "privacy") | |
| 1110 { | |
| 1111 privacyList = subscription; | |
| 1112 break; | |
| 1113 } | |
| 1114 } | |
| 1115 return acceptableAdsList && acceptableAdsList.disabled == false && | |
| 1116 privacyList && privacyList.disabled == false; | |
| 1117 } | |
| 1118 | |
| 1119 function setPrivacyConflict() | |
| 1120 { | |
| 1121 let acceptableAdsForm = E("acceptable-ads"); | |
| 1122 if (hasPrivacyConflict()) | |
| 1123 { | |
| 1124 getPref("ui_warn_tracking", (showTrackingWarning) => | |
| 1125 { | |
| 1126 acceptableAdsForm.classList.toggle("show-warning", showTrackingWarning); | |
| 1127 }); | |
| 1128 } | |
| 1129 else | |
| 1130 { | |
| 1131 acceptableAdsForm.classList.remove("show-warning"); | |
| 1132 } | |
| 1133 } | |
| 1134 | |
| 1135 function populateLists() | |
| 1136 { | |
| 1137 subscriptionsMap = Object.create(null); | |
| 1138 filtersMap = Object.create(null); | |
| 1139 | |
| 1140 // Empty collections and lists | |
| 1141 for (let property in collections) | |
| 1142 collections[property].clearAll(); | |
| 1143 | |
| 1144 setCustomFiltersView("empty"); | |
| 1145 browser.runtime.sendMessage({ | |
| 1146 type: "subscriptions.get", | |
| 1147 special: true | |
| 1148 }, | |
| 1149 (subscriptions) => | |
| 1150 { | |
| 1151 // Load filters | |
| 1152 for (let subscription of subscriptions) | |
| 1153 { | |
| 1154 browser.runtime.sendMessage({ | |
| 1155 type: "filters.get", | |
| 1156 subscriptionUrl: subscription.url | |
| 1157 }, | |
| 1158 (filters) => | |
| 1159 { | |
| 1160 loadCustomFilters(filters); | |
| 1161 }); | |
| 1162 } | |
| 1163 }); | |
| 1164 loadRecommendations(); | |
| 1165 browser.runtime.sendMessage({ | 1356 browser.runtime.sendMessage({ |
| 1166 type: "prefs.get", | 1357 type: "prefs.get", |
| 1167 key: "subscriptions_exceptionsurl" | 1358 key |
| 1168 }, | 1359 }, callback); |
| 1169 (url) => | 1360 }); |
| 1170 { | 1361 } |
| 1171 acceptableAdsUrl = url; | 1362 |
| 1172 | 1363 getPref.checkNone = function(callback) |
| 1173 browser.runtime.sendMessage({ | 1364 { |
| 1174 type: "prefs.get", | 1365 callback(true); |
| 1175 key: "subscriptions_exceptionsurl_privacy" | 1366 }; |
| 1176 }, | 1367 |
| 1177 (urlPrivacy) => | 1368 getPref.checks = |
| 1178 { | 1369 { |
| 1179 acceptableAdsPrivacyUrl = urlPrivacy; | 1370 notifications_ignoredcategories(callback) |
| 1180 | 1371 { |
| 1181 // Load user subscriptions | 1372 getPref("notifications_showui", callback); |
| 1182 browser.runtime.sendMessage({ | 1373 } |
| 1183 type: "subscriptions.get", | 1374 }; |
| 1184 downloadable: true | 1375 |
| 1185 }, | 1376 function onPrefMessage(key, value, initial) |
| 1186 (subscriptions) => | 1377 { |
| 1187 { | 1378 switch (key) |
| 1188 for (let subscription of subscriptions) | 1379 { |
| 1189 onSubscriptionMessage("added", subscription); | 1380 case "notifications_ignoredcategories": |
| 1190 | 1381 value = value.indexOf("*") == -1; |
| 1191 setAcceptableAds(); | 1382 break; |
| 1192 }); | 1383 |
| 1193 }); | 1384 case "notifications_showui": |
| 1194 }); | 1385 hidePref("notifications_ignoredcategories", !value); |
| 1195 } | 1386 break; |
| 1196 | 1387 case "ui_warn_tracking": |
| 1197 function addWhitelistedDomain() | 1388 setPrivacyConflict(); |
| 1198 { | 1389 break; |
| 1199 let domain = E("whitelisting-textbox"); | 1390 } |
| 1200 for (let whitelistItem of collections.whitelist.items) | 1391 |
| 1201 { | 1392 let checkbox = document.querySelector( |
| 1202 if (whitelistItem.title == domain.value) | 1393 "[data-pref='" + key + "'] button[role='checkbox']" |
| 1203 { | 1394 ); |
| 1204 whitelistItem[timestampUI] = Date.now(); | 1395 if (checkbox) |
| 1205 collections.whitelist.updateItem(whitelistItem); | 1396 checkbox.setAttribute("aria-checked", value); |
| 1206 domain.value = ""; | 1397 } |
| 1207 break; | 1398 |
| 1208 } | 1399 function updateTooltips() |
| 1209 } | 1400 { |
| 1210 const value = domain.value.trim(); | 1401 let anchors = document.querySelectorAll(":not(.tooltip) > [data-tooltip]"); |
| 1211 if (value) | 1402 for (let anchor of anchors) |
| 1212 { | 1403 { |
| 1213 const host = /^https?:\/\//i.test(value) ? new URL(value).host : value; | 1404 let id = anchor.getAttribute("data-tooltip"); |
| 1214 sendMessageHandleErrors({ | 1405 |
| 1215 type: "filters.add", | 1406 let wrapper = document.createElement("div"); |
| 1216 text: "@@||" + host.toLowerCase() + "^$document" | 1407 wrapper.className = "icon tooltip"; |
| 1217 }); | 1408 anchor.parentNode.replaceChild(wrapper, anchor); |
| 1218 } | 1409 wrapper.appendChild(anchor); |
| 1219 | 1410 |
| 1220 domain.value = ""; | 1411 let tooltip = document.createElement("div"); |
| 1221 E("whitelisting-add-button").disabled = true; | 1412 tooltip.setAttribute("role", "tooltip"); |
| 1222 } | 1413 |
| 1223 | 1414 let paragraph = document.createElement("p"); |
| 1224 function addEnableSubscription(url, title, homepage) | 1415 paragraph.textContent = getMessage(id); |
| 1225 { | 1416 tooltip.appendChild(paragraph); |
| 1226 let messageType = null; | 1417 |
| 1227 let knownSubscription = subscriptionsMap[url]; | 1418 wrapper.appendChild(tooltip); |
| 1228 if (knownSubscription && knownSubscription.disabled == true) | 1419 } |
| 1229 messageType = "subscriptions.toggle"; | 1420 } |
| 1230 else | 1421 |
| 1231 messageType = "subscriptions.add"; | 1422 ext.onMessage.addListener((message) => |
| 1232 | 1423 { |
| 1233 let message = { | 1424 switch (message.type) |
| 1234 type: messageType, | 1425 { |
| 1235 url | 1426 case "app.respond": |
| 1236 }; | 1427 switch (message.action) |
| 1237 if (title) | 1428 { |
| 1238 message.title = title; | 1429 case "addSubscription": |
| 1239 if (homepage) | 1430 let subscription = message.args[0]; |
| 1240 message.homepage = homepage; | 1431 let dialog = E("dialog-content-predefined"); |
| 1241 | 1432 dialog.querySelector("h3").textContent = subscription.title || ""; |
| 1242 browser.runtime.sendMessage(message); | 1433 dialog.querySelector(".url").textContent = subscription.url; |
| 1243 } | 1434 openDialog("predefined"); |
| 1244 | 1435 break; |
| 1245 function onFilterMessage(action, filter) | 1436 case "focusSection": |
| 1246 { | 1437 document.body.setAttribute("data-tab", message.args[0]); |
| 1247 switch (action) | 1438 break; |
| 1248 { | 1439 } |
| 1249 case "added": | 1440 break; |
| 1250 filter[timestampUI] = Date.now(); | 1441 case "filters.respond": |
| 1251 updateFilter(filter); | 1442 onFilterMessage(message.action, message.args[0]); |
| 1252 break; | 1443 break; |
| 1253 case "loaded": | 1444 case "prefs.respond": |
| 1254 populateLists(); | 1445 onPrefMessage(message.action, message.args[0], false); |
| 1255 break; | 1446 break; |
| 1256 case "removed": | 1447 case "subscriptions.respond": |
| 1257 let knownFilter = filtersMap[filter.text]; | 1448 onSubscriptionMessage(message.action, message.args[0]); |
| 1258 if (whitelistedDomainRegexp.test(knownFilter.text)) | 1449 break; |
| 1259 collections.whitelist.removeItem(knownFilter); | 1450 } |
| 1260 else | 1451 }); |
| 1261 removeCustomFilter(filter.text); | 1452 |
| 1262 | 1453 browser.runtime.sendMessage({ |
| 1263 delete filtersMap[filter.text]; | 1454 type: "app.listen", |
| 1264 break; | 1455 filter: ["addSubscription", "focusSection"] |
| 1265 } | 1456 }); |
| 1266 } | 1457 browser.runtime.sendMessage({ |
| 1267 | 1458 type: "filters.listen", |
| 1268 function onSubscriptionMessage(action, subscription) | 1459 filter: ["added", "loaded", "removed"] |
| 1269 { | 1460 }); |
| 1270 if (subscription.url in subscriptionsMap) | 1461 browser.runtime.sendMessage({ |
| 1271 { | 1462 type: "prefs.listen", |
| 1272 let knownSubscription = subscriptionsMap[subscription.url]; | 1463 filter: ["notifications_ignoredcategories", "notifications_showui", |
| 1273 for (let property in subscription) | 1464 "show_devtools_panel", "shouldShowBlockElementMenu", |
| 1274 { | 1465 "ui_warn_tracking"] |
| 1275 if (property == "title" && knownSubscription.recommended) | 1466 }); |
| 1276 knownSubscription.originalTitle = subscription.title; | 1467 browser.runtime.sendMessage({ |
| 1277 else | 1468 type: "subscriptions.listen", |
| 1278 knownSubscription[property] = subscription[property]; | 1469 filter: ["added", "disabled", "homepage", "lastDownload", "removed", |
| 1279 } | 1470 "title", "downloadStatus", "downloading"] |
| 1280 subscription = knownSubscription; | 1471 }); |
| 1281 } | 1472 |
| 1282 switch (action) | 1473 window.addEventListener("DOMContentLoaded", onDOMLoaded, false); |
| 1283 { | 1474 window.addEventListener("hashchange", onHashChange, false); |
| 1284 case "disabled": | |
| 1285 updateSubscription(subscription); | |
| 1286 setPrivacyConflict(); | |
| 1287 break; | |
| 1288 case "downloading": | |
| 1289 case "downloadStatus": | |
| 1290 case "homepage": | |
| 1291 case "lastDownload": | |
| 1292 case "title": | |
| 1293 updateSubscription(subscription); | |
| 1294 break; | |
| 1295 case "added": | |
| 1296 let {url} = subscription; | |
| 1297 // Handle custom subscription | |
| 1298 if (/^~user/.test(url)) | |
| 1299 { | |
| 1300 loadCustomFilters(subscription.filters); | |
| 1301 return; | |
| 1302 } | |
| 1303 else if (url in subscriptionsMap) | |
| 1304 updateSubscription(subscription); | |
| 1305 else | |
| 1306 addSubscription(subscription); | |
| 1307 | |
| 1308 if (isAcceptableAds(url)) | |
| 1309 setAcceptableAds(); | |
| 1310 | |
| 1311 collections.filterLists.addItem(subscription); | |
| 1312 setPrivacyConflict(); | |
| 1313 break; | |
| 1314 case "removed": | |
| 1315 if (subscription.recommended) | |
| 1316 { | |
| 1317 subscription.disabled = true; | |
| 1318 onSubscriptionMessage("disabled", subscription); | |
| 1319 } | |
| 1320 else | |
| 1321 { | |
| 1322 delete subscriptionsMap[subscription.url]; | |
| 1323 if (isAcceptableAds(subscription.url)) | |
| 1324 { | |
| 1325 setAcceptableAds(); | |
| 1326 } | |
| 1327 else | |
| 1328 { | |
| 1329 collections.more.removeItem(subscription); | |
| 1330 } | |
| 1331 } | |
| 1332 | |
| 1333 collections.filterLists.removeItem(subscription); | |
| 1334 setPrivacyConflict(); | |
| 1335 break; | |
| 1336 } | |
| 1337 } | |
| 1338 | |
| 1339 function hidePref(key, value) | |
| 1340 { | |
| 1341 let element = document.querySelector("[data-pref='" + key + "']"); | |
| 1342 if (element) | |
| 1343 element.setAttribute("aria-hidden", value); | |
| 1344 } | |
| 1345 | |
| 1346 function getPref(key, callback) | |
| 1347 { | |
| 1348 let checkPref = getPref.checks[key] || getPref.checkNone; | |
| 1349 checkPref((isActive) => | |
| 1350 { | |
| 1351 if (!isActive) | |
| 1352 { | |
| 1353 hidePref(key, !isActive); | |
| 1354 return; | |
| 1355 } | |
| 1356 | |
| 1357 browser.runtime.sendMessage({ | |
| 1358 type: "prefs.get", | |
| 1359 key | |
| 1360 }, callback); | |
| 1361 }); | |
| 1362 } | |
| 1363 | |
| 1364 getPref.checkNone = function(callback) | |
| 1365 { | |
| 1366 callback(true); | |
| 1367 }; | |
| 1368 | |
| 1369 getPref.checks = | |
| 1370 { | |
| 1371 notifications_ignoredcategories(callback) | |
| 1372 { | |
| 1373 getPref("notifications_showui", callback); | |
| 1374 } | |
| 1375 }; | |
| 1376 | |
| 1377 function onPrefMessage(key, value, initial) | |
| 1378 { | |
| 1379 switch (key) | |
| 1380 { | |
| 1381 case "notifications_ignoredcategories": | |
| 1382 value = value.indexOf("*") == -1; | |
| 1383 break; | |
| 1384 | |
| 1385 case "notifications_showui": | |
| 1386 hidePref("notifications_ignoredcategories", !value); | |
| 1387 break; | |
| 1388 case "ui_warn_tracking": | |
| 1389 setPrivacyConflict(); | |
| 1390 break; | |
| 1391 } | |
| 1392 | |
| 1393 let checkbox = document.querySelector( | |
| 1394 "[data-pref='" + key + "'] button[role='checkbox']" | |
| 1395 ); | |
| 1396 if (checkbox) | |
| 1397 checkbox.setAttribute("aria-checked", value); | |
| 1398 } | |
| 1399 | |
| 1400 function updateTooltips() | |
| 1401 { | |
| 1402 let anchors = document.querySelectorAll(":not(.tooltip) > [data-tooltip]"); | |
| 1403 for (let anchor of anchors) | |
| 1404 { | |
| 1405 let id = anchor.getAttribute("data-tooltip"); | |
| 1406 | |
| 1407 let wrapper = document.createElement("div"); | |
| 1408 wrapper.className = "icon tooltip"; | |
| 1409 anchor.parentNode.replaceChild(wrapper, anchor); | |
| 1410 wrapper.appendChild(anchor); | |
| 1411 | |
| 1412 let tooltip = document.createElement("div"); | |
| 1413 tooltip.setAttribute("role", "tooltip"); | |
| 1414 | |
| 1415 let paragraph = document.createElement("p"); | |
| 1416 paragraph.textContent = getMessage(id); | |
| 1417 tooltip.appendChild(paragraph); | |
| 1418 | |
| 1419 wrapper.appendChild(tooltip); | |
| 1420 } | |
| 1421 } | |
| 1422 | |
| 1423 ext.onMessage.addListener((message) => | |
| 1424 { | |
| 1425 switch (message.type) | |
| 1426 { | |
| 1427 case "app.respond": | |
| 1428 switch (message.action) | |
| 1429 { | |
| 1430 case "addSubscription": | |
| 1431 let subscription = message.args[0]; | |
| 1432 let dialog = E("dialog-content-predefined"); | |
| 1433 dialog.querySelector("h3").textContent = subscription.title || ""; | |
| 1434 dialog.querySelector(".url").textContent = subscription.url; | |
| 1435 openDialog("predefined"); | |
| 1436 break; | |
| 1437 case "focusSection": | |
| 1438 document.body.setAttribute("data-tab", message.args[0]); | |
| 1439 break; | |
| 1440 } | |
| 1441 break; | |
| 1442 case "filters.respond": | |
| 1443 onFilterMessage(message.action, message.args[0]); | |
| 1444 break; | |
| 1445 case "prefs.respond": | |
| 1446 onPrefMessage(message.action, message.args[0], false); | |
| 1447 break; | |
| 1448 case "subscriptions.respond": | |
| 1449 onSubscriptionMessage(message.action, message.args[0]); | |
| 1450 break; | |
| 1451 } | |
| 1452 }); | |
| 1453 | |
| 1454 browser.runtime.sendMessage({ | |
| 1455 type: "app.listen", | |
| 1456 filter: ["addSubscription", "focusSection"] | |
| 1457 }); | |
| 1458 browser.runtime.sendMessage({ | |
| 1459 type: "filters.listen", | |
| 1460 filter: ["added", "loaded", "removed"] | |
| 1461 }); | |
| 1462 browser.runtime.sendMessage({ | |
| 1463 type: "prefs.listen", | |
| 1464 filter: ["notifications_ignoredcategories", "notifications_showui", | |
| 1465 "show_devtools_panel", "shouldShowBlockElementMenu", | |
| 1466 "ui_warn_tracking"] | |
| 1467 }); | |
| 1468 browser.runtime.sendMessage({ | |
| 1469 type: "subscriptions.listen", | |
| 1470 filter: ["added", "disabled", "homepage", "lastDownload", "removed", | |
| 1471 "title", "downloadStatus", "downloading"] | |
| 1472 }); | |
| 1473 | |
| 1474 window.addEventListener("DOMContentLoaded", onDOMLoaded, false); | |
| 1475 window.addEventListener("hashchange", onHashChange, false); | |
| 1476 } | |
| OLD | NEW |