OLD | NEW |
| (Empty) |
1 /* | |
2 * This file is part of Adblock Plus <https://adblockplus.org/>, | |
3 * Copyright (C) 2006-2016 Eyeo GmbH | |
4 * | |
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 | |
7 * published by the Free Software Foundation. | |
8 * | |
9 * Adblock Plus is distributed in the hope that it will be useful, | |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 * GNU General Public License for more details. | |
13 * | |
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/>. | |
16 */ | |
17 | |
18 "use strict"; | |
19 | |
20 (function() | |
21 { | |
22 var subscriptionsMap = Object.create(null); | |
23 var recommendationsMap = Object.create(null); | |
24 var filtersMap = Object.create(null); | |
25 var collections = Object.create(null); | |
26 var acceptableAdsUrl = null; | |
27 var maxLabelId = 0; | |
28 var getMessage = ext.i18n.getMessage; | |
29 var filterErrors = | |
30 { | |
31 "synchronize_invalid_url": "options_filterList_lastDownload_invalidURL", | |
32 "synchronize_connection_error": "options_filterList_lastDownload_connectionE
rror", | |
33 "synchronize_invalid_data": "options_filterList_lastDownload_invalidData", | |
34 "synchronize_checksum_mismatch": "options_filterList_lastDownload_checksumMi
smatch" | |
35 }; | |
36 | |
37 function Collection(details) | |
38 { | |
39 this.details = details; | |
40 this.items = []; | |
41 } | |
42 | |
43 Collection.prototype._setEmpty = function(table, text) | |
44 { | |
45 var placeholder = table.querySelector(".empty-placeholder"); | |
46 if (text && !placeholder) | |
47 { | |
48 placeholder = document.createElement("li"); | |
49 placeholder.className = "empty-placeholder"; | |
50 placeholder.textContent = getMessage(text); | |
51 table.appendChild(placeholder); | |
52 } | |
53 else if (placeholder) | |
54 table.removeChild(placeholder); | |
55 } | |
56 | |
57 Collection.prototype._createElementQuery = function(item) | |
58 { | |
59 var access = (item.url || item.text).replace(/'/g, "\\'"); | |
60 return function(container) | |
61 { | |
62 return container.querySelector("[data-access='" + access + "']"); | |
63 }; | |
64 }; | |
65 | |
66 Collection.prototype._getItemTitle = function(item, i) | |
67 { | |
68 if (item.url == acceptableAdsUrl) | |
69 return getMessage("options_acceptableAds_description"); | |
70 if (this.details[i].useOriginalTitle && item.originalTitle) | |
71 return item.originalTitle; | |
72 return item.title || item.url || item.text; | |
73 }; | |
74 | |
75 Collection.prototype.addItems = function() | |
76 { | |
77 var length = Array.prototype.push.apply(this.items, arguments); | |
78 if (length == 0) | |
79 return; | |
80 | |
81 this.items.sort(function(a, b) | |
82 { | |
83 // Make sure that Acceptable Ads is always last, since it cannot be | |
84 // disabled, but only be removed. That way it's grouped together with | |
85 // the "Own filter list" which cannot be disabled either at the bottom | |
86 // of the filter lists in the Advanced tab. | |
87 if (a.url == acceptableAdsUrl) | |
88 return 1; | |
89 if (b.url == acceptableAdsUrl) | |
90 return -1; | |
91 | |
92 var aTitle = this._getItemTitle(a, 0).toLowerCase(); | |
93 var bTitle = this._getItemTitle(b, 0).toLowerCase(); | |
94 return aTitle.localeCompare(bTitle); | |
95 }.bind(this)); | |
96 | |
97 for (var j = 0; j < this.details.length; j++) | |
98 { | |
99 var table = E(this.details[j].id); | |
100 var template = table.querySelector("template"); | |
101 for (var i = 0; i < arguments.length; i++) | |
102 { | |
103 var item = arguments[i]; | |
104 var listItem = document.createElement("li"); | |
105 listItem.appendChild(document.importNode(template.content, true)); | |
106 listItem.setAttribute("data-access", item.url || item.text); | |
107 | |
108 var labelId = "label-" + (++maxLabelId); | |
109 var label = listItem.querySelector(".display"); | |
110 label.setAttribute("id", labelId); | |
111 | |
112 var control = listItem.querySelector(".control"); | |
113 if (control) | |
114 { | |
115 control.setAttribute("aria-labelledby", labelId); | |
116 control.addEventListener("click", this.details[j].onClick, false); | |
117 | |
118 var role = control.getAttribute("role"); | |
119 if (role == "checkbox" && !label.hasAttribute("data-action")) | |
120 { | |
121 var controlId = "control-" + maxLabelId; | |
122 control.setAttribute("id", controlId); | |
123 label.setAttribute("for", controlId); | |
124 } | |
125 } | |
126 | |
127 this._setEmpty(table, null); | |
128 if (table.hasChildNodes()) | |
129 { | |
130 table.insertBefore(listItem, | |
131 table.childNodes[this.items.indexOf(item)]); | |
132 } | |
133 else | |
134 table.appendChild(listItem); | |
135 this.updateItem(item); | |
136 } | |
137 } | |
138 return length; | |
139 }; | |
140 | |
141 Collection.prototype.removeItem = function(item) | |
142 { | |
143 var index = this.items.indexOf(item); | |
144 if (index == -1) | |
145 return; | |
146 | |
147 this.items.splice(index, 1); | |
148 var getListElement = this._createElementQuery(item); | |
149 for (var i = 0; i < this.details.length; i++) | |
150 { | |
151 var table = E(this.details[i].id); | |
152 var element = getListElement(table); | |
153 | |
154 // Element gets removed so make sure to handle focus appropriately | |
155 var control = element.querySelector(".control"); | |
156 if (control && control == document.activeElement) | |
157 { | |
158 if (!focusNextElement(element.parentElement, control)) | |
159 { | |
160 // Fall back to next focusable element within same tab or dialog | |
161 var focusableElement = element.parentElement; | |
162 while (focusableElement) | |
163 { | |
164 if (focusableElement.classList.contains("tab-content") | |
165 || focusableElement.classList.contains("dialog-content")) | |
166 break; | |
167 | |
168 focusableElement = focusableElement.parentElement; | |
169 } | |
170 focusNextElement(focusableElement || document, control); | |
171 } | |
172 } | |
173 | |
174 element.parentElement.removeChild(element); | |
175 if (this.items.length == 0) | |
176 this._setEmpty(table, this.details[i].emptyText); | |
177 } | |
178 }; | |
179 | |
180 Collection.prototype.updateItem = function(item) | |
181 { | |
182 var access = (item.url || item.text).replace(/'/g, "\\'"); | |
183 for (var i = 0; i < this.details.length; i++) | |
184 { | |
185 var table = E(this.details[i].id); | |
186 var element = table.querySelector("[data-access='" + access + "']"); | |
187 if (!element) | |
188 continue; | |
189 | |
190 var title = this._getItemTitle(item, i); | |
191 element.querySelector(".display").textContent = title; | |
192 if (title) | |
193 element.setAttribute("data-search", title.toLowerCase()); | |
194 var control = element.querySelector(".control[role='checkbox']"); | |
195 if (control) | |
196 { | |
197 control.setAttribute("aria-checked", item.disabled == false); | |
198 if (item.url == acceptableAdsUrl && this.details[i].onClick == | |
199 toggleDisableSubscription) | |
200 control.setAttribute("disabled", true); | |
201 } | |
202 | |
203 var dateElement = element.querySelector(".date"); | |
204 var timeElement = element.querySelector(".time"); | |
205 if (dateElement && timeElement) | |
206 { | |
207 var message = element.querySelector(".message"); | |
208 if (item.isDownloading) | |
209 { | |
210 var text = getMessage("options_filterList_lastDownload_inProgress"); | |
211 message.textContent = text; | |
212 element.classList.add("show-message"); | |
213 } | |
214 else if (item.downloadStatus != "synchronize_ok") | |
215 { | |
216 var error = filterErrors[item.downloadStatus]; | |
217 if (error) | |
218 message.textContent = getMessage(error); | |
219 else | |
220 message.textContent = item.downloadStatus; | |
221 element.classList.add("show-message"); | |
222 } | |
223 else if (item.lastDownload > 0) | |
224 { | |
225 var dateTime = i18n_formatDateTime(item.lastDownload * 1000); | |
226 dateElement.textContent = dateTime[0]; | |
227 timeElement.textContent = dateTime[1]; | |
228 element.classList.remove("show-message"); | |
229 } | |
230 } | |
231 | |
232 var websiteElement = element.querySelector(".context-menu .website"); | |
233 if (websiteElement) | |
234 { | |
235 if (item.homepage) | |
236 websiteElement.setAttribute("href", item.homepage); | |
237 else | |
238 websiteElement.setAttribute("aria-hidden", true); | |
239 } | |
240 | |
241 var sourceElement = element.querySelector(".context-menu .source"); | |
242 if (sourceElement) | |
243 sourceElement.setAttribute("href", item.url); | |
244 } | |
245 }; | |
246 | |
247 Collection.prototype.clearAll = function() | |
248 { | |
249 this.items = []; | |
250 for (var i = 0; i < this.details.length; i++) | |
251 { | |
252 var table = E(this.details[i].id); | |
253 var element = table.firstChild; | |
254 while (element) | |
255 { | |
256 if (element.tagName == "LI" && !element.classList.contains("static")) | |
257 table.removeChild(element); | |
258 element = element.nextElementSibling; | |
259 } | |
260 | |
261 this._setEmpty(table, this.details[i].emptyText); | |
262 } | |
263 }; | |
264 | |
265 function focusNextElement(container, currentElement) | |
266 { | |
267 var focusables = container.querySelectorAll("a, button, input, .control"); | |
268 focusables = Array.prototype.slice.call(focusables); | |
269 var index = focusables.indexOf(currentElement); | |
270 index += (index == focusables.length - 1) ? -1 : 1; | |
271 | |
272 var nextElement = focusables[index]; | |
273 if (!nextElement) | |
274 return false; | |
275 | |
276 nextElement.focus(); | |
277 return true; | |
278 } | |
279 | |
280 function toggleRemoveSubscription(e) | |
281 { | |
282 e.preventDefault(); | |
283 var subscriptionUrl = findParentData(e.target, "access", false); | |
284 if (e.target.getAttribute("aria-checked") == "true") | |
285 { | |
286 ext.backgroundPage.sendMessage({ | |
287 type: "subscriptions.remove", | |
288 url: subscriptionUrl | |
289 }); | |
290 } | |
291 else | |
292 addEnableSubscription(subscriptionUrl); | |
293 } | |
294 | |
295 function toggleDisableSubscription(e) | |
296 { | |
297 e.preventDefault(); | |
298 var subscriptionUrl = findParentData(e.target, "access", false); | |
299 ext.backgroundPage.sendMessage( | |
300 { | |
301 type: "subscriptions.toggle", | |
302 keepInstalled: true, | |
303 url: subscriptionUrl | |
304 }); | |
305 } | |
306 | |
307 function onAddLanguageSubscriptionClick(e) | |
308 { | |
309 e.preventDefault(); | |
310 var url = findParentData(this, "access", false); | |
311 addEnableSubscription(url); | |
312 } | |
313 | |
314 function onRemoveFilterClick() | |
315 { | |
316 var filter = findParentData(this, "access", false); | |
317 ext.backgroundPage.sendMessage( | |
318 { | |
319 type: "filters.remove", | |
320 text: filter | |
321 }); | |
322 } | |
323 | |
324 collections.popular = new Collection( | |
325 [ | |
326 { | |
327 id: "recommend-list-table", | |
328 onClick: toggleRemoveSubscription | |
329 } | |
330 ]); | |
331 collections.langs = new Collection( | |
332 [ | |
333 { | |
334 id: "blocking-languages-table", | |
335 emptyText: "options_dialog_language_added_empty", | |
336 onClick: toggleRemoveSubscription | |
337 }, | |
338 { | |
339 id: "blocking-languages-dialog-table", | |
340 emptyText: "options_dialog_language_added_empty" | |
341 } | |
342 ]); | |
343 collections.allLangs = new Collection( | |
344 [ | |
345 { | |
346 id: "all-lang-table", | |
347 emptyText: "options_dialog_language_other_empty", | |
348 onClick: onAddLanguageSubscriptionClick | |
349 } | |
350 ]); | |
351 collections.acceptableAds = new Collection( | |
352 [ | |
353 { | |
354 id: "acceptableads-table", | |
355 onClick: toggleRemoveSubscription | |
356 } | |
357 ]); | |
358 collections.custom = new Collection( | |
359 [ | |
360 { | |
361 id: "custom-list-table", | |
362 onClick: toggleRemoveSubscription | |
363 } | |
364 ]); | |
365 collections.whitelist = new Collection( | |
366 [ | |
367 { | |
368 id: "whitelisting-table", | |
369 emptyText: "options_whitelisted_empty", | |
370 onClick: onRemoveFilterClick | |
371 } | |
372 ]); | |
373 collections.customFilters = new Collection( | |
374 [ | |
375 { | |
376 id: "custom-filters-table", | |
377 emptyText: "options_customFilters_empty" | |
378 } | |
379 ]); | |
380 collections.filterLists = new Collection( | |
381 [ | |
382 { | |
383 id: "all-filter-lists-table", | |
384 onClick: toggleDisableSubscription, | |
385 useOriginalTitle: true | |
386 } | |
387 ]); | |
388 | |
389 function observeSubscription(subscription) | |
390 { | |
391 function onObjectChanged(change) | |
392 { | |
393 for (var i = 0; i < change.length; i++) | |
394 { | |
395 if (change[i].name == "disabled") | |
396 { | |
397 var recommendation = recommendationsMap[subscription.url]; | |
398 if (recommendation && recommendation.type == "ads") | |
399 { | |
400 if (subscription.disabled == false) | |
401 { | |
402 collections.allLangs.removeItem(subscription); | |
403 collections.langs.addItems(subscription); | |
404 } | |
405 else | |
406 { | |
407 collections.allLangs.addItems(subscription); | |
408 collections.langs.removeItem(subscription); | |
409 } | |
410 } | |
411 } | |
412 } | |
413 | |
414 for (var name in collections) | |
415 collections[name].updateItem(subscription); | |
416 } | |
417 | |
418 if (!Object.observe) | |
419 { | |
420 Object.keys(subscription).forEach(function(property) | |
421 { | |
422 var value = subscription[property]; | |
423 Object.defineProperty(subscription, property, | |
424 { | |
425 get: function() | |
426 { | |
427 return value; | |
428 }, | |
429 set: function(newValue) | |
430 { | |
431 if (value != newValue) | |
432 { | |
433 value = newValue; | |
434 onObjectChanged([{name: property}]); | |
435 } | |
436 } | |
437 }); | |
438 }); | |
439 } | |
440 else | |
441 { | |
442 Object.observe(subscription, onObjectChanged); | |
443 } | |
444 } | |
445 | |
446 function updateSubscription(subscription) | |
447 { | |
448 var subscriptionUrl = subscription.url; | |
449 var knownSubscription = subscriptionsMap[subscriptionUrl]; | |
450 if (knownSubscription) | |
451 { | |
452 for (var property in subscription) | |
453 { | |
454 if (property == "title" && subscriptionUrl in recommendationsMap) | |
455 knownSubscription.originalTitle = subscription.title; | |
456 else | |
457 knownSubscription[property] = subscription[property]; | |
458 } | |
459 } | |
460 else | |
461 { | |
462 observeSubscription(subscription); | |
463 | |
464 var collection; | |
465 if (subscriptionUrl in recommendationsMap) | |
466 { | |
467 var recommendation = recommendationsMap[subscriptionUrl]; | |
468 if (recommendation.type != "ads") | |
469 collection = collections.popular; | |
470 else if (subscription.disabled == false) | |
471 collection = collections.langs; | |
472 else | |
473 collection = collections.allLangs; | |
474 } | |
475 else if (subscriptionUrl == acceptableAdsUrl) | |
476 collection = collections.acceptableAds; | |
477 else | |
478 collection = collections.custom; | |
479 | |
480 collection.addItems(subscription); | |
481 subscriptionsMap[subscriptionUrl] = subscription; | |
482 } | |
483 } | |
484 | |
485 function updateFilter(filter) | |
486 { | |
487 var match = filter.text.match(/^@@\|\|([^\/:]+)\^\$document$/); | |
488 if (match && !filtersMap[filter.text]) | |
489 { | |
490 filter.title = match[1]; | |
491 collections.whitelist.addItems(filter); | |
492 } | |
493 else | |
494 collections.customFilters.addItems(filter); | |
495 | |
496 filtersMap[filter.text] = filter; | |
497 } | |
498 | |
499 function loadRecommendations() | |
500 { | |
501 fetch("subscriptions.xml") | |
502 .then(function(response) | |
503 { | |
504 return response.text(); | |
505 }) | |
506 .then(function(text) | |
507 { | |
508 var list = document.getElementById("subscriptionSelector"); | |
509 var doc = new DOMParser().parseFromString(text, "application/xml"); | |
510 var elements = doc.documentElement.getElementsByTagName("subscription"); | |
511 for (var i = 0; i < elements.length; i++) | |
512 { | |
513 var element = elements[i]; | |
514 var subscription = Object.create(null); | |
515 subscription.originalTitle = element.getAttribute("title"); | |
516 subscription.url = element.getAttribute("url"); | |
517 subscription.disabled = null; | |
518 subscription.downloadStatus = null; | |
519 subscription.homepage = null; | |
520 var recommendation = Object.create(null); | |
521 recommendation.type = element.getAttribute("type"); | |
522 var prefix = element.getAttribute("prefixes"); | |
523 if (prefix) | |
524 { | |
525 prefix = prefix.replace(/\W/g, "_"); | |
526 subscription.title = getMessage("options_language_" + prefix); | |
527 } | |
528 else | |
529 { | |
530 var type = recommendation.type.replace(/\W/g, "_"); | |
531 subscription.title = getMessage("common_feature_" + type + "_title")
; | |
532 } | |
533 | |
534 recommendationsMap[subscription.url] = recommendation; | |
535 updateSubscription(subscription); | |
536 } | |
537 }); | |
538 } | |
539 | |
540 function findParentData(element, dataName, returnElement) | |
541 { | |
542 while (element) | |
543 { | |
544 if (element.hasAttribute("data-" + dataName)) | |
545 return returnElement ? element : element.getAttribute("data-" + dataName
); | |
546 | |
547 element = element.parentElement; | |
548 } | |
549 return null; | |
550 } | |
551 | |
552 function sendMessageHandleErrors(message, onSuccess) | |
553 { | |
554 ext.backgroundPage.sendMessage(message, function(errors) | |
555 { | |
556 if (errors.length > 0) | |
557 alert(errors.join("\n")); | |
558 else if (onSuccess) | |
559 onSuccess(); | |
560 }); | |
561 } | |
562 | |
563 function onClick(e) | |
564 { | |
565 var context = document.querySelector(".show-context-menu"); | |
566 if (context) | |
567 context.classList.remove("show-context-menu"); | |
568 | |
569 var element = e.target; | |
570 while (true) | |
571 { | |
572 if (!element) | |
573 return; | |
574 | |
575 if (element.hasAttribute("data-action")) | |
576 break; | |
577 | |
578 element = element.parentElement; | |
579 } | |
580 | |
581 var actions = element.getAttribute("data-action").split(","); | |
582 for (var i = 0; i < actions.length; i++) | |
583 { | |
584 switch (actions[i]) | |
585 { | |
586 case "add-domain-exception": | |
587 addWhitelistedDomain(); | |
588 break; | |
589 case "add-predefined-subscription": | |
590 var dialog = E("dialog-content-predefined"); | |
591 var title = dialog.querySelector("h3").textContent; | |
592 var url = dialog.querySelector(".url").textContent; | |
593 addEnableSubscription(url, title); | |
594 closeDialog(); | |
595 break; | |
596 case "cancel-custom-filters": | |
597 E("custom-filters").classList.remove("mode-edit"); | |
598 break; | |
599 case "cancel-domain-exception": | |
600 E("whitelisting-textbox").value = ""; | |
601 document.querySelector("#whitelisting .controls").classList.remove("mo
de-edit"); | |
602 break; | |
603 case "close-dialog": | |
604 closeDialog(); | |
605 break; | |
606 case "edit-custom-filters": | |
607 E("custom-filters").classList.add("mode-edit"); | |
608 editCustomFilters(); | |
609 break; | |
610 case "edit-domain-exception": | |
611 document.querySelector("#whitelisting .controls").classList.add("mode-
edit"); | |
612 E("whitelisting-textbox").focus(); | |
613 break; | |
614 case "import-subscription": | |
615 var url = E("blockingList-textbox").value; | |
616 addEnableSubscription(url); | |
617 closeDialog(); | |
618 break; | |
619 case "open-dialog": | |
620 openDialog(element.getAttribute("data-dialog")); | |
621 break; | |
622 case "save-custom-filters": | |
623 sendMessageHandleErrors( | |
624 { | |
625 type: "filters.importRaw", | |
626 text: E("custom-filters-raw").value, | |
627 removeExisting: true | |
628 }, | |
629 function() | |
630 { | |
631 E("custom-filters").classList.remove("mode-edit"); | |
632 }); | |
633 break; | |
634 case "switch-tab": | |
635 document.body.setAttribute("data-tab", | |
636 element.getAttribute("data-tab")); | |
637 break; | |
638 case "toggle-pref": | |
639 ext.backgroundPage.sendMessage( | |
640 { | |
641 type: "prefs.toggle", | |
642 key: findParentData(element, "pref", false) | |
643 }); | |
644 break; | |
645 case "update-all-subscriptions": | |
646 ext.backgroundPage.sendMessage( | |
647 { | |
648 type: "subscriptions.update" | |
649 }); | |
650 break; | |
651 case "open-context-menu": | |
652 var listItem = findParentData(element, "access", true); | |
653 if (listItem != context) | |
654 listItem.classList.add("show-context-menu"); | |
655 break; | |
656 case "update-subscription": | |
657 ext.backgroundPage.sendMessage( | |
658 { | |
659 type: "subscriptions.update", | |
660 url: findParentData(element, "access", false) | |
661 }); | |
662 break; | |
663 case "remove-subscription": | |
664 ext.backgroundPage.sendMessage( | |
665 { | |
666 type: "subscriptions.remove", | |
667 url: findParentData(element, "access", false) | |
668 }); | |
669 break; | |
670 } | |
671 } | |
672 } | |
673 | |
674 function onDOMLoaded() | |
675 { | |
676 populateLists(); | |
677 function onFindLanguageKeyUp() | |
678 { | |
679 var searchStyle = E("search-style"); | |
680 if (!this.value) | |
681 searchStyle.innerHTML = ""; | |
682 else | |
683 searchStyle.innerHTML = "#all-lang-table li:not([data-search*=\"" + this
.value.toLowerCase() + "\"]) { display: none; }"; | |
684 } | |
685 | |
686 function getKey(e) | |
687 { | |
688 // e.keyCode has been deprecated so we attempt to use e.key | |
689 if ("key" in e) | |
690 return e.key; | |
691 return getKey.keys[e.keyCode]; | |
692 } | |
693 getKey.keys = { | |
694 9: "Tab", | |
695 13: "Enter", | |
696 27: "Escape" | |
697 }; | |
698 | |
699 // Initialize navigation sidebar | |
700 ext.backgroundPage.sendMessage( | |
701 { | |
702 type: "app.get", | |
703 what: "addonVersion" | |
704 }, | |
705 function(addonVersion) | |
706 { | |
707 E("abp-version").textContent = addonVersion; | |
708 }); | |
709 getDocLink("releases", function(link) | |
710 { | |
711 E("link-version").setAttribute("href", link); | |
712 }); | |
713 | |
714 getDocLink("contribute", function(link) | |
715 { | |
716 document.querySelector("#tab-contribute a").setAttribute("href", link); | |
717 }); | |
718 | |
719 updateShareLink(); | |
720 | |
721 // Initialize interactive UI elements | |
722 document.body.addEventListener("click", onClick, false); | |
723 var placeholderValue = getMessage("options_dialog_language_find"); | |
724 E("find-language").setAttribute("placeholder", placeholderValue); | |
725 E("find-language").addEventListener("keyup", onFindLanguageKeyUp, false); | |
726 E("whitelisting-textbox").addEventListener("keypress", function(e) | |
727 { | |
728 if (getKey(e) == "Enter") | |
729 addWhitelistedDomain(); | |
730 }, false); | |
731 | |
732 // Advanced tab | |
733 var tweaks = document.querySelectorAll("#tweaks li[data-pref]"); | |
734 tweaks = Array.prototype.map.call(tweaks, function(checkbox) | |
735 { | |
736 return checkbox.getAttribute("data-pref"); | |
737 }); | |
738 tweaks.forEach(function(key) | |
739 { | |
740 getPref(key, function(value) | |
741 { | |
742 onPrefMessage(key, value, true); | |
743 }); | |
744 }); | |
745 ext.backgroundPage.sendMessage( | |
746 { | |
747 type: "app.get", | |
748 what: "features" | |
749 }, | |
750 function(features) | |
751 { | |
752 hidePref("show_devtools_panel", !features.devToolsPanel); | |
753 | |
754 // Only show option to switch between Safari Content Blockers | |
755 // and event based blocking if both are available. | |
756 hidePref("safari_contentblocker", !( | |
757 features.safariContentBlocker && | |
758 "canLoad" in safari.self.tab && | |
759 "onbeforeload" in Element.prototype | |
760 )); | |
761 }); | |
762 | |
763 var filterTextbox = document.querySelector("#custom-filters-add input"); | |
764 placeholderValue = getMessage("options_customFilters_textbox_placeholder"); | |
765 filterTextbox.setAttribute("placeholder", placeholderValue); | |
766 function addCustomFilters() | |
767 { | |
768 var filterText = filterTextbox.value; | |
769 sendMessageHandleErrors( | |
770 { | |
771 type: "filters.add", | |
772 text: filterText | |
773 }, | |
774 function() | |
775 { | |
776 filterTextbox.value = ""; | |
777 }); | |
778 } | |
779 E("custom-filters-add").addEventListener("submit", function(e) | |
780 { | |
781 e.preventDefault(); | |
782 addCustomFilters(); | |
783 }, false); | |
784 var customFilterEditButtons = document.querySelectorAll("#custom-filters-edi
t-wrapper button"); | |
785 | |
786 E("dialog").addEventListener("keydown", function(e) | |
787 { | |
788 switch (getKey(e)) | |
789 { | |
790 case "Escape": | |
791 closeDialog(); | |
792 break; | |
793 case "Tab": | |
794 if (e.shiftKey) | |
795 { | |
796 if (e.target.classList.contains("focus-first")) | |
797 { | |
798 e.preventDefault(); | |
799 this.querySelector(".focus-last").focus(); | |
800 } | |
801 } | |
802 else if (e.target.classList.contains("focus-last")) | |
803 { | |
804 e.preventDefault(); | |
805 this.querySelector(".focus-first").focus(); | |
806 } | |
807 break; | |
808 } | |
809 }, false); | |
810 } | |
811 | |
812 var focusedBeforeDialog = null; | |
813 function openDialog(name) | |
814 { | |
815 var dialog = E("dialog"); | |
816 dialog.setAttribute("aria-hidden", false); | |
817 dialog.setAttribute("aria-labelledby", "dialog-title-" + name); | |
818 document.body.setAttribute("data-dialog", name); | |
819 | |
820 var defaultFocus = document.querySelector("#dialog-content-" + name | |
821 + " .default-focus"); | |
822 if (!defaultFocus) | |
823 defaultFocus = dialog.querySelector(".focus-first"); | |
824 focusedBeforeDialog = document.activeElement; | |
825 defaultFocus.focus(); | |
826 } | |
827 | |
828 function closeDialog() | |
829 { | |
830 var dialog = E("dialog"); | |
831 dialog.setAttribute("aria-hidden", true); | |
832 dialog.removeAttribute("aria-labelledby"); | |
833 document.body.removeAttribute("data-dialog"); | |
834 focusedBeforeDialog.focus(); | |
835 } | |
836 | |
837 function populateLists() | |
838 { | |
839 subscriptionsMap = Object.create(null); | |
840 filtersMap = Object.create(null); | |
841 recommendationsMap = Object.create(null); | |
842 | |
843 // Empty collections and lists | |
844 for (var property in collections) | |
845 collections[property].clearAll(); | |
846 | |
847 ext.backgroundPage.sendMessage( | |
848 { | |
849 type: "subscriptions.get", | |
850 special: true | |
851 }, | |
852 function(subscriptions) | |
853 { | |
854 // Load filters | |
855 for (var i = 0; i < subscriptions.length; i++) | |
856 { | |
857 ext.backgroundPage.sendMessage( | |
858 { | |
859 type: "filters.get", | |
860 subscriptionUrl: subscriptions[i].url | |
861 }, | |
862 function(filters) | |
863 { | |
864 for (var i = 0; i < filters.length; i++) | |
865 updateFilter(filters[i]); | |
866 }); | |
867 } | |
868 }); | |
869 loadRecommendations(); | |
870 ext.backgroundPage.sendMessage( | |
871 { | |
872 type: "prefs.get", | |
873 key: "subscriptions_exceptionsurl" | |
874 }, | |
875 function(url) | |
876 { | |
877 acceptableAdsUrl = url; | |
878 updateSubscription({ | |
879 url: acceptableAdsUrl, | |
880 disabled: true | |
881 }); | |
882 | |
883 // Load user subscriptions | |
884 ext.backgroundPage.sendMessage( | |
885 { | |
886 type: "subscriptions.get", | |
887 downloadable: true | |
888 }, | |
889 function(subscriptions) | |
890 { | |
891 for (var i = 0; i < subscriptions.length; i++) | |
892 onSubscriptionMessage("added", subscriptions[i]); | |
893 }); | |
894 }); | |
895 } | |
896 | |
897 function addWhitelistedDomain() | |
898 { | |
899 var domain = E("whitelisting-textbox"); | |
900 if (domain.value) | |
901 { | |
902 sendMessageHandleErrors( | |
903 { | |
904 type: "filters.add", | |
905 text: "@@||" + domain.value.toLowerCase() + "^$document" | |
906 }); | |
907 } | |
908 | |
909 domain.value = ""; | |
910 document.querySelector("#whitelisting .controls").classList.remove("mode-edi
t"); | |
911 } | |
912 | |
913 function editCustomFilters() | |
914 { | |
915 var customFilterItems = collections.customFilters.items; | |
916 var filterTexts = []; | |
917 for (var i = 0; i < customFilterItems.length; i++) | |
918 filterTexts.push(customFilterItems[i].text); | |
919 E("custom-filters-raw").value = filterTexts.join("\n"); | |
920 } | |
921 | |
922 function addEnableSubscription(url, title, homepage) | |
923 { | |
924 var messageType = null; | |
925 var knownSubscription = subscriptionsMap[url]; | |
926 if (knownSubscription && knownSubscription.disabled == true) | |
927 messageType = "subscriptions.toggle" | |
928 else | |
929 messageType = "subscriptions.add" | |
930 | |
931 var message = { | |
932 type: messageType, | |
933 url: url | |
934 }; | |
935 if (title) | |
936 message.title = title; | |
937 if (homepage) | |
938 message.homepage = homepage; | |
939 | |
940 ext.backgroundPage.sendMessage(message); | |
941 } | |
942 | |
943 function onFilterMessage(action, filter) | |
944 { | |
945 switch (action) | |
946 { | |
947 case "added": | |
948 updateFilter(filter); | |
949 updateShareLink(); | |
950 break; | |
951 case "loaded": | |
952 populateLists(); | |
953 break; | |
954 case "removed": | |
955 var knownFilter = filtersMap[filter.text]; | |
956 collections.whitelist.removeItem(knownFilter); | |
957 collections.customFilters.removeItem(knownFilter); | |
958 delete filtersMap[filter.text]; | |
959 updateShareLink(); | |
960 break; | |
961 } | |
962 } | |
963 | |
964 function onSubscriptionMessage(action, subscription) | |
965 { | |
966 switch (action) | |
967 { | |
968 case "added": | |
969 updateSubscription(subscription); | |
970 updateShareLink(); | |
971 | |
972 var knownSubscription = subscriptionsMap[subscription.url]; | |
973 if (knownSubscription) | |
974 collections.filterLists.addItems(knownSubscription); | |
975 else | |
976 collections.filterLists.addItems(subscription); | |
977 break; | |
978 case "disabled": | |
979 updateSubscription(subscription); | |
980 updateShareLink(); | |
981 break; | |
982 case "removed": | |
983 var knownSubscription = subscriptionsMap[subscription.url]; | |
984 if (subscription.url == acceptableAdsUrl) | |
985 { | |
986 subscription.disabled = true; | |
987 updateSubscription(subscription); | |
988 } | |
989 else | |
990 { | |
991 if (subscription.url in recommendationsMap) | |
992 knownSubscription.disabled = true; | |
993 else | |
994 { | |
995 collections.custom.removeItem(knownSubscription); | |
996 delete subscriptionsMap[subscription.url]; | |
997 } | |
998 } | |
999 updateShareLink(); | |
1000 collections.filterLists.removeItem(knownSubscription); | |
1001 break; | |
1002 default: | |
1003 updateSubscription(subscription); | |
1004 break; | |
1005 } | |
1006 } | |
1007 | |
1008 function hidePref(key, value) | |
1009 { | |
1010 var element = document.querySelector("[data-pref='" + key + "']"); | |
1011 if (element) | |
1012 element.setAttribute("aria-hidden", value); | |
1013 } | |
1014 | |
1015 function getPref(key, callback) | |
1016 { | |
1017 var checkPref = getPref.checks[key] || getPref.checkNone; | |
1018 checkPref(function(isActive) | |
1019 { | |
1020 if (!isActive) | |
1021 { | |
1022 hidePref(key, !isActive); | |
1023 return; | |
1024 } | |
1025 | |
1026 ext.backgroundPage.sendMessage( | |
1027 { | |
1028 type: "prefs.get", | |
1029 key: key | |
1030 }, callback); | |
1031 }); | |
1032 } | |
1033 | |
1034 getPref.checkNone = function(callback) | |
1035 { | |
1036 callback(true); | |
1037 }; | |
1038 | |
1039 getPref.checks = | |
1040 { | |
1041 notifications_ignoredcategories: function(callback) | |
1042 { | |
1043 getPref("notifications_showui", callback); | |
1044 } | |
1045 }; | |
1046 | |
1047 function onPrefMessage(key, value, initial) | |
1048 { | |
1049 switch (key) | |
1050 { | |
1051 case "notifications_ignoredcategories": | |
1052 value = value.indexOf("*") == -1; | |
1053 break; | |
1054 | |
1055 case "notifications_showui": | |
1056 hidePref("notifications_ignoredcategories", !value); | |
1057 break; | |
1058 | |
1059 case "safari_contentblocker": | |
1060 E("restart-safari").setAttribute("aria-hidden", value || initial); | |
1061 break; | |
1062 } | |
1063 | |
1064 var checkbox = document.querySelector("[data-pref='" + key + "'] button[role
='checkbox']"); | |
1065 if (checkbox) | |
1066 checkbox.setAttribute("aria-checked", value); | |
1067 } | |
1068 | |
1069 function onShareLinkClick(e) | |
1070 { | |
1071 e.preventDefault(); | |
1072 | |
1073 getDocLink("share-general", function(link) | |
1074 { | |
1075 openSharePopup(link); | |
1076 }); | |
1077 } | |
1078 | |
1079 function updateShareLink() | |
1080 { | |
1081 var shareResources = [ | |
1082 "https://facebook.com/plugins/like.php?", | |
1083 "https://platform.twitter.com/widgets/", | |
1084 "https://apis.google.com/se/0/_/+1/fastbutton?" | |
1085 ]; | |
1086 var isAnyBlocked = false; | |
1087 var checksRemaining = shareResources.length; | |
1088 | |
1089 function onResult(isBlocked) | |
1090 { | |
1091 isAnyBlocked |= isBlocked; | |
1092 if (!--checksRemaining) | |
1093 { | |
1094 // Hide the share tab if a script on the share page would be blocked | |
1095 var tab = E("tab-share"); | |
1096 if (isAnyBlocked) | |
1097 { | |
1098 tab.hidden = true; | |
1099 tab.removeEventListener("click", onShareLinkClick, false); | |
1100 } | |
1101 else | |
1102 tab.addEventListener("click", onShareLinkClick, false); | |
1103 } | |
1104 } | |
1105 | |
1106 for (var i = 0; i < shareResources.length; i++) | |
1107 checkShareResource(shareResources[i], onResult); | |
1108 } | |
1109 | |
1110 ext.onMessage.addListener(function(message) | |
1111 { | |
1112 switch (message.type) | |
1113 { | |
1114 case "app.respond": | |
1115 switch (message.action) | |
1116 { | |
1117 case "addSubscription": | |
1118 var subscription = message.args[0]; | |
1119 var dialog = E("dialog-content-predefined"); | |
1120 dialog.querySelector("h3").textContent = subscription.title || ""; | |
1121 dialog.querySelector(".url").textContent = subscription.url; | |
1122 openDialog("predefined"); | |
1123 break; | |
1124 case "focusSection": | |
1125 document.body.setAttribute("data-tab", message.args[0]); | |
1126 break; | |
1127 } | |
1128 break; | |
1129 case "filters.respond": | |
1130 onFilterMessage(message.action, message.args[0]); | |
1131 break; | |
1132 case "prefs.respond": | |
1133 onPrefMessage(message.action, message.args[0], false); | |
1134 break; | |
1135 case "subscriptions.respond": | |
1136 onSubscriptionMessage(message.action, message.args[0]); | |
1137 break; | |
1138 } | |
1139 }); | |
1140 | |
1141 ext.backgroundPage.sendMessage( | |
1142 { | |
1143 type: "app.listen", | |
1144 filter: ["addSubscription", "focusSection"] | |
1145 }); | |
1146 ext.backgroundPage.sendMessage( | |
1147 { | |
1148 type: "filters.listen", | |
1149 filter: ["added", "loaded", "removed"] | |
1150 }); | |
1151 ext.backgroundPage.sendMessage( | |
1152 { | |
1153 type: "prefs.listen", | |
1154 filter: ["notifications_ignoredcategories", "notifications_showui", | |
1155 "safari_contentblocker", "show_devtools_panel", | |
1156 "shouldShowBlockElementMenu"] | |
1157 }); | |
1158 ext.backgroundPage.sendMessage( | |
1159 { | |
1160 type: "subscriptions.listen", | |
1161 filter: ["added", "disabled", "homepage", "lastDownload", "removed", | |
1162 "title", "downloadStatus", "downloading"] | |
1163 }); | |
1164 | |
1165 window.addEventListener("DOMContentLoaded", onDOMLoaded, false); | |
1166 })(); | |
OLD | NEW |