| 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 |