| 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 "use strict"; |   18 "use strict"; | 
|   19  |   19  | 
|   20 /** |   20 const {ElemHide} = require("compiled"); | 
|   21  * @fileOverview Element hiding implementation. |  | 
|   22  */ |  | 
|   23  |   21  | 
|   24 const {ElemHideException} = require("filterClasses"); |   22 // Keep in sync with ElemHide.h | 
|   25 const {FilterNotifier} = require("filterNotifier"); |   23 ElemHide.ALL_MATCHING = 0; | 
 |   24 ElemHide.NO_UNCONDITIONAL = 1; | 
 |   25 ElemHide.SPECIFIC_ONLY = 2; | 
|   26  |   26  | 
|   27 /** |   27 exports.ElemHide = ElemHide; | 
|   28  * Lookup table, filters by their associated key |  | 
|   29  * @type {Object} |  | 
|   30  */ |  | 
|   31 let filterByKey = []; |  | 
|   32  |  | 
|   33 /** |  | 
|   34  * Lookup table, keys of the filters by filter text |  | 
|   35  * @type {Object} |  | 
|   36  */ |  | 
|   37 let keyByFilter = Object.create(null); |  | 
|   38  |  | 
|   39 /** |  | 
|   40  * Nested lookup table, filter (or false if inactive) by filter key by domain. |  | 
|   41  * (Only contains filters that aren't unconditionally matched for all domains.) |  | 
|   42  * @type {Object} |  | 
|   43  */ |  | 
|   44 let filtersByDomain = Object.create(null); |  | 
|   45  |  | 
|   46 /** |  | 
|   47  * Lookup table, filter key by selector. (Only used for selectors that are |  | 
|   48  * unconditionally matched for all domains.) |  | 
|   49  */ |  | 
|   50 let filterKeyBySelector = Object.create(null); |  | 
|   51  |  | 
|   52 /** |  | 
|   53  * This array caches the keys of filterKeyBySelector table (selectors which |  | 
|   54  * unconditionally apply on all domains). It will be null if the cache needs to |  | 
|   55  * be rebuilt. |  | 
|   56  */ |  | 
|   57 let unconditionalSelectors = null; |  | 
|   58  |  | 
|   59 /** |  | 
|   60  * This array caches the values of filterKeyBySelector table (filterIds for |  | 
|   61  * selectors which unconditionally apply on all domains). It will be null if the |  | 
|   62  * cache needs to be rebuilt. |  | 
|   63  */ |  | 
|   64 let unconditionalFilterKeys = null; |  | 
|   65  |  | 
|   66 /** |  | 
|   67  * Object to be used instead when a filter has a blank domains property. |  | 
|   68  */ |  | 
|   69 let defaultDomains = Object.create(null); |  | 
|   70 defaultDomains[""] = true; |  | 
|   71  |  | 
|   72 /** |  | 
|   73  * Lookup table, keys are known element hiding exceptions |  | 
|   74  * @type {Object} |  | 
|   75  */ |  | 
|   76 let knownExceptions = Object.create(null); |  | 
|   77  |  | 
|   78 /** |  | 
|   79  * Lookup table, lists of element hiding exceptions by selector |  | 
|   80  * @type {Object} |  | 
|   81  */ |  | 
|   82 let exceptions = Object.create(null); |  | 
|   83  |  | 
|   84 /** |  | 
|   85  * Container for element hiding filters |  | 
|   86  * @class |  | 
|   87  */ |  | 
|   88 let ElemHide = exports.ElemHide = { |  | 
|   89   /** |  | 
|   90    * Removes all known filters |  | 
|   91    */ |  | 
|   92   clear() |  | 
|   93   { |  | 
|   94     filterByKey = []; |  | 
|   95     keyByFilter = Object.create(null); |  | 
|   96     filtersByDomain = Object.create(null); |  | 
|   97     filterKeyBySelector = Object.create(null); |  | 
|   98     unconditionalSelectors = unconditionalFilterKeys = null; |  | 
|   99     knownExceptions = Object.create(null); |  | 
|  100     exceptions = Object.create(null); |  | 
|  101     FilterNotifier.emit("elemhideupdate"); |  | 
|  102   }, |  | 
|  103  |  | 
|  104   _addToFiltersByDomain(key, filter) |  | 
|  105   { |  | 
|  106     let domains = filter.domains || defaultDomains; |  | 
|  107     for (let domain in domains) |  | 
|  108     { |  | 
|  109       let filters = filtersByDomain[domain]; |  | 
|  110       if (!filters) |  | 
|  111         filters = filtersByDomain[domain] = Object.create(null); |  | 
|  112  |  | 
|  113       if (domains[domain]) |  | 
|  114         filters[key] = filter; |  | 
|  115       else |  | 
|  116         filters[key] = false; |  | 
|  117     } |  | 
|  118   }, |  | 
|  119  |  | 
|  120   /** |  | 
|  121    * Add a new element hiding filter |  | 
|  122    * @param {ElemHideFilter} filter |  | 
|  123    */ |  | 
|  124   add(filter) |  | 
|  125   { |  | 
|  126     if (filter instanceof ElemHideException) |  | 
|  127     { |  | 
|  128       if (filter.text in knownExceptions) |  | 
|  129         return; |  | 
|  130  |  | 
|  131       let {selector} = filter; |  | 
|  132       if (!(selector in exceptions)) |  | 
|  133         exceptions[selector] = []; |  | 
|  134       exceptions[selector].push(filter); |  | 
|  135  |  | 
|  136       // If this is the first exception for a previously unconditionally |  | 
|  137       // applied element hiding selector we need to take care to update the |  | 
|  138       // lookups. |  | 
|  139       let filterKey = filterKeyBySelector[selector]; |  | 
|  140       if (typeof filterKey != "undefined") |  | 
|  141       { |  | 
|  142         this._addToFiltersByDomain(filterKey, filterByKey[filterKey]); |  | 
|  143         delete filterKeyBySelector[selector]; |  | 
|  144         unconditionalSelectors = unconditionalFilterKeys = null; |  | 
|  145       } |  | 
|  146  |  | 
|  147       knownExceptions[filter.text] = true; |  | 
|  148     } |  | 
|  149     else |  | 
|  150     { |  | 
|  151       if (filter.text in keyByFilter) |  | 
|  152         return; |  | 
|  153  |  | 
|  154       let key = filterByKey.push(filter) - 1; |  | 
|  155       keyByFilter[filter.text] = key; |  | 
|  156  |  | 
|  157       if (!(filter.domains || filter.selector in exceptions)) |  | 
|  158       { |  | 
|  159         // The new filter's selector is unconditionally applied to all domains |  | 
|  160         filterKeyBySelector[filter.selector] = key; |  | 
|  161         unconditionalSelectors = unconditionalFilterKeys = null; |  | 
|  162       } |  | 
|  163       else |  | 
|  164       { |  | 
|  165         // The new filter's selector only applies to some domains |  | 
|  166         this._addToFiltersByDomain(key, filter); |  | 
|  167       } |  | 
|  168     } |  | 
|  169  |  | 
|  170     FilterNotifier.emit("elemhideupdate"); |  | 
|  171   }, |  | 
|  172  |  | 
|  173   _removeFilterKey(key, filter) |  | 
|  174   { |  | 
|  175     if (filterKeyBySelector[filter.selector] == key) |  | 
|  176     { |  | 
|  177       delete filterKeyBySelector[filter.selector]; |  | 
|  178       unconditionalSelectors = unconditionalFilterKeys = null; |  | 
|  179       return; |  | 
|  180     } |  | 
|  181  |  | 
|  182     // We haven't found this filter in unconditional filters, look in |  | 
|  183     // filtersByDomain. |  | 
|  184     let domains = filter.domains || defaultDomains; |  | 
|  185     for (let domain in domains) |  | 
|  186     { |  | 
|  187       let filters = filtersByDomain[domain]; |  | 
|  188       if (filters) |  | 
|  189         delete filters[key]; |  | 
|  190     } |  | 
|  191   }, |  | 
|  192  |  | 
|  193   /** |  | 
|  194    * Removes an element hiding filter |  | 
|  195    * @param {ElemHideFilter} filter |  | 
|  196    */ |  | 
|  197   remove(filter) |  | 
|  198   { |  | 
|  199     if (filter instanceof ElemHideException) |  | 
|  200     { |  | 
|  201       if (!(filter.text in knownExceptions)) |  | 
|  202         return; |  | 
|  203  |  | 
|  204       let list = exceptions[filter.selector]; |  | 
|  205       let index = list.indexOf(filter); |  | 
|  206       if (index >= 0) |  | 
|  207         list.splice(index, 1); |  | 
|  208       delete knownExceptions[filter.text]; |  | 
|  209     } |  | 
|  210     else |  | 
|  211     { |  | 
|  212       if (!(filter.text in keyByFilter)) |  | 
|  213         return; |  | 
|  214  |  | 
|  215       let key = keyByFilter[filter.text]; |  | 
|  216       delete filterByKey[key]; |  | 
|  217       delete keyByFilter[filter.text]; |  | 
|  218       this._removeFilterKey(key, filter); |  | 
|  219     } |  | 
|  220  |  | 
|  221     FilterNotifier.emit("elemhideupdate"); |  | 
|  222   }, |  | 
|  223  |  | 
|  224   /** |  | 
|  225    * Checks whether an exception rule is registered for a filter on a particular |  | 
|  226    * domain. |  | 
|  227    * @param {Filter} filter |  | 
|  228    * @param {string} docDomain |  | 
|  229    * @return {ElemHideException} |  | 
|  230    */ |  | 
|  231   getException(filter, docDomain) |  | 
|  232   { |  | 
|  233     if (!(filter.selector in exceptions)) |  | 
|  234       return null; |  | 
|  235  |  | 
|  236     let list = exceptions[filter.selector]; |  | 
|  237     for (let i = list.length - 1; i >= 0; i--) |  | 
|  238     { |  | 
|  239       if (list[i].isActiveOnDomain(docDomain)) |  | 
|  240         return list[i]; |  | 
|  241     } |  | 
|  242  |  | 
|  243     return null; |  | 
|  244   }, |  | 
|  245  |  | 
|  246   /** |  | 
|  247    * Retrieves an element hiding filter by the corresponding protocol key |  | 
|  248    * @param {number} key |  | 
|  249    * @return {Filter} |  | 
|  250    */ |  | 
|  251   getFilterByKey(key) |  | 
|  252   { |  | 
|  253     return (key in filterByKey ? filterByKey[key] : null); |  | 
|  254   }, |  | 
|  255  |  | 
|  256   /** |  | 
|  257    * Returns a list of all selectors as a nested map. On first level, the keys |  | 
|  258    * are all values of `ElemHideBase.selectorDomain` (domains on which these |  | 
|  259    * selectors should apply, ignoring exceptions). The values are maps again, |  | 
|  260    * with the keys being selectors and values the corresponding filter keys. |  | 
|  261    * @returns {Map.<String,Map<String,String>>} |  | 
|  262    */ |  | 
|  263   getSelectors() |  | 
|  264   { |  | 
|  265     let domains = new Map(); |  | 
|  266     for (let key in filterByKey) |  | 
|  267     { |  | 
|  268       let filter = filterByKey[key]; |  | 
|  269       if (!filter.selector) |  | 
|  270         continue; |  | 
|  271  |  | 
|  272       let domain = filter.selectorDomain || ""; |  | 
|  273  |  | 
|  274       if (!domains.has(domain)) |  | 
|  275         domains.set(domain, new Map()); |  | 
|  276       domains.get(domain).set(filter.selector, key); |  | 
|  277     } |  | 
|  278  |  | 
|  279     return domains; |  | 
|  280   }, |  | 
|  281  |  | 
|  282   /** |  | 
|  283    * Returns a list of selectors that apply on each website unconditionally. |  | 
|  284    * @returns {string[]} |  | 
|  285    */ |  | 
|  286   getUnconditionalSelectors() |  | 
|  287   { |  | 
|  288     if (!unconditionalSelectors) |  | 
|  289       unconditionalSelectors = Object.keys(filterKeyBySelector); |  | 
|  290     return unconditionalSelectors.slice(); |  | 
|  291   }, |  | 
|  292  |  | 
|  293   /** |  | 
|  294    * Returns a list of filter keys for selectors which apply to all websites |  | 
|  295    * without exception. |  | 
|  296    * @returns {number[]} |  | 
|  297    */ |  | 
|  298   getUnconditionalFilterKeys() |  | 
|  299   { |  | 
|  300     if (!unconditionalFilterKeys) |  | 
|  301     { |  | 
|  302       let selectors = this.getUnconditionalSelectors(); |  | 
|  303       unconditionalFilterKeys = []; |  | 
|  304       for (let selector of selectors) |  | 
|  305         unconditionalFilterKeys.push(filterKeyBySelector[selector]); |  | 
|  306     } |  | 
|  307     return unconditionalFilterKeys.slice(); |  | 
|  308   }, |  | 
|  309  |  | 
|  310  |  | 
|  311   /** |  | 
|  312    * Constant used by getSelectorsForDomain to return all selectors applying to |  | 
|  313    * a particular hostname. |  | 
|  314    */ |  | 
|  315   ALL_MATCHING: 0, |  | 
|  316  |  | 
|  317   /** |  | 
|  318    * Constant used by getSelectorsForDomain to exclude selectors which apply to |  | 
|  319    * all websites without exception. |  | 
|  320    */ |  | 
|  321   NO_UNCONDITIONAL: 1, |  | 
|  322  |  | 
|  323   /** |  | 
|  324    * Constant used by getSelectorsForDomain to return only selectors for filters |  | 
|  325    * which specifically match the given host name. |  | 
|  326    */ |  | 
|  327   SPECIFIC_ONLY: 2, |  | 
|  328  |  | 
|  329   /** |  | 
|  330    * Determines from the current filter list which selectors should be applied |  | 
|  331    * on a particular host name. Optionally returns the corresponding filter |  | 
|  332    * keys. |  | 
|  333    * @param {string} domain |  | 
|  334    * @param {number} [criteria] |  | 
|  335    *   One of the following: ElemHide.ALL_MATCHING, ElemHide.NO_UNCONDITIONAL or |  | 
|  336    *                         ElemHide.SPECIFIC_ONLY. |  | 
|  337    * @param {boolean} [provideFilterKeys] |  | 
|  338    *   If true, the function will return a list of corresponding filter keys in |  | 
|  339    *   addition to selectors. |  | 
|  340    * @returns {string[]|Array.<string[]>} |  | 
|  341    *   List of selectors or an array with two elements (list of selectors and |  | 
|  342    *   list of corresponding keys) if provideFilterKeys is true. |  | 
|  343    */ |  | 
|  344   getSelectorsForDomain(domain, criteria, provideFilterKeys) |  | 
|  345   { |  | 
|  346     let filterKeys = []; |  | 
|  347     let selectors = []; |  | 
|  348  |  | 
|  349     if (typeof criteria == "undefined") |  | 
|  350       criteria = ElemHide.ALL_MATCHING; |  | 
|  351     if (criteria < ElemHide.NO_UNCONDITIONAL) |  | 
|  352     { |  | 
|  353       selectors = this.getUnconditionalSelectors(); |  | 
|  354       if (provideFilterKeys) |  | 
|  355         filterKeys = this.getUnconditionalFilterKeys(); |  | 
|  356     } |  | 
|  357  |  | 
|  358     let specificOnly = (criteria >= ElemHide.SPECIFIC_ONLY); |  | 
|  359     let seenFilters = Object.create(null); |  | 
|  360     let currentDomain = domain ? domain.toUpperCase() : ""; |  | 
|  361     while (true) |  | 
|  362     { |  | 
|  363       if (specificOnly && currentDomain == "") |  | 
|  364         break; |  | 
|  365  |  | 
|  366       let filters = filtersByDomain[currentDomain]; |  | 
|  367       if (filters) |  | 
|  368       { |  | 
|  369         for (let filterKey in filters) |  | 
|  370         { |  | 
|  371           if (filterKey in seenFilters) |  | 
|  372             continue; |  | 
|  373           seenFilters[filterKey] = true; |  | 
|  374  |  | 
|  375           let filter = filters[filterKey]; |  | 
|  376           if (filter && !this.getException(filter, domain)) |  | 
|  377           { |  | 
|  378             selectors.push(filter.selector); |  | 
|  379             // It is faster to always push the key, even if not required. |  | 
|  380             filterKeys.push(filterKey); |  | 
|  381           } |  | 
|  382         } |  | 
|  383       } |  | 
|  384  |  | 
|  385       if (currentDomain == "") |  | 
|  386         break; |  | 
|  387  |  | 
|  388       let nextDot = currentDomain.indexOf("."); |  | 
|  389       currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); |  | 
|  390     } |  | 
|  391  |  | 
|  392     if (provideFilterKeys) |  | 
|  393       return [selectors, filterKeys]; |  | 
|  394     return selectors; |  | 
|  395   } |  | 
|  396 }; |  | 
| OLD | NEW |