| 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 /* global $, i18n, i18nTimeDateStrings */ |  | 
| 19 |  | 
| 20 "use strict"; | 18 "use strict"; | 
| 21 | 19 | 
| 22 /** | 20 let iframe = document.getElementById("content"); | 
| 23  * Creates a wrapping function used to conveniently send a type of message. | 21 | 
| 24  * | 22 iframe.onload = () => | 
| 25  * @param {Object} baseMessage The part of the message that's always sent |  | 
| 26  * @param {...string} paramKeys Any message keys that have dynamic values. The |  | 
| 27  *                              returned function will take the corresponding |  | 
| 28  *                              values as arguments. |  | 
| 29  * @return {function} The generated messaging function, optionally |  | 
| 30  *                    taking any values as specified by the paramKeys |  | 
| 31  *                    and finally an optional callback.  (Although the |  | 
| 32  *                    value arguments are optional their index must be |  | 
| 33  *                    maintained. E.g. if you omit the first value you |  | 
| 34  *                    must omit the second too.) |  | 
| 35  */ |  | 
| 36 function wrapper(baseMessage, ...paramKeys) |  | 
| 37 { | 23 { | 
| 38   return function(...paramValues /* , callback */) | 24   document.title = iframe.contentDocument.title; | 
| 39   { | 25 }; | 
| 40     let message = Object.assign(Object.create(null), baseMessage); |  | 
| 41     let callback; |  | 
| 42 | 26 | 
| 43     if (paramValues.length > 0) | 27 chrome.runtime.sendMessage({ | 
| 44     { | 28   type: "app.get", | 
| 45       let lastArg = paramValues[paramValues.length - 1]; | 29   what: "application" | 
| 46       if (typeof lastArg == "function") | 30 }, | 
| 47         callback = lastArg; | 31 application => | 
| 48 |  | 
| 49       for (let i = 0; i < paramValues.length - (callback ? 1 : 0); i++) |  | 
| 50         message[paramKeys[i]] = paramValues[i]; |  | 
| 51     } |  | 
| 52 |  | 
| 53     ext.backgroundPage.sendMessage(message, callback); |  | 
| 54   }; |  | 
| 55 } |  | 
| 56 |  | 
| 57 const getDocLink = wrapper({type: "app.get", what: "doclink"}, "link"); |  | 
| 58 const getInfo = wrapper({type: "app.get"}, "what"); |  | 
| 59 const getPref = wrapper({type: "prefs.get"}, "key"); |  | 
| 60 const togglePref = wrapper({type: "prefs.toggle"}, "key"); |  | 
| 61 const getSubscriptions = wrapper({type: "subscriptions.get"}, |  | 
| 62                                  "downloadable", "special"); |  | 
| 63 const removeSubscription = wrapper({type: "subscriptions.remove"}, "url"); |  | 
| 64 const addSubscription = wrapper({type: "subscriptions.add"}, |  | 
| 65                                 "url", "title", "homepage"); |  | 
| 66 const toggleSubscription = wrapper({type: "subscriptions.toggle"}, |  | 
| 67                                    "url", "keepInstalled"); |  | 
| 68 const updateSubscription = wrapper({type: "subscriptions.update"}, "url"); |  | 
| 69 const importRawFilters = wrapper({type: "filters.importRaw"}, |  | 
| 70                                  "text", "removeExisting"); |  | 
| 71 const addFilter = wrapper({type: "filters.add"}, "text"); |  | 
| 72 const removeFilter = wrapper({type: "filters.remove"}, "text"); |  | 
| 73 const quoteCSS = wrapper({type: "composer.quoteCSS"}, "CSS"); |  | 
| 74 |  | 
| 75 const whitelistedDomainRegexp = /^@@\|\|([^/:]+)\^\$document$/; |  | 
| 76 const statusMessages = new Map([ |  | 
| 77   ["synchronize_invalid_url", |  | 
| 78    "filters_subscription_lastDownload_invalidURL"], |  | 
| 79   ["synchronize_connection_error", |  | 
| 80    "filters_subscription_lastDownload_connectionError"], |  | 
| 81   ["synchronize_invalid_data", |  | 
| 82    "filters_subscription_lastDownload_invalidData"], |  | 
| 83   ["synchronize_checksum_mismatch", |  | 
| 84    "filters_subscription_lastDownload_checksumMismatch"] |  | 
| 85 ]); |  | 
| 86 |  | 
| 87 let delayedSubscriptionSelection = null; |  | 
| 88 let acceptableAdsUrl; |  | 
| 89 |  | 
| 90 // Loads options from localStorage and sets UI elements accordingly |  | 
| 91 function loadOptions() |  | 
| 92 { | 32 { | 
| 93   // Set page title to i18n version of "Adblock Plus Options" | 33   // Load the mobile version of the options page on Firefox for Android. | 
| 94   document.title = i18n.getMessage("options"); | 34   iframe.src = iframe.getAttribute("data-src-" + application) || | 
| 95 | 35                iframe.getAttribute("data-src"); | 
| 96   // Set links |  | 
| 97   getPref("subscriptions_exceptionsurl", url => |  | 
| 98   { |  | 
| 99     acceptableAdsUrl = url; |  | 
| 100     $("#acceptableAdsLink").attr("href", acceptableAdsUrl); |  | 
| 101   }); |  | 
| 102   getDocLink("acceptable_ads", url => |  | 
| 103   { |  | 
| 104     $("#acceptableAdsDocs").attr("href", url); |  | 
| 105   }); |  | 
| 106   getDocLink("filterdoc", url => |  | 
| 107   { |  | 
| 108     setLinks("filter-must-follow-syntax", url); |  | 
| 109   }); |  | 
| 110   getInfo("application", application => |  | 
| 111   { |  | 
| 112     getInfo("platform", platform => |  | 
| 113     { |  | 
| 114       if (platform == "chromium" && application != "opera") |  | 
| 115         application = "chrome"; |  | 
| 116 |  | 
| 117       getDocLink(application + "_support", url => |  | 
| 118       { |  | 
| 119         setLinks("found-a-bug", url); |  | 
| 120       }); |  | 
| 121 |  | 
| 122       if (platform == "gecko") |  | 
| 123         $("#firefox-warning").removeAttr("hidden"); |  | 
| 124     }); |  | 
| 125   }); |  | 
| 126 |  | 
| 127   // Add event listeners |  | 
| 128   $("#updateFilterLists").click(updateFilterLists); |  | 
| 129   $("#startSubscriptionSelection").click(startSubscriptionSelection); |  | 
| 130   $("#subscriptionSelector").change(updateSubscriptionSelection); |  | 
| 131   $("#addSubscription").click(addSubscriptionClicked); |  | 
| 132   $("#acceptableAds").click(toggleAcceptableAds); |  | 
| 133   $("#whitelistForm").submit(addWhitelistDomain); |  | 
| 134   $("#removeWhitelist").click(removeSelectedExcludedDomain); |  | 
| 135   $("#customFilterForm").submit(addTypedFilter); |  | 
| 136   $("#removeCustomFilter").click(removeSelectedFilters); |  | 
| 137   $("#rawFiltersButton").click(toggleFiltersInRawFormat); |  | 
| 138   $("#importRawFilters").click(importRawFiltersText); |  | 
| 139 |  | 
| 140   // Display jQuery UI elements |  | 
| 141   $("#tabs").tabs(); |  | 
| 142   $("button:not(.subscriptionRemoveButton)").button(); |  | 
| 143   $(".refreshButton").button("option", "icons", {primary: "ui-icon-refresh"}); |  | 
| 144   $(".addButton").button("option", "icons", {primary: "ui-icon-plus"}); |  | 
| 145   $(".removeButton").button("option", "icons", {primary: "ui-icon-minus"}); |  | 
| 146 |  | 
| 147   // Popuplate option checkboxes |  | 
| 148   initCheckbox("shouldShowBlockElementMenu"); |  | 
| 149   initCheckbox("show_devtools_panel"); |  | 
| 150   initCheckbox("shouldShowNotifications", "notifications_ignoredcategories"); |  | 
| 151 |  | 
| 152   getInfo("features", features => |  | 
| 153   { |  | 
| 154     if (!features.devToolsPanel) |  | 
| 155       document.getElementById("showDevtoolsPanelContainer").hidden = true; |  | 
| 156   }); |  | 
| 157   getPref("notifications_showui", showNotificationsUI => |  | 
| 158   { |  | 
| 159     if (!showNotificationsUI) |  | 
| 160       document.getElementById("shouldShowNotificationsContainer").hidden = true; |  | 
| 161   }); |  | 
| 162 |  | 
| 163   // Register listeners in the background message responder |  | 
| 164   ext.backgroundPage.sendMessage({ |  | 
| 165     type: "app.listen", |  | 
| 166     filter: ["addSubscription", "focusSection"] |  | 
| 167   }); |  | 
| 168   ext.backgroundPage.sendMessage({ |  | 
| 169     type: "filters.listen", |  | 
| 170     filter: ["added", "loaded", "removed"] |  | 
| 171   }); |  | 
| 172   ext.backgroundPage.sendMessage({ |  | 
| 173     type: "prefs.listen", |  | 
| 174     filter: ["notifications_ignoredcategories", "notifications_showui", |  | 
| 175              "show_devtools_panel", "shouldShowBlockElementMenu"] |  | 
| 176   }); |  | 
| 177   ext.backgroundPage.sendMessage({ |  | 
| 178     type: "subscriptions.listen", |  | 
| 179     filter: ["added", "disabled", "homepage", "lastDownload", "removed", |  | 
| 180              "title", "downloadStatus", "downloading"] |  | 
| 181   }); |  | 
| 182 |  | 
| 183   // Load recommended subscriptions |  | 
| 184   loadRecommendations(); |  | 
| 185 |  | 
| 186   // Show user's filters |  | 
| 187   reloadFilters(); |  | 
| 188 } |  | 
| 189 $(loadOptions); |  | 
| 190 |  | 
| 191 function convertSpecialSubscription(subscription) |  | 
| 192 { |  | 
| 193   for (let filter of subscription.filters) |  | 
| 194   { |  | 
| 195     if (whitelistedDomainRegexp.test(filter.text)) |  | 
| 196       appendToListBox("excludedDomainsBox", RegExp.$1); |  | 
| 197     else |  | 
| 198       appendToListBox("userFiltersBox", filter.text); |  | 
| 199   } |  | 
| 200 } |  | 
| 201 |  | 
| 202 // Reloads the displayed subscriptions and filters |  | 
| 203 function reloadFilters() |  | 
| 204 { |  | 
| 205   // Load user filter URLs |  | 
| 206   let container = document.getElementById("filterLists"); |  | 
| 207   while (container.lastChild) |  | 
| 208     container.removeChild(container.lastChild); |  | 
| 209 |  | 
| 210   getSubscriptions(true, false, subscriptions => |  | 
| 211   { |  | 
| 212     for (let subscription of subscriptions) |  | 
| 213     { |  | 
| 214       if (subscription.url == acceptableAdsUrl) |  | 
| 215         $("#acceptableAds").prop("checked", !subscription.disabled); |  | 
| 216       else |  | 
| 217         addSubscriptionEntry(subscription); |  | 
| 218     } |  | 
| 219   }); |  | 
| 220 |  | 
| 221   // User-entered filters |  | 
| 222   getSubscriptions(false, true, subscriptions => |  | 
| 223   { |  | 
| 224     document.getElementById("userFiltersBox").innerHTML = ""; |  | 
| 225     document.getElementById("excludedDomainsBox").innerHTML = ""; |  | 
| 226 |  | 
| 227     for (let subscription of subscriptions) |  | 
| 228       convertSpecialSubscription(subscription); |  | 
| 229   }); |  | 
| 230 } |  | 
| 231 |  | 
| 232 function initCheckbox(id, key) |  | 
| 233 { |  | 
| 234   key = key || id; |  | 
| 235   let checkbox = document.getElementById(id); |  | 
| 236 |  | 
| 237   getPref(key, value => |  | 
| 238   { |  | 
| 239     onPrefMessage(key, value); |  | 
| 240   }); |  | 
| 241 |  | 
| 242   checkbox.addEventListener("click", () => |  | 
| 243   { |  | 
| 244     togglePref(key); |  | 
| 245   }, false); |  | 
| 246 } |  | 
| 247 |  | 
| 248 function loadRecommendations() |  | 
| 249 { |  | 
| 250   fetch("subscriptions.xml") |  | 
| 251     .then(response => |  | 
| 252     { |  | 
| 253       return response.text(); |  | 
| 254     }) |  | 
| 255     .then(text => |  | 
| 256     { |  | 
| 257       let selectedIndex = 0; |  | 
| 258       let selectedPrefix = null; |  | 
| 259       let matchCount = 0; |  | 
| 260 |  | 
| 261       let list = document.getElementById("subscriptionSelector"); |  | 
| 262       let doc = new DOMParser().parseFromString(text, "application/xml"); |  | 
| 263       let elements = doc.documentElement.getElementsByTagName("subscription"); |  | 
| 264 |  | 
| 265       for (let i = 0; i < elements.length; i++) |  | 
| 266       { |  | 
| 267         let element = elements[i]; |  | 
| 268         let option = new Option(); |  | 
| 269         option.text = element.getAttribute("title") + " (" + |  | 
| 270                       element.getAttribute("specialization") + ")"; |  | 
| 271         option._data = { |  | 
| 272           title: element.getAttribute("title"), |  | 
| 273           url: element.getAttribute("url"), |  | 
| 274           homepage: element.getAttribute("homepage") |  | 
| 275         }; |  | 
| 276 |  | 
| 277         let prefix = element.getAttribute("prefixes"); |  | 
| 278         if (prefix) |  | 
| 279         { |  | 
| 280           prefix = prefix.replace(/\W/g, "_"); |  | 
| 281           option.style.fontWeight = "bold"; |  | 
| 282           option.style.backgroundColor = "#E0FFE0"; |  | 
| 283           option.style.color = "#000000"; |  | 
| 284           if (!selectedPrefix || selectedPrefix.length < prefix.length) |  | 
| 285           { |  | 
| 286             selectedIndex = i; |  | 
| 287             selectedPrefix = prefix; |  | 
| 288             matchCount = 1; |  | 
| 289           } |  | 
| 290           else if (selectedPrefix && selectedPrefix.length == prefix.length) |  | 
| 291           { |  | 
| 292             matchCount++; |  | 
| 293 |  | 
| 294             // If multiple items have a matching prefix of the same length: |  | 
| 295             // Select one of the items randomly, probability should be the same |  | 
| 296             // for all items. So we replace the previous match here with |  | 
| 297             // probability 1/N (N being the number of matches). |  | 
| 298             if (Math.random() * matchCount < 1) |  | 
| 299             { |  | 
| 300               selectedIndex = i; |  | 
| 301               selectedPrefix = prefix; |  | 
| 302             } |  | 
| 303           } |  | 
| 304         } |  | 
| 305         list.appendChild(option); |  | 
| 306       } |  | 
| 307 |  | 
| 308       let option = new Option(); |  | 
| 309       let label = i18n.getMessage("filters_addSubscriptionOther_label"); |  | 
| 310       option.text = label + "\u2026"; |  | 
| 311       option._data = null; |  | 
| 312       list.appendChild(option); |  | 
| 313 |  | 
| 314       list.selectedIndex = selectedIndex; |  | 
| 315 |  | 
| 316       if (delayedSubscriptionSelection) |  | 
| 317         startSubscriptionSelection(...delayedSubscriptionSelection); |  | 
| 318     }); |  | 
| 319 } |  | 
| 320 |  | 
| 321 function startSubscriptionSelection(title, url) |  | 
| 322 { |  | 
| 323   let list = document.getElementById("subscriptionSelector"); |  | 
| 324   if (list.length == 0) |  | 
| 325   { |  | 
| 326     delayedSubscriptionSelection = [title, url]; |  | 
| 327     return; |  | 
| 328   } |  | 
| 329 |  | 
| 330   $("#tabs").tabs("option", "active", 0); |  | 
| 331   $("#addSubscriptionContainer").show(); |  | 
| 332   $("#addSubscriptionButton").hide(); |  | 
| 333   $("#subscriptionSelector").focus(); |  | 
| 334   if (typeof url != "undefined") |  | 
| 335   { |  | 
| 336     list.selectedIndex = list.length - 1; |  | 
| 337     document.getElementById("customSubscriptionTitle").value = title; |  | 
| 338     document.getElementById("customSubscriptionLocation").value = url; |  | 
| 339   } |  | 
| 340   updateSubscriptionSelection(); |  | 
| 341   document.getElementById("addSubscriptionContainer").scrollIntoView(true); |  | 
| 342 } |  | 
| 343 |  | 
| 344 function updateSubscriptionSelection() |  | 
| 345 { |  | 
| 346   let list = document.getElementById("subscriptionSelector"); |  | 
| 347   let data = list.options[list.selectedIndex]._data; |  | 
| 348   if (data) |  | 
| 349     $("#customSubscriptionContainer").hide(); |  | 
| 350   else |  | 
| 351   { |  | 
| 352     $("#customSubscriptionContainer").show(); |  | 
| 353     $("#customSubscriptionTitle").focus(); |  | 
| 354   } |  | 
| 355 } |  | 
| 356 |  | 
| 357 function addSubscriptionClicked() |  | 
| 358 { |  | 
| 359   let list = document.getElementById("subscriptionSelector"); |  | 
| 360   let data = list.options[list.selectedIndex]._data; |  | 
| 361   if (data) |  | 
| 362     addSubscription(data.url, data.title, data.homepage); |  | 
| 363   else |  | 
| 364   { |  | 
| 365     let url = document.getElementById("customSubscriptionLocation") |  | 
| 366                       .value.trim(); |  | 
| 367     if (!/^https?:/i.test(url)) |  | 
| 368     { |  | 
| 369       alert(i18n.getMessage("global_subscription_invalid_location")); |  | 
| 370       $("#customSubscriptionLocation").focus(); |  | 
| 371       return; |  | 
| 372     } |  | 
| 373 |  | 
| 374     let title = document.getElementById("customSubscriptionTitle").value.trim(); |  | 
| 375     if (!title) |  | 
| 376       title = url; |  | 
| 377 |  | 
| 378     addSubscription(url, title, null); |  | 
| 379   } |  | 
| 380 |  | 
| 381   $("#addSubscriptionContainer").hide(); |  | 
| 382   $("#customSubscriptionContainer").hide(); |  | 
| 383   $("#addSubscriptionButton").show(); |  | 
| 384 } |  | 
| 385 |  | 
| 386 function toggleAcceptableAds() |  | 
| 387 { |  | 
| 388   toggleSubscription(acceptableAdsUrl, true); |  | 
| 389 } |  | 
| 390 |  | 
| 391 function findSubscriptionElement(subscription) |  | 
| 392 { |  | 
| 393   for (let child of document.getElementById("filterLists").childNodes) |  | 
| 394   { |  | 
| 395     if (child._subscription.url == subscription.url) |  | 
| 396       return child; |  | 
| 397   } |  | 
| 398   return null; |  | 
| 399 } |  | 
| 400 |  | 
| 401 function updateSubscriptionInfo(element, subscription) |  | 
| 402 { |  | 
| 403   if (subscription) |  | 
| 404     element._subscription = subscription; |  | 
| 405   else |  | 
| 406     subscription = element._subscription; |  | 
| 407 |  | 
| 408   let title = element.getElementsByClassName("subscriptionTitle")[0]; |  | 
| 409   title.textContent = subscription.title; |  | 
| 410   title.setAttribute("title", subscription.url); |  | 
| 411   if (subscription.homepage) |  | 
| 412     title.href = subscription.homepage; |  | 
| 413   else |  | 
| 414     title.href = subscription.url; |  | 
| 415 |  | 
| 416   let enabled = element.getElementsByClassName("subscriptionEnabled")[0]; |  | 
| 417   enabled.checked = !subscription.disabled; |  | 
| 418 |  | 
| 419   let lastUpdate = element.getElementsByClassName("subscriptionUpdate")[0]; |  | 
| 420   lastUpdate.classList.remove("error"); |  | 
| 421 |  | 
| 422   let {downloadStatus} = subscription; |  | 
| 423   if (subscription.isDownloading) |  | 
| 424   { |  | 
| 425     lastUpdate.textContent = i18n.getMessage( |  | 
| 426       "filters_subscription_lastDownload_inProgress" |  | 
| 427     ); |  | 
| 428   } |  | 
| 429   else if (downloadStatus && downloadStatus != "synchronize_ok") |  | 
| 430   { |  | 
| 431     if (statusMessages.has(downloadStatus)) |  | 
| 432     { |  | 
| 433       lastUpdate.textContent = i18n.getMessage( |  | 
| 434         statusMessages.get(downloadStatus) |  | 
| 435       ); |  | 
| 436     } |  | 
| 437     else |  | 
| 438       lastUpdate.textContent = downloadStatus; |  | 
| 439     lastUpdate.classList.add("error"); |  | 
| 440   } |  | 
| 441   else if (subscription.lastDownload > 0) |  | 
| 442   { |  | 
| 443     let timeDate = i18nTimeDateStrings(subscription.lastDownload * 1000); |  | 
| 444     let messageID = (timeDate[1] ? "last_updated_at" : "last_updated_at_today"); |  | 
| 445     lastUpdate.textContent = i18n.getMessage(messageID, timeDate); |  | 
| 446   } |  | 
| 447 } |  | 
| 448 |  | 
| 449 function onSubscriptionMessage(action, subscription) |  | 
| 450 { |  | 
| 451   let element = findSubscriptionElement(subscription); |  | 
| 452 |  | 
| 453   switch (action) |  | 
| 454   { |  | 
| 455     case "disabled": |  | 
| 456     case "downloading": |  | 
| 457     case "downloadStatus": |  | 
| 458     case "homepage": |  | 
| 459     case "lastDownload": |  | 
| 460     case "title": |  | 
| 461       if (element) |  | 
| 462         updateSubscriptionInfo(element, subscription); |  | 
| 463       break; |  | 
| 464     case "added": |  | 
| 465       if (subscription.url.indexOf("~user") == 0) |  | 
| 466         convertSpecialSubscription(subscription); |  | 
| 467       else if (subscription.url == acceptableAdsUrl) |  | 
| 468         $("#acceptableAds").prop("checked", true); |  | 
| 469       else if (!element) |  | 
| 470         addSubscriptionEntry(subscription); |  | 
| 471       break; |  | 
| 472     case "removed": |  | 
| 473       if (subscription.url == acceptableAdsUrl) |  | 
| 474         $("#acceptableAds").prop("checked", false); |  | 
| 475       else if (element) |  | 
| 476         element.parentNode.removeChild(element); |  | 
| 477       break; |  | 
| 478   } |  | 
| 479 } |  | 
| 480 |  | 
| 481 function onPrefMessage(key, value) |  | 
| 482 { |  | 
| 483   switch (key) |  | 
| 484   { |  | 
| 485     case "notifications_showui": |  | 
| 486       document.getElementById( |  | 
| 487         "shouldShowNotificationsContainer" |  | 
| 488       ).hidden = !value; |  | 
| 489       return; |  | 
| 490     case "notifications_ignoredcategories": |  | 
| 491       key = "shouldShowNotifications"; |  | 
| 492       value = value.indexOf("*") == -1; |  | 
| 493       break; |  | 
| 494   } |  | 
| 495   let checkbox = document.getElementById(key); |  | 
| 496   if (checkbox) |  | 
| 497     checkbox.checked = value; |  | 
| 498 } |  | 
| 499 |  | 
| 500 function onFilterMessage(action, filter) |  | 
| 501 { |  | 
| 502   switch (action) |  | 
| 503   { |  | 
| 504     case "loaded": |  | 
| 505       reloadFilters(); |  | 
| 506       break; |  | 
| 507     case "added": |  | 
| 508       if (whitelistedDomainRegexp.test(filter.text)) |  | 
| 509         appendToListBox("excludedDomainsBox", RegExp.$1); |  | 
| 510       else |  | 
| 511         appendToListBox("userFiltersBox", filter.text); |  | 
| 512       break; |  | 
| 513     case "removed": |  | 
| 514       if (whitelistedDomainRegexp.test(filter.text)) |  | 
| 515         removeFromListBox("excludedDomainsBox", RegExp.$1); |  | 
| 516       else |  | 
| 517         removeFromListBox("userFiltersBox", filter.text); |  | 
| 518       break; |  | 
| 519   } |  | 
| 520 } |  | 
| 521 |  | 
| 522 // Add a filter string to the list box. |  | 
| 523 function appendToListBox(boxId, text) |  | 
| 524 { |  | 
| 525   // Note: document.createElement("option") is unreliable in Opera |  | 
| 526   let elt = new Option(); |  | 
| 527   elt.text = text; |  | 
| 528   elt.value = text; |  | 
| 529   document.getElementById(boxId).appendChild(elt); |  | 
| 530 } |  | 
| 531 |  | 
| 532 // Remove a filter string from a list box. |  | 
| 533 function removeFromListBox(boxId, text) |  | 
| 534 { |  | 
| 535   let list = document.getElementById(boxId); |  | 
| 536   // Edge does not support CSS.escape yet: |  | 
| 537   // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/101410
     / |  | 
| 538   quoteCSS(text, escapedCSS => |  | 
| 539   { |  | 
| 540     let selector = "option[value=" + escapedCSS + "]"; |  | 
| 541     for (let option of list.querySelectorAll(selector)) |  | 
| 542       list.removeChild(option); |  | 
| 543   }); |  | 
| 544 } |  | 
| 545 |  | 
| 546 function addWhitelistDomain(event) |  | 
| 547 { |  | 
| 548   event.preventDefault(); |  | 
| 549 |  | 
| 550   let domain = document.getElementById( |  | 
| 551     "newWhitelistDomain" |  | 
| 552   ).value.replace(/\s/g, ""); |  | 
| 553   document.getElementById("newWhitelistDomain").value = ""; |  | 
| 554   if (!domain) |  | 
| 555     return; |  | 
| 556 |  | 
| 557   let filterText = "@@||" + domain + "^$document"; |  | 
| 558   addFilter(filterText); |  | 
| 559 } |  | 
| 560 |  | 
| 561 // Adds filter text that user typed to the selection box |  | 
| 562 function addTypedFilter(event) |  | 
| 563 { |  | 
| 564   event.preventDefault(); |  | 
| 565 |  | 
| 566   let element = document.getElementById("newFilter"); |  | 
| 567   addFilter(element.value, errors => |  | 
| 568   { |  | 
| 569     if (errors.length > 0) |  | 
| 570       alert(errors.join("\n")); |  | 
| 571     else |  | 
| 572       element.value = ""; |  | 
| 573   }); |  | 
| 574 } |  | 
| 575 |  | 
| 576 // Removes currently selected whitelisted domains |  | 
| 577 function removeSelectedExcludedDomain(event) |  | 
| 578 { |  | 
| 579   event.preventDefault(); |  | 
| 580   let remove = []; |  | 
| 581   for (let option of document.getElementById("excludedDomainsBox").options) |  | 
| 582   { |  | 
| 583     if (option.selected) |  | 
| 584       remove.push(option.value); |  | 
| 585   } |  | 
| 586   if (!remove.length) |  | 
| 587     return; |  | 
| 588 |  | 
| 589   for (let domain of remove) |  | 
| 590     removeFilter("@@||" + domain + "^$document"); |  | 
| 591 } |  | 
| 592 |  | 
| 593 // Removes all currently selected filters |  | 
| 594 function removeSelectedFilters(event) |  | 
| 595 { |  | 
| 596   event.preventDefault(); |  | 
| 597   let options = document.querySelectorAll("#userFiltersBox > option:checked"); |  | 
| 598   for (let option of options) |  | 
| 599     removeFilter(option.value); |  | 
| 600 } |  | 
| 601 |  | 
| 602 // Shows raw filters box and fills it with the current user filters |  | 
| 603 function toggleFiltersInRawFormat(event) |  | 
| 604 { |  | 
| 605   event.preventDefault(); |  | 
| 606 |  | 
| 607   let rawFilters = document.getElementById("rawFilters"); |  | 
| 608   let filters = []; |  | 
| 609 |  | 
| 610   if (rawFilters.style.display != "table-row") |  | 
| 611   { |  | 
| 612     rawFilters.style.display = "table-row"; |  | 
| 613     for (let option of document.getElementById("userFiltersBox").options) |  | 
| 614       filters.push(option.value); |  | 
| 615   } |  | 
| 616   else |  | 
| 617   { |  | 
| 618     rawFilters.style.display = "none"; |  | 
| 619   } |  | 
| 620 |  | 
| 621   document.getElementById("rawFiltersText").value = filters.join("\n"); |  | 
| 622 } |  | 
| 623 |  | 
| 624 // Imports filters in the raw text box |  | 
| 625 function importRawFiltersText() |  | 
| 626 { |  | 
| 627   let text = document.getElementById("rawFiltersText").value; |  | 
| 628 |  | 
| 629   importRawFilters(text, true, errors => |  | 
| 630   { |  | 
| 631     if (errors.length > 0) |  | 
| 632       alert(errors.join("\n")); |  | 
| 633     else |  | 
| 634       $("#rawFilters").hide(); |  | 
| 635   }); |  | 
| 636 } |  | 
| 637 |  | 
| 638 // Called when user explicitly requests filter list updates |  | 
| 639 function updateFilterLists() |  | 
| 640 { |  | 
| 641   // Without the URL parameter this will update all subscriptions |  | 
| 642   updateSubscription(); |  | 
| 643 } |  | 
| 644 |  | 
| 645 // Adds a subscription entry to the UI. |  | 
| 646 function addSubscriptionEntry(subscription) |  | 
| 647 { |  | 
| 648   let template = document.getElementById("subscriptionTemplate"); |  | 
| 649   let element = template.cloneNode(true); |  | 
| 650   element.removeAttribute("id"); |  | 
| 651   element._subscription = subscription; |  | 
| 652 |  | 
| 653   let removeButton = element.getElementsByClassName( |  | 
| 654     "subscriptionRemoveButton" |  | 
| 655   )[0]; |  | 
| 656   removeButton.setAttribute("title", removeButton.textContent); |  | 
| 657   removeButton.textContent = "\xD7"; |  | 
| 658   removeButton.addEventListener("click", () => |  | 
| 659   { |  | 
| 660     if (!confirm(i18n.getMessage("global_remove_subscription_warning"))) |  | 
| 661       return; |  | 
| 662 |  | 
| 663     removeSubscription(subscription.url); |  | 
| 664   }, false); |  | 
| 665 |  | 
| 666   getPref("additional_subscriptions", additionalSubscriptions => |  | 
| 667   { |  | 
| 668     if (additionalSubscriptions.includes(subscription.url)) |  | 
| 669       removeButton.style.visibility = "hidden"; |  | 
| 670   }); |  | 
| 671 |  | 
| 672   let enabled = element.getElementsByClassName("subscriptionEnabled")[0]; |  | 
| 673   enabled.addEventListener("click", () => |  | 
| 674   { |  | 
| 675     subscription.disabled = !subscription.disabled; |  | 
| 676     toggleSubscription(subscription.url, true); |  | 
| 677   }, false); |  | 
| 678 |  | 
| 679   updateSubscriptionInfo(element); |  | 
| 680 |  | 
| 681   document.getElementById("filterLists").appendChild(element); |  | 
| 682 } |  | 
| 683 |  | 
| 684 function setLinks(id, ...args) |  | 
| 685 { |  | 
| 686   let element = document.getElementById(id); |  | 
| 687   if (!element) |  | 
| 688     return; |  | 
| 689 |  | 
| 690   let links = element.getElementsByTagName("a"); |  | 
| 691   for (let i = 0; i < links.length; i++) |  | 
| 692   { |  | 
| 693     if (typeof args[i] == "string") |  | 
| 694     { |  | 
| 695       links[i].href = args[i]; |  | 
| 696       links[i].setAttribute("target", "_blank"); |  | 
| 697     } |  | 
| 698     else if (typeof args[i] == "function") |  | 
| 699     { |  | 
| 700       links[i].href = "javascript:void(0);"; |  | 
| 701       links[i].addEventListener("click", args[i], false); |  | 
| 702     } |  | 
| 703   } |  | 
| 704 } |  | 
| 705 |  | 
| 706 ext.onMessage.addListener(message => |  | 
| 707 { |  | 
| 708   switch (message.type) |  | 
| 709   { |  | 
| 710     case "app.respond": |  | 
| 711       switch (message.action) |  | 
| 712       { |  | 
| 713         case "addSubscription": |  | 
| 714           let subscription = message.args[0]; |  | 
| 715           startSubscriptionSelection(subscription.title, subscription.url); |  | 
| 716           break; |  | 
| 717         case "focusSection": |  | 
| 718           for (let tab of document.getElementsByClassName("ui-tabs-panel")) |  | 
| 719           { |  | 
| 720             let found = tab.querySelector( |  | 
| 721               "[data-section='" + message.args[0] + "']" |  | 
| 722             ); |  | 
| 723             if (!found) |  | 
| 724               continue; |  | 
| 725 |  | 
| 726             let previous = document.getElementsByClassName("focused"); |  | 
| 727             if (previous.length > 0) |  | 
| 728               previous[0].classList.remove("focused"); |  | 
| 729 |  | 
| 730             let index = $("[href='#" + tab.id + "']").parent().index(); |  | 
| 731             $("#tabs").tabs("option", "active", index); |  | 
| 732             found.classList.add("focused"); |  | 
| 733           } |  | 
| 734           break; |  | 
| 735       } |  | 
| 736       break; |  | 
| 737     case "filters.respond": |  | 
| 738       onFilterMessage(message.action, message.args[0]); |  | 
| 739       break; |  | 
| 740     case "prefs.respond": |  | 
| 741       onPrefMessage(message.action, message.args[0]); |  | 
| 742       break; |  | 
| 743     case "subscriptions.respond": |  | 
| 744       onSubscriptionMessage(message.action, message.args[0]); |  | 
| 745       break; |  | 
| 746   } |  | 
| 747 }); | 36 }); | 
| OLD | NEW | 
|---|