| OLD | NEW | 
| (Empty) |  | 
 |    1 /* | 
 |    2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
 |    3  * Copyright (C) 2006-present 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 /* globals getDocLink */ | 
 |   19  | 
 |   20 "use strict"; | 
 |   21  | 
 |   22 { | 
 |   23   const {getMessage} = ext.i18n; | 
 |   24  | 
 |   25   const dialogSubscribe = "subscribe"; | 
 |   26   let whitelistFilter = null; | 
 |   27   let promisedAcceptableAdsUrl = getAcceptableAdsUrl(); | 
 |   28  | 
 |   29   /* Utility functions */ | 
 |   30  | 
 |   31   function get(selector, origin) | 
 |   32   { | 
 |   33     return (origin || document).querySelector(selector); | 
 |   34   } | 
 |   35  | 
 |   36   function getAll(selector, origin) | 
 |   37   { | 
 |   38     return (origin || document).querySelectorAll(selector); | 
 |   39   } | 
 |   40  | 
 |   41   function create(parent, tagName, content, attributes, onclick) | 
 |   42   { | 
 |   43     let element = document.createElement(tagName); | 
 |   44  | 
 |   45     if (typeof content == "string") | 
 |   46     { | 
 |   47       element.textContent = content; | 
 |   48     } | 
 |   49  | 
 |   50     if (attributes) | 
 |   51     { | 
 |   52       for (let name in attributes) | 
 |   53       { | 
 |   54         element.setAttribute(name, attributes[name]); | 
 |   55       } | 
 |   56     } | 
 |   57  | 
 |   58     if (onclick) | 
 |   59     { | 
 |   60       element.addEventListener("click", (ev) => | 
 |   61       { | 
 |   62         onclick(ev); | 
 |   63         ev.stopPropagation(); | 
 |   64       }); | 
 |   65     } | 
 |   66  | 
 |   67     parent.appendChild(element); | 
 |   68     return element; | 
 |   69   } | 
 |   70  | 
 |   71   /* Extension interactions */ | 
 |   72  | 
 |   73   function getInstalled() | 
 |   74   { | 
 |   75     return new Promise((resolve, reject) => | 
 |   76     { | 
 |   77       ext.backgroundPage.sendMessage( | 
 |   78         {type: "subscriptions.get", downloadable: true}, | 
 |   79         resolve | 
 |   80       ); | 
 |   81     }); | 
 |   82   } | 
 |   83  | 
 |   84   function getAcceptableAdsUrl() | 
 |   85   { | 
 |   86     return new Promise((resolve, reject) => | 
 |   87     { | 
 |   88       ext.backgroundPage.sendMessage( | 
 |   89         {type: "prefs.get", key: "subscriptions_exceptionsurl"}, | 
 |   90         resolve | 
 |   91       ); | 
 |   92     }); | 
 |   93   } | 
 |   94  | 
 |   95   function getRecommendedAds() | 
 |   96   { | 
 |   97     return fetch("subscriptions.xml") | 
 |   98       .then((response) => response.text()) | 
 |   99       .then((text) => | 
 |  100       { | 
 |  101         let doc = new DOMParser().parseFromString(text, "application/xml"); | 
 |  102         let elements = Array.from(doc.getElementsByTagName("subscription")); | 
 |  103  | 
 |  104         return elements | 
 |  105           .filter((element) => element.getAttribute("type") == "ads") | 
 |  106           .map((element) => | 
 |  107           { | 
 |  108             return { | 
 |  109               title: element.getAttribute("title"), | 
 |  110               url: element.getAttribute("url") | 
 |  111             }; | 
 |  112           }); | 
 |  113       }); | 
 |  114   } | 
 |  115  | 
 |  116   function installSubscription(url, title) | 
 |  117   { | 
 |  118     ext.backgroundPage.sendMessage({type: "subscriptions.add", url, title}); | 
 |  119   } | 
 |  120  | 
 |  121   function uninstallSubscription(url) | 
 |  122   { | 
 |  123     ext.backgroundPage.sendMessage({type: "subscriptions.remove", url}); | 
 |  124   } | 
 |  125  | 
 |  126   /* Actions */ | 
 |  127  | 
 |  128   function setSubscription({disabled, title, url}, shouldAdd) | 
 |  129   { | 
 |  130     if (disabled) | 
 |  131       return; | 
 |  132  | 
 |  133     promisedAcceptableAdsUrl.then((acceptableAdsUrl) => | 
 |  134     { | 
 |  135       if (url == acceptableAdsUrl) | 
 |  136       { | 
 |  137         get("#acceptableAds").checked = true; | 
 |  138         return; | 
 |  139       } | 
 |  140  | 
 |  141       let listInstalled = get("#subscriptions-installed"); | 
 |  142       let installed = get(`[data-url="${url}"]`, listInstalled); | 
 |  143  | 
 |  144       if (installed) | 
 |  145       { | 
 |  146         let titleElement = get("span", installed); | 
 |  147         titleElement.textContent = title || url; | 
 |  148       } | 
 |  149       else if (shouldAdd) | 
 |  150       { | 
 |  151         let element = create(listInstalled, "li", null, {"data-url": url}); | 
 |  152         create(element, "span", title || url); | 
 |  153         create(element, "button", null, {class: "remove"}, | 
 |  154           () => uninstallSubscription(url) | 
 |  155         ); | 
 |  156  | 
 |  157         let recommended = get(`#subscriptions-recommended [data-url="${url}"]`); | 
 |  158         if (recommended) | 
 |  159         { | 
 |  160           recommended.classList.add("installed"); | 
 |  161         } | 
 |  162       } | 
 |  163     }); | 
 |  164   } | 
 |  165  | 
 |  166   function removeSubscription(url) | 
 |  167   { | 
 |  168     promisedAcceptableAdsUrl.then((acceptableAdsUrl) => | 
 |  169     { | 
 |  170       if (url == acceptableAdsUrl) | 
 |  171       { | 
 |  172         get("#acceptableAds").checked = false; | 
 |  173         return; | 
 |  174       } | 
 |  175  | 
 |  176       let installed = get(`#subscriptions-installed [data-url="${url}"]`); | 
 |  177       if (installed) | 
 |  178       { | 
 |  179         installed.parentNode.removeChild(installed); | 
 |  180       } | 
 |  181  | 
 |  182       let recommended = get(`#subscriptions-recommended [data-url="${url}"]`); | 
 |  183       if (recommended) | 
 |  184       { | 
 |  185         recommended.classList.remove("installed"); | 
 |  186       } | 
 |  187     }); | 
 |  188   } | 
 |  189  | 
 |  190   function setDialog(id, options) | 
 |  191   { | 
 |  192     if (!id) | 
 |  193     { | 
 |  194       delete document.body.dataset.dialog; | 
 |  195       return; | 
 |  196     } | 
 |  197  | 
 |  198     let fields = getAll(`#dialog-${id} input`); | 
 |  199     for (let field of fields) | 
 |  200     { | 
 |  201       let {name} = field; | 
 |  202       field.value = (options && name in options) ? options[name] : ""; | 
 |  203     } | 
 |  204     setError(id, null); | 
 |  205  | 
 |  206     document.body.dataset.dialog = id; | 
 |  207   } | 
 |  208  | 
 |  209   function setError(dialogId, fieldName) | 
 |  210   { | 
 |  211     let dialog = get(`#dialog-${dialogId}`); | 
 |  212     if (fieldName) | 
 |  213     { | 
 |  214       dialog.dataset.error = fieldName; | 
 |  215     } | 
 |  216     else | 
 |  217     { | 
 |  218       delete dialog.dataset.error; | 
 |  219     } | 
 |  220   } | 
 |  221  | 
 |  222   function populateLists() | 
 |  223   { | 
 |  224     Promise.all([getInstalled(), getRecommendedAds()]) | 
 |  225       .then(([installed, recommended]) => | 
 |  226       { | 
 |  227         let listRecommended = get("#subscriptions-recommended"); | 
 |  228         for (let {title, url} of recommended) | 
 |  229         { | 
 |  230           create(listRecommended, "li", title, {"data-url": url}, | 
 |  231             (ev) => | 
 |  232             { | 
 |  233               if (ev.target.classList.contains("installed")) | 
 |  234                 return; | 
 |  235  | 
 |  236               setDialog(dialogSubscribe, {title, url}); | 
 |  237             } | 
 |  238           ); | 
 |  239         } | 
 |  240  | 
 |  241         for (let subscription of installed) | 
 |  242         { | 
 |  243           if (subscription.disabled) | 
 |  244             continue; | 
 |  245  | 
 |  246           setSubscription(subscription, true); | 
 |  247         } | 
 |  248       }) | 
 |  249       .catch((err) => console.error(err)); | 
 |  250   } | 
 |  251  | 
 |  252   /* Listeners */ | 
 |  253  | 
 |  254   function onChange(ev) | 
 |  255   { | 
 |  256     if (ev.target.id != "acceptableAds") | 
 |  257       return; | 
 |  258  | 
 |  259     promisedAcceptableAdsUrl.then((acceptableAdsUrl) => | 
 |  260     { | 
 |  261       if (ev.target.checked) | 
 |  262       { | 
 |  263         installSubscription(acceptableAdsUrl, null); | 
 |  264       } | 
 |  265       else | 
 |  266       { | 
 |  267         uninstallSubscription(acceptableAdsUrl); | 
 |  268       } | 
 |  269     }); | 
 |  270   } | 
 |  271   document.addEventListener("change", onChange); | 
 |  272  | 
 |  273   function toggleWhitelistFilter(toggle) | 
 |  274   { | 
 |  275     if (whitelistFilter) | 
 |  276     { | 
 |  277       ext.backgroundPage.sendMessage( | 
 |  278         { | 
 |  279           type: (toggle.checked) ? "filters.remove" : "filters.add", | 
 |  280           text: whitelistFilter | 
 |  281         }, | 
 |  282         (errors) => | 
 |  283         { | 
 |  284           if (errors.length < 1) | 
 |  285             return; | 
 |  286  | 
 |  287           console.error(errors); | 
 |  288           toggle.checked = !toggle.checked; | 
 |  289         } | 
 |  290       ); | 
 |  291     } | 
 |  292     else | 
 |  293     { | 
 |  294       console.error("Whitelist filter hasn't been initialized yet"); | 
 |  295     } | 
 |  296   } | 
 |  297  | 
 |  298   function onClick(ev) | 
 |  299   { | 
 |  300     switch (ev.target.dataset.action) | 
 |  301     { | 
 |  302       case "close-dialog": | 
 |  303         setDialog(null); | 
 |  304         break; | 
 |  305       case "open-dialog": | 
 |  306         setDialog(ev.target.dataset.dialog); | 
 |  307         break; | 
 |  308       case "toggle-enabled": | 
 |  309         toggleWhitelistFilter(ev.target); | 
 |  310         ev.preventDefault(); | 
 |  311         break; | 
 |  312     } | 
 |  313   } | 
 |  314   document.addEventListener("click", onClick); | 
 |  315  | 
 |  316   function onSubmit(ev) | 
 |  317   { | 
 |  318     let fields = ev.target.elements; | 
 |  319     let title = fields.title.value; | 
 |  320     let url = fields.url.value; | 
 |  321  | 
 |  322     if (!title) | 
 |  323     { | 
 |  324       setError(dialogSubscribe, "title"); | 
 |  325     } | 
 |  326     else if (!url) | 
 |  327     { | 
 |  328       setError(dialogSubscribe, "url"); | 
 |  329     } | 
 |  330     else | 
 |  331     { | 
 |  332       installSubscription(url, title); | 
 |  333       setDialog(null); | 
 |  334     } | 
 |  335  | 
 |  336     ev.preventDefault(); | 
 |  337   } | 
 |  338   document.addEventListener("submit", onSubmit); | 
 |  339  | 
 |  340   function onMessage(msg) | 
 |  341   { | 
 |  342     switch (msg.type) | 
 |  343     { | 
 |  344       case "app.respond": { | 
 |  345         switch (msg.action) | 
 |  346         { | 
 |  347           case "addSubscription": | 
 |  348             let [subscription] = msg.args; | 
 |  349             setDialog(dialogSubscribe, { | 
 |  350               title: subscription.title, | 
 |  351               url: subscription.url | 
 |  352             }); | 
 |  353             break; | 
 |  354           case "showPageOptions": | 
 |  355             let [{host, whitelisted}] = msg.args; | 
 |  356             whitelistFilter = `@@||${host}^$document`; | 
 |  357  | 
 |  358             ext.i18n.setElementText( | 
 |  359               get("#enabled-label"), | 
 |  360               "mops_enabled_label", | 
 |  361               [host] | 
 |  362             ); | 
 |  363  | 
 |  364             let toggle = get("#enabled"); | 
 |  365             toggle.checked = !whitelisted; | 
 |  366  | 
 |  367             get("#enabled-container").hidden = false; | 
 |  368             break; | 
 |  369         } | 
 |  370         break; | 
 |  371       } | 
 |  372       case "filters.respond": { | 
 |  373         let [filter] = msg.args; | 
 |  374         if (!whitelistFilter || filter.text != whitelistFilter) | 
 |  375           break; | 
 |  376  | 
 |  377         get("#enabled").checked = (msg.action == "removed"); | 
 |  378         break; | 
 |  379       } | 
 |  380       case "subscriptions.respond": { | 
 |  381         let [subscription] = msg.args; | 
 |  382         switch (msg.action) | 
 |  383         { | 
 |  384           case "added": | 
 |  385             setSubscription(subscription, true); | 
 |  386             break; | 
 |  387           case "disabled": | 
 |  388             if (subscription.disabled) | 
 |  389             { | 
 |  390               removeSubscription(subscription.url); | 
 |  391             } | 
 |  392             else | 
 |  393             { | 
 |  394               setSubscription(subscription, true); | 
 |  395             } | 
 |  396             break; | 
 |  397           case "removed": | 
 |  398             removeSubscription(subscription.url); | 
 |  399             break; | 
 |  400           case "title": | 
 |  401             // We're also receiving these messages for subscriptions that are | 
 |  402             // not installed so we shouldn't add those by accident | 
 |  403             setSubscription(subscription, false); | 
 |  404             break; | 
 |  405         } | 
 |  406         break; | 
 |  407       } | 
 |  408     } | 
 |  409   } | 
 |  410   ext.onMessage.addListener(onMessage); | 
 |  411  | 
 |  412   ext.backgroundPage.sendMessage({ | 
 |  413     type: "app.listen", | 
 |  414     filter: ["addSubscription", "showPageOptions"] | 
 |  415   }); | 
 |  416  | 
 |  417   ext.backgroundPage.sendMessage({ | 
 |  418     type: "filters.listen", | 
 |  419     filter: ["added", "removed"] | 
 |  420   }); | 
 |  421  | 
 |  422   ext.backgroundPage.sendMessage({ | 
 |  423     type: "subscriptions.listen", | 
 |  424     filter: ["added", "disabled", "removed", "title"] | 
 |  425   }); | 
 |  426  | 
 |  427   /* Initialization */ | 
 |  428  | 
 |  429   populateLists(); | 
 |  430  | 
 |  431   getDocLink("acceptable_ads", (link) => | 
 |  432   { | 
 |  433     get("#acceptableAds-more").href = link; | 
 |  434   }); | 
 |  435  | 
 |  436   get("#dialog-subscribe [name='title']").setAttribute( | 
 |  437     "placeholder", | 
 |  438     getMessage("mops_subscribe_title") | 
 |  439   ); | 
 |  440  | 
 |  441   get("#dialog-subscribe [name='url']").setAttribute( | 
 |  442     "placeholder", | 
 |  443     getMessage("mops_subscribe_url") | 
 |  444   ); | 
 |  445 } | 
| OLD | NEW |