| LEFT | RIGHT | 
|    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 "use strict"; |   18 "use strict"; | 
|   19  |   19  | 
|   20 /** |   20 /** | 
|   21  * @fileOverview Element hiding implementation. |   21  * @fileOverview Element hiding implementation. | 
|   22  */ |   22  */ | 
|   23  |   23  | 
|   24 const {ElemHideException} = require("./filterClasses"); |   24 const {ElemHideExceptions} = require("./elemHideExceptions"); | 
|   25 const {FilterNotifier} = require("./filterNotifier"); |   25 const {FilterNotifier} = require("./filterNotifier"); | 
|   26  |   26  | 
|   27 /** |   27 /** | 
|   28  * Lookup table, active flag, by filter by domain. |   28  * Lookup table, active flag, by filter by domain. | 
|   29  * (Only contains filters that aren't unconditionally matched for all domains.) |   29  * (Only contains filters that aren't unconditionally matched for all domains.) | 
|   30  * @type {Map.<string,Map.<Filter,boolean>>} |   30  * @type {Map.<string,Map.<Filter,boolean>>} | 
|   31  */ |   31  */ | 
|   32 let filtersByDomain = new Map(); |   32 let filtersByDomain = new Map(); | 
|   33  |   33  | 
|   34 /** |   34 /** | 
| (...skipping 12 matching lines...) Expand all  Loading... | 
|   47 let unconditionalSelectors = null; |   47 let unconditionalSelectors = null; | 
|   48  |   48  | 
|   49 /** |   49 /** | 
|   50  * Map to be used instead when a filter has a blank domains property. |   50  * Map to be used instead when a filter has a blank domains property. | 
|   51  * @type {Map.<string,boolean>} |   51  * @type {Map.<string,boolean>} | 
|   52  * @const |   52  * @const | 
|   53  */ |   53  */ | 
|   54 let defaultDomains = new Map([["", true]]); |   54 let defaultDomains = new Map([["", true]]); | 
|   55  |   55  | 
|   56 /** |   56 /** | 
|   57  * Set containing known element hiding and exception filters |   57  * Set containing known element hiding filters | 
|   58  * @type {Set.<ElemHideBase>} |   58  * @type {Set.<ElemHideFilter>} | 
|   59  */ |   59  */ | 
|   60 let knownFilters = new Set(); |   60 let knownFilters = new Set(); | 
|   61  |  | 
|   62 /** |  | 
|   63  * Lookup table, lists of element hiding exceptions by selector |  | 
|   64  * @type {Map.<string,Filter[]>} |  | 
|   65  */ |  | 
|   66 let exceptions = new Map(); |  | 
|   67  |   61  | 
|   68 /** |   62 /** | 
|   69  * Adds a filter to the lookup table of filters by domain. |   63  * Adds a filter to the lookup table of filters by domain. | 
|   70  * @param {Filter} filter |   64  * @param {Filter} filter | 
|   71  */ |   65  */ | 
|   72 function addToFiltersByDomain(filter) |   66 function addToFiltersByDomain(filter) | 
|   73 { |   67 { | 
|   74   let domains = filter.domains || defaultDomains; |   68   let domains = filter.domains || defaultDomains; | 
|   75   for (let [domain, isIncluded] of domains) |   69   for (let [domain, isIncluded] of domains) | 
|   76   { |   70   { | 
| (...skipping 13 matching lines...) Expand all  Loading... | 
|   90  * @returns {string[]} |   84  * @returns {string[]} | 
|   91  */ |   85  */ | 
|   92 function getUnconditionalSelectors() |   86 function getUnconditionalSelectors() | 
|   93 { |   87 { | 
|   94   if (!unconditionalSelectors) |   88   if (!unconditionalSelectors) | 
|   95     unconditionalSelectors = [...filterBySelector.keys()]; |   89     unconditionalSelectors = [...filterBySelector.keys()]; | 
|   96  |   90  | 
|   97   return unconditionalSelectors; |   91   return unconditionalSelectors; | 
|   98 } |   92 } | 
|   99  |   93  | 
 |   94 ElemHideExceptions.on("added", ({selector}) => | 
 |   95 { | 
 |   96   // If this is the first exception for a previously unconditionally applied | 
 |   97   // element hiding selector we need to take care to update the lookups. | 
 |   98   let unconditionalFilterForSelector = filterBySelector.get(selector); | 
 |   99   if (unconditionalFilterForSelector) | 
 |  100   { | 
 |  101     addToFiltersByDomain(unconditionalFilterForSelector); | 
 |  102     filterBySelector.delete(selector); | 
 |  103     unconditionalSelectors = null; | 
 |  104   } | 
 |  105 }); | 
 |  106  | 
|  100 /** |  107 /** | 
|  101  * Container for element hiding filters |  108  * Container for element hiding filters | 
|  102  * @class |  109  * @class | 
|  103  */ |  110  */ | 
|  104 exports.ElemHide = { |  111 exports.ElemHide = { | 
|  105   /** |  112   /** | 
|  106    * Removes all known filters |  113    * Removes all known filters | 
|  107    */ |  114    */ | 
|  108   clear() |  115   clear() | 
|  109   { |  116   { | 
|  110     for (let collection of [filtersByDomain, filterBySelector, |  117     for (let collection of [filtersByDomain, filterBySelector, knownFilters]) | 
|  111                             knownFilters, exceptions]) |  | 
|  112     { |  | 
|  113       collection.clear(); |  118       collection.clear(); | 
|  114     } |  119  | 
|  115     unconditionalSelectors = null; |  120     unconditionalSelectors = null; | 
|  116     FilterNotifier.emit("elemhideupdate"); |  121     FilterNotifier.emit("elemhideupdate"); | 
|  117   }, |  122   }, | 
|  118  |  123  | 
|  119   /** |  124   /** | 
|  120    * Add a new element hiding filter |  125    * Add a new element hiding filter | 
|  121    * @param {ElemHideBase} filter |  126    * @param {ElemHideFilter} filter | 
|  122    */ |  127    */ | 
|  123   add(filter) |  128   add(filter) | 
|  124   { |  129   { | 
|  125     if (knownFilters.has(filter)) |  130     if (knownFilters.has(filter)) | 
|  126       return; |  131       return; | 
|  127  |  132  | 
|  128     let {selector} = filter; |  133     let {selector} = filter; | 
|  129  |  134  | 
|  130     if (filter instanceof ElemHideException) |  135     if (!(filter.domains || ElemHideExceptions.hasExceptions(selector))) | 
|  131     { |  | 
|  132       let list = exceptions.get(selector); |  | 
|  133       if (list) |  | 
|  134         list.push(filter); |  | 
|  135       else |  | 
|  136         exceptions.set(selector, [filter]); |  | 
|  137  |  | 
|  138       // If this is the first exception for a previously unconditionally |  | 
|  139       // applied element hiding selector we need to take care to update the |  | 
|  140       // lookups. |  | 
|  141       let unconditionalFilterForSelector = filterBySelector.get(selector); |  | 
|  142       if (unconditionalFilterForSelector) |  | 
|  143       { |  | 
|  144         addToFiltersByDomain(unconditionalFilterForSelector); |  | 
|  145         filterBySelector.delete(selector); |  | 
|  146         unconditionalSelectors = null; |  | 
|  147       } |  | 
|  148     } |  | 
|  149     else if (!(filter.domains || exceptions.has(selector))) |  | 
|  150     { |  136     { | 
|  151       // The new filter's selector is unconditionally applied to all domains |  137       // The new filter's selector is unconditionally applied to all domains | 
|  152       filterBySelector.set(selector, filter); |  138       filterBySelector.set(selector, filter); | 
|  153       unconditionalSelectors = null; |  139       unconditionalSelectors = null; | 
|  154     } |  140     } | 
|  155     else |  141     else | 
|  156     { |  142     { | 
|  157       // The new filter's selector only applies to some domains |  143       // The new filter's selector only applies to some domains | 
|  158       addToFiltersByDomain(filter); |  144       addToFiltersByDomain(filter); | 
|  159     } |  145     } | 
|  160  |  146  | 
|  161     knownFilters.add(filter); |  147     knownFilters.add(filter); | 
|  162     FilterNotifier.emit("elemhideupdate"); |  148     FilterNotifier.emit("elemhideupdate"); | 
|  163   }, |  149   }, | 
|  164  |  150  | 
|  165   /** |  151   /** | 
|  166    * Removes an element hiding filter |  152    * Removes an element hiding filter | 
|  167    * @param {ElemHideBase} filter |  153    * @param {ElemHideFilter} filter | 
|  168    */ |  154    */ | 
|  169   remove(filter) |  155   remove(filter) | 
|  170   { |  156   { | 
|  171     if (!knownFilters.has(filter)) |  157     if (!knownFilters.has(filter)) | 
|  172       return; |  158       return; | 
|  173  |  159  | 
|  174     let {selector} = filter; |  160     let {selector} = filter; | 
|  175  |  161  | 
|  176     // Whitelisting filters |  | 
|  177     if (filter instanceof ElemHideException) |  | 
|  178     { |  | 
|  179       let list = exceptions.get(selector); |  | 
|  180       let index = list.indexOf(filter); |  | 
|  181       if (index >= 0) |  | 
|  182         list.splice(index, 1); |  | 
|  183     } |  | 
|  184     // Unconditially applied element hiding filters |  162     // Unconditially applied element hiding filters | 
|  185     else if (filterBySelector.get(selector) == filter) |  163     if (filterBySelector.get(selector) == filter) | 
|  186     { |  164     { | 
|  187       filterBySelector.delete(selector); |  165       filterBySelector.delete(selector); | 
|  188       unconditionalSelectors = null; |  166       unconditionalSelectors = null; | 
|  189     } |  167     } | 
|  190     // Conditionally applied element hiding filters |  168     // Conditionally applied element hiding filters | 
|  191     else |  169     else | 
|  192     { |  170     { | 
|  193       let domains = filter.domains || defaultDomains; |  171       let domains = filter.domains || defaultDomains; | 
|  194       for (let domain of domains.keys()) |  172       for (let domain of domains.keys()) | 
|  195       { |  173       { | 
|  196         let filters = filtersByDomain.get(domain); |  174         let filters = filtersByDomain.get(domain); | 
|  197         if (filters) |  175         if (filters) | 
|  198         { |  176         { | 
|  199           filters.delete(filter); |  177           filters.delete(filter); | 
|  200  |  178  | 
|  201           if (filters.size == 0) |  179           if (filters.size == 0) | 
|  202             filtersByDomain.delete(domain); |  180             filtersByDomain.delete(domain); | 
|  203         } |  181         } | 
|  204       } |  182       } | 
|  205     } |  183     } | 
|  206  |  184  | 
|  207     knownFilters.delete(filter); |  185     knownFilters.delete(filter); | 
|  208     FilterNotifier.emit("elemhideupdate"); |  186     FilterNotifier.emit("elemhideupdate"); | 
|  209   }, |  | 
|  210  |  | 
|  211   /** |  | 
|  212    * Checks whether an exception rule is registered for a selector on a |  | 
|  213    * particular domain. |  | 
|  214    * @param {string} selector |  | 
|  215    * @param {?string} docDomain |  | 
|  216    * @return {?ElemHideException} |  | 
|  217    */ |  | 
|  218   getException(selector, docDomain) |  | 
|  219   { |  | 
|  220     let list = exceptions.get(selector); |  | 
|  221     if (!list) |  | 
|  222       return null; |  | 
|  223  |  | 
|  224     for (let i = list.length - 1; i >= 0; i--) |  | 
|  225     { |  | 
|  226       if (list[i].isActiveOnDomain(docDomain)) |  | 
|  227         return list[i]; |  | 
|  228     } |  | 
|  229  |  | 
|  230     return null; |  | 
|  231   }, |  187   }, | 
|  232  |  188  | 
|  233   /** |  189   /** | 
|  234    * Determines from the current filter list which selectors should be applied |  190    * Determines from the current filter list which selectors should be applied | 
|  235    * on a particular host name. |  191    * on a particular host name. | 
|  236    * @param {string} domain |  192    * @param {string} domain | 
|  237    * @param {boolean} [specificOnly] true if generic filters should not apply. |  193    * @param {boolean} [specificOnly] true if generic filters should not apply. | 
|  238    * @returns {string[]} List of selectors. |  194    * @returns {string[]} List of selectors. | 
|  239    */ |  195    */ | 
|  240   getSelectorsForDomain(domain, specificOnly = false) |  196   getSelectorsForDomain(domain, specificOnly = false) | 
| (...skipping 16 matching lines...) Expand all  Loading... | 
|  257         for (let [filter, isIncluded] of filters) |  213         for (let [filter, isIncluded] of filters) | 
|  258         { |  214         { | 
|  259           if (!isIncluded) |  215           if (!isIncluded) | 
|  260           { |  216           { | 
|  261             excluded.add(filter); |  217             excluded.add(filter); | 
|  262           } |  218           } | 
|  263           else |  219           else | 
|  264           { |  220           { | 
|  265             let {selector} = filter; |  221             let {selector} = filter; | 
|  266             if ((excluded.size == 0 || !excluded.has(filter)) && |  222             if ((excluded.size == 0 || !excluded.has(filter)) && | 
|  267                 !this.getException(selector, domain)) |  223                 !ElemHideExceptions.getException(selector, domain)) | 
|  268             { |  224             { | 
|  269               selectors.push(selector); |  225               selectors.push(selector); | 
|  270             } |  226             } | 
|  271           } |  227           } | 
|  272         } |  228         } | 
|  273       } |  229       } | 
|  274  |  230  | 
|  275       if (currentDomain == "") |  231       if (currentDomain == "") | 
|  276         break; |  232         break; | 
|  277  |  233  | 
|  278       let nextDot = currentDomain.indexOf("."); |  234       let nextDot = currentDomain.indexOf("."); | 
|  279       currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); |  235       currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); | 
|  280     } |  236     } | 
|  281  |  237  | 
|  282     if (!specificOnly) |  238     if (!specificOnly) | 
|  283       selectors = getUnconditionalSelectors().concat(selectors); |  239       selectors = getUnconditionalSelectors().concat(selectors); | 
|  284  |  240  | 
|  285     return selectors; |  241     return selectors; | 
|  286   } |  242   } | 
|  287 }; |  243 }; | 
| LEFT | RIGHT |