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 |