| 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 | 
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 71  */ | 71  */ | 
| 72 let genericExceptions = new Map(); | 72 let genericExceptions = new Map(); | 
| 73 | 73 | 
| 74 /** | 74 /** | 
| 75  * List of selectors that apply on any unknown domain | 75  * List of selectors that apply on any unknown domain | 
| 76  * @type {?string[]} | 76  * @type {?string[]} | 
| 77  */ | 77  */ | 
| 78 let conditionalGenericSelectors = null; | 78 let conditionalGenericSelectors = null; | 
| 79 | 79 | 
| 80 /** | 80 /** | 
| 81  * Domains that are known not to be specifically excluded from any generic | 81  * Adds a filter to the lookup table of filters by domain. | 
| 82  * filters | 82  * @param {Filter} filter | 
| 83  * @type {Set.<string>} | 83  */ | 
| 84  */ | 84 function addToFiltersByDomain(filter) | 
| 85 let genericFriendlyDomains = new Set(); | 85 { | 
|  | 86   let domains = filter.domains || defaultDomains; | 
|  | 87   if (filter instanceof ElemHideException) | 
|  | 88   { | 
|  | 89     for (let domain of domains.keys()) | 
|  | 90     { | 
|  | 91       // Add an entry for each domain, but without any filters. This makes | 
|  | 92       // the domain "known" and helps us avoid certain optimizations that | 
|  | 93       // would otherwise yield incorrect results. | 
|  | 94       if (domain != "" && !filtersByDomain.has(domain)) | 
|  | 95         filtersByDomain.set(domain, null); | 
|  | 96     } | 
|  | 97   } | 
|  | 98   else | 
|  | 99   { | 
|  | 100     for (let [domain, isIncluded] of domains) | 
|  | 101     { | 
|  | 102       // There's no need to note that a filter is generically disabled. | 
|  | 103       if (!isIncluded && domain == "") | 
|  | 104         continue; | 
|  | 105 | 
|  | 106       let filters = filtersByDomain.get(domain); | 
|  | 107       if (!filters) | 
|  | 108         filtersByDomain.set(domain, filters = new Map()); | 
|  | 109       filters.set(filter, isIncluded); | 
|  | 110     } | 
|  | 111   } | 
|  | 112 } | 
| 86 | 113 | 
| 87 /** | 114 /** | 
| 88  * Returns a list of domain-specific filters matching a domain | 115  * Returns a list of domain-specific filters matching a domain | 
| 89  * @param {string} [domain] | 116  * @param {string} [domain] | 
| 90  * @returns {Array.<?Map.<Filter,boolean>>} | 117  * @returns {Array.<?Map.<Filter,boolean>>} | 
| 91  */ | 118  */ | 
| 92 function getSpecificFiltersForDomain(domain) | 119 function getSpecificFiltersForDomain(domain) | 
| 93 { | 120 { | 
| 94   let filtersList = []; | 121   let filtersList = []; | 
| 95 | 122 | 
| 96   if (domain) | 123   if (domain) | 
| 97     domain = domain.toUpperCase(); | 124     domain = domain.toUpperCase(); | 
| 98 | 125 | 
| 99   while (domain) | 126   while (domain) | 
| 100   { | 127   { | 
| 101     // Note that we also push null values into the list, because |  | 
| 102     // ElemHide.getSelectorsForDomain still needs to know if there are any |  | 
| 103     // entries for the domain. |  | 
| 104     let filters = filtersByDomain.get(domain); | 128     let filters = filtersByDomain.get(domain); | 
| 105     if (typeof filters != "undefined") | 129     if (typeof filters != "undefined") | 
| 106       filtersList.push(filters); | 130       filtersList.push(filters); | 
| 107 | 131 | 
| 108     let nextDot = domain.indexOf("."); | 132     let nextDot = domain.indexOf("."); | 
| 109     domain = nextDot == -1 ? null : domain.substring(nextDot + 1); | 133     domain = nextDot == -1 ? null : domain.substring(nextDot + 1); | 
| 110   } | 134   } | 
| 111 | 135 | 
| 112   return filtersList; | 136   return filtersList; | 
| 113 } | 137 } | 
| 114 | 138 | 
| 115 /** | 139 /** | 
| 116  * Returns a list of selectors from a given list of filters that apply on a | 140  * Returns a list of selectors that apply on a domain from a given list of | 
| 117  * domain | 141  * filters | 
| 118  * @param {string} [domain] | 142  * @param {string} [domain] | 
| 119  * @param {Array.<?Map.<Filter,boolean>>} filtersList | 143  * @param {Array.<?Map.<Filter,boolean>>} filtersList | 
| 120  * @param {boolean} specificOnly | 144  * @param {Set.<Filter>} excludeSet | 
| 121  * @returns {string[]} | 145  * @returns {string[]} | 
| 122  */ | 146  */ | 
| 123 function getConditionalSelectorsForDomain(domain, filtersList, specificOnly) | 147 function matchSelectors(domain, filtersList, excludeSet) | 
| 124 { | 148 { | 
| 125   let selectors = []; | 149   let matches = []; | 
| 126 |  | 
| 127   let genericFilters = !specificOnly ? filtersList.pop() : null; |  | 
| 128   let excluded = new Set(); |  | 
| 129 | 150 | 
| 130   // This code is a performance hot-spot, which is why we've made certain | 151   // This code is a performance hot-spot, which is why we've made certain | 
| 131   // micro-optimisations. Please be careful before making changes. | 152   // micro-optimisations. Please be careful before making changes. | 
| 132   for (let i = 0; i < filtersList.length; i++) | 153   for (let i = 0; i < filtersList.length; i++) | 
| 133   { | 154   { | 
| 134     if (!filtersList[i]) | 155     let filters = filtersList[i]; | 
| 135       continue; | 156     if (filters) | 
| 136 | 157     { | 
| 137     for (let [filter, isIncluded] of filtersList[i]) | 158       for (let [filter, isIncluded] of filters) | 
| 138     { |  | 
| 139       if (!isIncluded) |  | 
| 140       { | 159       { | 
| 141         excluded.add(filter); | 160         if (!isIncluded) | 
|  | 161         { | 
|  | 162           excludeSet.add(filter); | 
|  | 163         } | 
|  | 164         else if ((excludeSet.size == 0 || !excludeSet.has(filter)) && | 
|  | 165                   !exports.ElemHide.getException(filter, domain)) | 
|  | 166         { | 
|  | 167           matches.push(filter.selector); | 
|  | 168         } | 
| 142       } | 169       } | 
| 143       else if ((excluded.size == 0 || !excluded.has(filter)) && | 170     } | 
| 144                !ElemHide.getException(filter, domain)) | 171   } | 
| 145       { | 172 | 
| 146         selectors.push(filter.selector); | 173   return matches; | 
| 147       } |  | 
| 148     } |  | 
| 149   } |  | 
| 150 |  | 
| 151   if (!genericFilters) |  | 
| 152     return selectors; |  | 
| 153 |  | 
| 154   if (genericFriendlyDomains.has(domain)) |  | 
| 155     return selectors.concat(getConditionalGenericSelectors()); |  | 
| 156 |  | 
| 157   let genericSelectors = []; |  | 
| 158 |  | 
| 159   for (let filter of genericFilters.keys()) |  | 
| 160   { |  | 
| 161     if ((excluded.size == 0 || !excluded.has(filter)) && |  | 
| 162         !ElemHide.getException(filter, domain)) |  | 
| 163     { |  | 
| 164       genericSelectors.push(filter.selector); |  | 
| 165     } |  | 
| 166   } |  | 
| 167 |  | 
| 168   // If the number of conditional generic selectors that apply on this domain |  | 
| 169   // is the same as the total number of conditional generic selectors, the |  | 
| 170   // domain is "generic friendly". In that case, we mark it is as such for |  | 
| 171   // faster lookups. |  | 
| 172   if (conditionalGenericSelectors && |  | 
| 173       genericSelectors.length == conditionalGenericSelectors.length) |  | 
| 174   { |  | 
| 175     if (genericFriendlyDomains.size >= 1000) |  | 
| 176       genericFriendlyDomains.clear(); |  | 
| 177 |  | 
| 178     genericFriendlyDomains.add(domain); |  | 
| 179   } |  | 
| 180 |  | 
| 181   return selectors.concat(genericSelectors); |  | 
| 182 } | 174 } | 
| 183 | 175 | 
| 184 /** | 176 /** | 
| 185  * Returns a list of selectors that apply on any unknown domain | 177  * Returns a list of selectors that apply on any unknown domain | 
| 186  * @returns {string[]} | 178  * @returns {string[]} | 
| 187  */ | 179  */ | 
| 188 function getConditionalGenericSelectors() | 180 function getConditionalGenericSelectors() | 
| 189 { | 181 { | 
| 190   if (conditionalGenericSelectors) | 182   if (conditionalGenericSelectors) | 
| 191     return conditionalGenericSelectors; | 183     return conditionalGenericSelectors; | 
| (...skipping 22 matching lines...) Expand all  Loading... | 
| 214   if (!unconditionalSelectors) | 206   if (!unconditionalSelectors) | 
| 215     unconditionalSelectors = [...filterBySelector.keys()]; | 207     unconditionalSelectors = [...filterBySelector.keys()]; | 
| 216 | 208 | 
| 217   return unconditionalSelectors; | 209   return unconditionalSelectors; | 
| 218 } | 210 } | 
| 219 | 211 | 
| 220 /** | 212 /** | 
| 221  * Container for element hiding filters | 213  * Container for element hiding filters | 
| 222  * @class | 214  * @class | 
| 223  */ | 215  */ | 
| 224 let ElemHide = exports.ElemHide = { | 216 exports.ElemHide = { | 
| 225   /** | 217   /** | 
| 226    * Removes all known filters | 218    * Removes all known filters | 
| 227    */ | 219    */ | 
| 228   clear() | 220   clear() | 
| 229   { | 221   { | 
| 230     for (let collection of [filtersByDomain, filterBySelector, | 222     for (let collection of [filtersByDomain, filterBySelector, | 
| 231                             knownFilters, exceptions, | 223                             knownFilters, exceptions, | 
| 232                             genericExceptions, genericFriendlyDomains]) | 224                             genericExceptions]) | 
| 233     { | 225     { | 
| 234       collection.clear(); | 226       collection.clear(); | 
| 235     } | 227     } | 
| 236     unconditionalSelectors = null; | 228     unconditionalSelectors = null; | 
| 237     conditionalGenericSelectors = null; | 229     conditionalGenericSelectors = null; | 
| 238     FilterNotifier.emit("elemhideupdate"); | 230     FilterNotifier.emit("elemhideupdate"); | 
| 239   }, |  | 
| 240 |  | 
| 241   _addToFiltersByDomain(filter) |  | 
| 242   { |  | 
| 243     let domains = filter.domains || defaultDomains; |  | 
| 244     if (filter instanceof ElemHideException) |  | 
| 245     { |  | 
| 246       for (let domain of domains.keys()) |  | 
| 247       { |  | 
| 248         // Add an entry for each domain, but without any filters. This makes |  | 
| 249         // the domain "known" and helps us avoid certain optimizations that |  | 
| 250         // would otherwise yield incorrect results. |  | 
| 251         if (domain != "" && !filtersByDomain.has(domain)) |  | 
| 252           filtersByDomain.set(domain, null); |  | 
| 253       } |  | 
| 254     } |  | 
| 255     else |  | 
| 256     { |  | 
| 257       for (let [domain, isIncluded] of domains) |  | 
| 258       { |  | 
| 259         // There's no need to note that a filter is generically disabled. |  | 
| 260         if (!isIncluded && domain == "") |  | 
| 261           continue; |  | 
| 262 |  | 
| 263         let filters = filtersByDomain.get(domain); |  | 
| 264         if (!filters) |  | 
| 265           filtersByDomain.set(domain, filters = new Map()); |  | 
| 266         filters.set(filter, isIncluded); |  | 
| 267       } |  | 
| 268     } |  | 
| 269   }, | 231   }, | 
| 270 | 232 | 
| 271   /** | 233   /** | 
| 272    * Add a new element hiding filter | 234    * Add a new element hiding filter | 
| 273    * @param {ElemHideBase} filter | 235    * @param {ElemHideBase} filter | 
| 274    */ | 236    */ | 
| 275   add(filter) | 237   add(filter) | 
| 276   { | 238   { | 
| 277     if (knownFilters.has(filter)) | 239     if (knownFilters.has(filter)) | 
| 278       return; | 240       return; | 
| 279 | 241 | 
| 280     conditionalGenericSelectors = null; | 242     conditionalGenericSelectors = null; | 
| 281     genericFriendlyDomains.clear(); |  | 
| 282 | 243 | 
| 283     if (filter instanceof ElemHideException) | 244     if (filter instanceof ElemHideException) | 
| 284     { | 245     { | 
| 285       let {selector, domains} = filter; | 246       let {selector, domains} = filter; | 
| 286 | 247 | 
| 287       let list = exceptions.get(selector); | 248       let list = exceptions.get(selector); | 
| 288       if (list) | 249       if (list) | 
| 289         list.push(filter); | 250         list.push(filter); | 
| 290       else | 251       else | 
| 291         exceptions.set(selector, [filter]); | 252         exceptions.set(selector, [filter]); | 
| 292 | 253 | 
| 293       if (domains) | 254       if (domains) | 
| 294         this._addToFiltersByDomain(filter); | 255         addToFiltersByDomain(filter); | 
| 295 | 256 | 
| 296       if (filter.isGeneric()) | 257       if (filter.isGeneric()) | 
| 297       { | 258       { | 
| 298         list = genericExceptions.get(selector); | 259         list = genericExceptions.get(selector); | 
| 299         if (list) | 260         if (list) | 
| 300           list.push(filter); | 261           list.push(filter); | 
| 301         else | 262         else | 
| 302           genericExceptions.set(selector, [filter]); | 263           genericExceptions.set(selector, [filter]); | 
| 303       } | 264       } | 
| 304 | 265 | 
| 305       // If this is the first exception for a previously unconditionally | 266       // If this is the first exception for a previously unconditionally | 
| 306       // applied element hiding selector we need to take care to update the | 267       // applied element hiding selector we need to take care to update the | 
| 307       // lookups. | 268       // lookups. | 
| 308       let unconditionalFilterForSelector = filterBySelector.get(selector); | 269       let unconditionalFilterForSelector = filterBySelector.get(selector); | 
| 309       if (unconditionalFilterForSelector) | 270       if (unconditionalFilterForSelector) | 
| 310       { | 271       { | 
| 311         this._addToFiltersByDomain(unconditionalFilterForSelector); | 272         addToFiltersByDomain(unconditionalFilterForSelector); | 
| 312         filterBySelector.delete(selector); | 273         filterBySelector.delete(selector); | 
| 313         unconditionalSelectors = null; | 274         unconditionalSelectors = null; | 
| 314       } | 275       } | 
| 315     } | 276     } | 
| 316     else if (!(filter.domains || exceptions.has(filter.selector))) | 277     else if (!(filter.domains || exceptions.has(filter.selector))) | 
| 317     { | 278     { | 
| 318       // The new filter's selector is unconditionally applied to all domains | 279       // The new filter's selector is unconditionally applied to all domains | 
| 319       filterBySelector.set(filter.selector, filter); | 280       filterBySelector.set(filter.selector, filter); | 
| 320       unconditionalSelectors = null; | 281       unconditionalSelectors = null; | 
| 321     } | 282     } | 
| 322     else | 283     else | 
| 323     { | 284     { | 
| 324       // The new filter's selector only applies to some domains | 285       // The new filter's selector only applies to some domains | 
| 325       this._addToFiltersByDomain(filter); | 286       addToFiltersByDomain(filter); | 
| 326     } | 287     } | 
| 327 | 288 | 
| 328     knownFilters.add(filter); | 289     knownFilters.add(filter); | 
| 329     FilterNotifier.emit("elemhideupdate"); | 290     FilterNotifier.emit("elemhideupdate"); | 
| 330   }, | 291   }, | 
| 331 | 292 | 
| 332   /** | 293   /** | 
| 333    * Removes an element hiding filter | 294    * Removes an element hiding filter | 
| 334    * @param {ElemHideBase} filter | 295    * @param {ElemHideBase} filter | 
| 335    */ | 296    */ | 
| 336   remove(filter) | 297   remove(filter) | 
| 337   { | 298   { | 
| 338     if (!knownFilters.has(filter)) | 299     if (!knownFilters.has(filter)) | 
| 339       return; | 300       return; | 
| 340 | 301 | 
| 341     conditionalGenericSelectors = null; | 302     conditionalGenericSelectors = null; | 
| 342     genericFriendlyDomains.clear(); |  | 
| 343 | 303 | 
| 344     // Whitelisting filters | 304     // Whitelisting filters | 
| 345     if (filter instanceof ElemHideException) | 305     if (filter instanceof ElemHideException) | 
| 346     { | 306     { | 
| 347       let list = exceptions.get(filter.selector); | 307       let list = exceptions.get(filter.selector); | 
| 348       let index = list.indexOf(filter); | 308       let index = list.indexOf(filter); | 
| 349       if (index >= 0) | 309       if (index >= 0) | 
| 350         list.splice(index, 1); | 310         list.splice(index, 1); | 
| 351 | 311 | 
| 352       if (filter.isGeneric()) | 312       if (filter.isGeneric()) | 
| 353       { | 313       { | 
| 354         list = genericExceptions.get(filter.selector); | 314         list = genericExceptions.get(filter.selector); | 
| 355         index = list.indexOf(filter); | 315         index = list.indexOf(filter); | 
| 356         if (index >= 0) | 316         if (index >= 0) | 
| 357           list.splice(index, 1); | 317           list.splice(index, 1); | 
| 358 | 318 | 
|  | 319         // It's important to delete the entry here so the selector no longer | 
|  | 320         // appears to have any generic exceptions. | 
| 359         if (list.length == 0) | 321         if (list.length == 0) | 
| 360           genericExceptions.delete(filter.selector); | 322           genericExceptions.delete(filter.selector); | 
| 361       } | 323       } | 
| 362     } | 324     } | 
| 363     // Unconditially applied element hiding filters | 325     // Unconditially applied element hiding filters | 
| 364     else if (filterBySelector.get(filter.selector) == filter) | 326     else if (filterBySelector.get(filter.selector) == filter) | 
| 365     { | 327     { | 
| 366       filterBySelector.delete(filter.selector); | 328       filterBySelector.delete(filter.selector); | 
| 367       unconditionalSelectors = null; | 329       unconditionalSelectors = null; | 
| 368     } | 330     } | 
| (...skipping 29 matching lines...) Expand all  Loading... | 
| 398     for (let i = list.length - 1; i >= 0; i--) | 360     for (let i = list.length - 1; i >= 0; i--) | 
| 399     { | 361     { | 
| 400       if (list[i].isActiveOnDomain(docDomain)) | 362       if (list[i].isActiveOnDomain(docDomain)) | 
| 401         return list[i]; | 363         return list[i]; | 
| 402     } | 364     } | 
| 403 | 365 | 
| 404     return null; | 366     return null; | 
| 405   }, | 367   }, | 
| 406 | 368 | 
| 407   /** | 369   /** | 
| 408    * Constant used by getSelectorsForDomain to return all selectors applying to |  | 
| 409    * a particular hostname. |  | 
| 410    * @type {number} |  | 
| 411    * @const |  | 
| 412    */ |  | 
| 413   ALL_MATCHING: 0, |  | 
| 414 |  | 
| 415   /** |  | 
| 416    * Constant used by getSelectorsForDomain to exclude selectors which apply to |  | 
| 417    * all websites without exception. |  | 
| 418    * @type {number} |  | 
| 419    * @const |  | 
| 420    */ |  | 
| 421   NO_UNCONDITIONAL: 1, |  | 
| 422 |  | 
| 423   /** |  | 
| 424    * Constant used by getSelectorsForDomain to return only selectors for filters |  | 
| 425    * which specifically match the given host name. |  | 
| 426    * @type {number} |  | 
| 427    * @const |  | 
| 428    */ |  | 
| 429   SPECIFIC_ONLY: 2, |  | 
| 430 |  | 
| 431   /** |  | 
| 432    * Determines from the current filter list which selectors should be applied | 370    * Determines from the current filter list which selectors should be applied | 
| 433    * on a particular host name. | 371    * on a particular host name. | 
| 434    * @param {string} domain | 372    * @param {string} domain | 
| 435    * @param {number} [criteria] | 373    * @param {boolean} [specificOnly] true if generic filters should not apply. | 
| 436    *   One of the following: ElemHide.ALL_MATCHING, ElemHide.NO_UNCONDITIONAL or | 374    * @returns {string[]} List of selectors. | 
| 437    *                         ElemHide.SPECIFIC_ONLY. |  | 
| 438    * @returns {string[]} |  | 
| 439    *   List of selectors. |  | 
| 440    */ | 375    */ | 
| 441   getSelectorsForDomain(domain, criteria = ElemHide.ALL_MATCHING) | 376   getSelectorsForDomain(domain, specificOnly = false) | 
| 442   { | 377   { | 
| 443     let selectors = []; | 378     let specificFilters = getSpecificFiltersForDomain(domain); | 
| 444 | 379 | 
| 445     let specificOnly = criteria >= ElemHide.SPECIFIC_ONLY; | 380     // If there are no specific filters (nor any specific exceptions), we can | 
| 446     let filtersList = getSpecificFiltersForDomain(domain); | 381     // just return the selectors from all the generic filters modulo any | 
| 447 | 382     // generic exceptions. | 
| 448     if (filtersList.length > 0) | 383     if (specificFilters.length == 0) | 
| 449     { | 384     { | 
| 450       if (!specificOnly) | 385       return specificOnly ? [] : | 
| 451         filtersList.push(filtersByDomain.get("")); | 386                getUnconditionalSelectors() | 
| 452 | 387                .concat(getConditionalGenericSelectors()); | 
| 453       selectors = getConditionalSelectorsForDomain(domain, filtersList, | 388     } | 
| 454                                                    specificOnly); | 389 | 
| 455     } | 390     let excluded = new Set(); | 
| 456     else if (!specificOnly) | 391     let selectors = matchSelectors(domain, specificFilters, excluded); | 
| 457     { | 392 | 
| 458       selectors = getConditionalGenericSelectors(); | 393     if (specificOnly) | 
| 459     } | 394       return selectors; | 
| 460 | 395 | 
| 461     if (criteria < ElemHide.NO_UNCONDITIONAL) | 396     return getUnconditionalSelectors() | 
| 462       selectors = getUnconditionalSelectors().concat(selectors); | 397            .concat(selectors, | 
| 463 | 398                    matchSelectors(domain, [filtersByDomain.get("")], | 
| 464     // If the above logic leaves us with a reference to our internal cache of | 399                                   excluded)); | 
| 465     // selectors, we make a copy here. |  | 
| 466     if (selectors == conditionalGenericSelectors) |  | 
| 467       selectors = selectors.slice(); |  | 
| 468 |  | 
| 469     return selectors; |  | 
| 470   } | 400   } | 
| 471 }; | 401 }; | 
| LEFT | RIGHT | 
|---|