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