| 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 |  | 
| 82  * filters |  | 
| 83  * @type {Set.<string>} |  | 
| 84  */ |  | 
| 85 let genericFriendlyDomains = new Set(); |  | 
| 86 |  | 
| 87 /** |  | 
| 88  * Adds a filter to the lookup table of filters by domain. | 81  * Adds a filter to the lookup table of filters by domain. | 
| 89  * @param {Filter} filter | 82  * @param {Filter} filter | 
| 90  */ | 83  */ | 
| 91 function addToFiltersByDomain(filter) | 84 function addToFiltersByDomain(filter) | 
| 92 { | 85 { | 
| 93   let domains = filter.domains || defaultDomains; | 86   let domains = filter.domains || defaultDomains; | 
| 94   if (filter instanceof ElemHideException) | 87   if (filter instanceof ElemHideException) | 
| 95   { | 88   { | 
| 96     for (let domain of domains.keys()) | 89     for (let domain of domains.keys()) | 
| 97     { | 90     { | 
| (...skipping 14 matching lines...) Expand all  Loading... | 
| 112 | 105 | 
| 113       let filters = filtersByDomain.get(domain); | 106       let filters = filtersByDomain.get(domain); | 
| 114       if (!filters) | 107       if (!filters) | 
| 115         filtersByDomain.set(domain, filters = new Map()); | 108         filtersByDomain.set(domain, filters = new Map()); | 
| 116       filters.set(filter, isIncluded); | 109       filters.set(filter, isIncluded); | 
| 117     } | 110     } | 
| 118   } | 111   } | 
| 119 } | 112 } | 
| 120 | 113 | 
| 121 /** | 114 /** | 
| 122  * Checks whether a filter applies on a domain |  | 
| 123  * @param {Filter} filter |  | 
| 124  * @param {string} [domain] |  | 
| 125  * @param {Set.<Filter>} excludeSet |  | 
| 126  * @returns {boolean} |  | 
| 127  */ |  | 
| 128 function doesFilterApply(filter, domain, excludeSet) |  | 
| 129 { |  | 
| 130   return (excludeSet.size == 0 || !excludeSet.has(filter)) && |  | 
| 131          !ElemHide.getException(filter, domain); |  | 
| 132 } |  | 
| 133 |  | 
| 134 /** |  | 
| 135  * Returns a list of domain-specific filters matching a domain | 115  * Returns a list of domain-specific filters matching a domain | 
| 136  * @param {string} [domain] | 116  * @param {string} [domain] | 
| 137  * @returns {Array.<{domain: string, filters: ?Map.<Filter,boolean>}>} | 117  * @returns {Array.<?Map.<Filter,boolean>>} | 
| 138  */ | 118  */ | 
| 139 function getSpecificFiltersForDomain(domain) | 119 function getSpecificFiltersForDomain(domain) | 
| 140 { | 120 { | 
| 141   let filtersList = []; | 121   let filtersList = []; | 
| 142 | 122 | 
| 143   if (domain) | 123   if (domain) | 
| 144     domain = domain.toUpperCase(); | 124     domain = domain.toUpperCase(); | 
| 145 | 125 | 
| 146   while (domain) | 126   while (domain) | 
| 147   { | 127   { | 
| 148     let filters = filtersByDomain.get(domain); | 128     let filters = filtersByDomain.get(domain); | 
| 149     if (typeof filters != "undefined") | 129     if (typeof filters != "undefined") | 
| 150       filtersList.push({domain, filters}); | 130       filtersList.push(filters); | 
| 151 | 131 | 
| 152     let nextDot = domain.indexOf("."); | 132     let nextDot = domain.indexOf("."); | 
| 153     domain = nextDot == -1 ? null : domain.substring(nextDot + 1); | 133     domain = nextDot == -1 ? null : domain.substring(nextDot + 1); | 
| 154   } | 134   } | 
| 155 | 135 | 
| 156   return filtersList; | 136   return filtersList; | 
| 157 } | 137 } | 
| 158 | 138 | 
| 159 /** | 139 /** | 
| 160  * Returns a list of selectors that apply on a domain | 140  * Returns a list of selectors that apply on a domain from a given list of | 
|  | 141  * filters | 
| 161  * @param {string} [domain] | 142  * @param {string} [domain] | 
| 162  * @param {boolean} specificOnly | 143  * @param {Array.<?Map.<Filter,boolean>>} filtersList | 
|  | 144  * @param {Set.<Filter>} excludeSet | 
| 163  * @returns {string[]} | 145  * @returns {string[]} | 
| 164  */ | 146  */ | 
| 165 function getConditionalSelectorsForDomain(domain, specificOnly) | 147 function matchSelectors(domain, filtersList, excludeSet) | 
| 166 { | 148 { | 
| 167   let specificFilters = getSpecificFiltersForDomain(domain); | 149   let matches = []; | 
| 168 |  | 
| 169   // If there are no specific filters (nor any specific exceptions), we can |  | 
| 170   // just return the selectors from all the generic filters modulo any generic |  | 
| 171   // exceptions. |  | 
| 172   if (specificFilters.length == 0) |  | 
| 173     return specificOnly ? [] : getConditionalGenericSelectors(); |  | 
| 174 |  | 
| 175   let specificSelectors = []; |  | 
| 176 |  | 
| 177   let excludeSet = new Set(); |  | 
| 178 | 150 | 
| 179   // 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 | 
| 180   // micro-optimisations. Please be careful before making changes. | 152   // micro-optimisations. Please be careful before making changes. | 
| 181   for (let i = 0; i < specificFilters.length; i++) | 153   for (let i = 0; i < filtersList.length; i++) | 
| 182   { | 154   { | 
| 183     for (let [filter, isIncluded] of specificFilters[i].filters || []) | 155     let filters = filtersList[i]; | 
| 184     { | 156     if (filters) | 
| 185       if (!isIncluded) | 157     { | 
| 186         excludeSet.add(filter); | 158       for (let [filter, isIncluded] of filters) | 
| 187       else if (doesFilterApply(filter, domain, excludeSet)) | 159       { | 
| 188         specificSelectors.push(filter.selector); | 160         if (!isIncluded) | 
| 189     } | 161         { | 
| 190   } | 162           excludeSet.add(filter); | 
| 191 | 163         } | 
| 192   if (specificOnly) | 164         else if ((excludeSet.size == 0 || !excludeSet.has(filter)) && | 
| 193     return specificSelectors; | 165                   !exports.ElemHide.getException(filter, domain)) | 
| 194 | 166         { | 
| 195   // We use the longest subdomain of this domain found in our data structures | 167           matches.push(filter.selector); | 
| 196   // as the key to check if the domain is "generic friendly." For example, | 168         } | 
| 197   // given foo.example.com, there may be an entry for example.com in our data | 169       } | 
| 198   // structures (e.g. "example.com###foo"), so we use that subdomain as the | 170     } | 
| 199   // key. This way we make only one entry and it works for all subdomains of | 171   } | 
| 200   // example.com, except those that have specific entries | 172 | 
| 201   // (e.g. "~bar.example.com##.no-bar"). | 173   return matches; | 
| 202   let domainKey = specificFilters[0].domain; |  | 
| 203 |  | 
| 204   if (genericFriendlyDomains.has(domainKey)) |  | 
| 205     return specificSelectors.concat(getConditionalGenericSelectors()); |  | 
| 206 |  | 
| 207   let genericFilters = filtersByDomain.get(""); |  | 
| 208   if (!genericFilters) |  | 
| 209     return specificSelectors; |  | 
| 210 |  | 
| 211   let genericSelectors = []; |  | 
| 212 |  | 
| 213   for (let filter of genericFilters.keys()) |  | 
| 214   { |  | 
| 215     if (doesFilterApply(filter, domain, excludeSet)) |  | 
| 216       genericSelectors.push(filter.selector); |  | 
| 217   } |  | 
| 218 |  | 
| 219   // If the number of conditional generic selectors that apply on this domain |  | 
| 220   // is the same as the total number of conditional generic selectors, the |  | 
| 221   // domain is "generic friendly" (i.e. all generic filters apply, except those |  | 
| 222   // with generic exceptions). In that case, we mark it is as such for faster |  | 
| 223   // lookups. |  | 
| 224   if (genericSelectors.length == (conditionalGenericSelectors || {}).length) |  | 
| 225     genericFriendlyDomains.add(domainKey); |  | 
| 226 |  | 
| 227   return specificSelectors.concat(genericSelectors); |  | 
| 228 } | 174 } | 
| 229 | 175 | 
| 230 /** | 176 /** | 
| 231  * Returns a list of selectors that apply on any unknown domain | 177  * Returns a list of selectors that apply on any unknown domain | 
| 232  * @returns {string[]} | 178  * @returns {string[]} | 
| 233  */ | 179  */ | 
| 234 function getConditionalGenericSelectors() | 180 function getConditionalGenericSelectors() | 
| 235 { | 181 { | 
| 236   if (conditionalGenericSelectors) | 182   if (conditionalGenericSelectors) | 
| 237     return conditionalGenericSelectors; | 183     return conditionalGenericSelectors; | 
| (...skipping 22 matching lines...) Expand all  Loading... | 
| 260   if (!unconditionalSelectors) | 206   if (!unconditionalSelectors) | 
| 261     unconditionalSelectors = [...filterBySelector.keys()]; | 207     unconditionalSelectors = [...filterBySelector.keys()]; | 
| 262 | 208 | 
| 263   return unconditionalSelectors; | 209   return unconditionalSelectors; | 
| 264 } | 210 } | 
| 265 | 211 | 
| 266 /** | 212 /** | 
| 267  * Container for element hiding filters | 213  * Container for element hiding filters | 
| 268  * @class | 214  * @class | 
| 269  */ | 215  */ | 
| 270 let ElemHide = exports.ElemHide = { | 216 exports.ElemHide = { | 
| 271   /** | 217   /** | 
| 272    * Removes all known filters | 218    * Removes all known filters | 
| 273    */ | 219    */ | 
| 274   clear() | 220   clear() | 
| 275   { | 221   { | 
| 276     for (let collection of [filtersByDomain, filterBySelector, | 222     for (let collection of [filtersByDomain, filterBySelector, | 
| 277                             knownFilters, exceptions, | 223                             knownFilters, exceptions, | 
| 278                             genericExceptions, genericFriendlyDomains]) | 224                             genericExceptions]) | 
| 279     { | 225     { | 
| 280       collection.clear(); | 226       collection.clear(); | 
| 281     } | 227     } | 
| 282     unconditionalSelectors = null; | 228     unconditionalSelectors = null; | 
| 283     conditionalGenericSelectors = null; | 229     conditionalGenericSelectors = null; | 
| 284     FilterNotifier.emit("elemhideupdate"); | 230     FilterNotifier.emit("elemhideupdate"); | 
| 285   }, | 231   }, | 
| 286 | 232 | 
| 287   /** | 233   /** | 
| 288    * Add a new element hiding filter | 234    * Add a new element hiding filter | 
| 289    * @param {ElemHideBase} filter | 235    * @param {ElemHideBase} filter | 
| 290    */ | 236    */ | 
| 291   add(filter) | 237   add(filter) | 
| 292   { | 238   { | 
| 293     if (knownFilters.has(filter)) | 239     if (knownFilters.has(filter)) | 
| 294       return; | 240       return; | 
| 295 | 241 | 
| 296     conditionalGenericSelectors = null; | 242     conditionalGenericSelectors = null; | 
| 297     genericFriendlyDomains.clear(); |  | 
| 298 | 243 | 
| 299     if (filter instanceof ElemHideException) | 244     if (filter instanceof ElemHideException) | 
| 300     { | 245     { | 
| 301       let {selector, domains} = filter; | 246       let {selector, domains} = filter; | 
| 302 | 247 | 
| 303       let list = exceptions.get(selector); | 248       let list = exceptions.get(selector); | 
| 304       if (list) | 249       if (list) | 
| 305         list.push(filter); | 250         list.push(filter); | 
| 306       else | 251       else | 
| 307         exceptions.set(selector, [filter]); | 252         exceptions.set(selector, [filter]); | 
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 348   /** | 293   /** | 
| 349    * Removes an element hiding filter | 294    * Removes an element hiding filter | 
| 350    * @param {ElemHideBase} filter | 295    * @param {ElemHideBase} filter | 
| 351    */ | 296    */ | 
| 352   remove(filter) | 297   remove(filter) | 
| 353   { | 298   { | 
| 354     if (!knownFilters.has(filter)) | 299     if (!knownFilters.has(filter)) | 
| 355       return; | 300       return; | 
| 356 | 301 | 
| 357     conditionalGenericSelectors = null; | 302     conditionalGenericSelectors = null; | 
| 358     genericFriendlyDomains.clear(); |  | 
| 359 | 303 | 
| 360     // Whitelisting filters | 304     // Whitelisting filters | 
| 361     if (filter instanceof ElemHideException) | 305     if (filter instanceof ElemHideException) | 
| 362     { | 306     { | 
| 363       let list = exceptions.get(filter.selector); | 307       let list = exceptions.get(filter.selector); | 
| 364       let index = list.indexOf(filter); | 308       let index = list.indexOf(filter); | 
| 365       if (index >= 0) | 309       if (index >= 0) | 
| 366         list.splice(index, 1); | 310         list.splice(index, 1); | 
| 367 | 311 | 
| 368       if (filter.isGeneric()) | 312       if (filter.isGeneric()) | 
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 416     for (let i = list.length - 1; i >= 0; i--) | 360     for (let i = list.length - 1; i >= 0; i--) | 
| 417     { | 361     { | 
| 418       if (list[i].isActiveOnDomain(docDomain)) | 362       if (list[i].isActiveOnDomain(docDomain)) | 
| 419         return list[i]; | 363         return list[i]; | 
| 420     } | 364     } | 
| 421 | 365 | 
| 422     return null; | 366     return null; | 
| 423   }, | 367   }, | 
| 424 | 368 | 
| 425   /** | 369   /** | 
| 426    * Constant used by getSelectorsForDomain to return all selectors applying to |  | 
| 427    * a particular hostname. |  | 
| 428    * @type {number} |  | 
| 429    * @const |  | 
| 430    */ |  | 
| 431   ALL_MATCHING: 0, |  | 
| 432 |  | 
| 433   /** |  | 
| 434    * Constant used by getSelectorsForDomain to exclude selectors which apply to |  | 
| 435    * all websites without exception. |  | 
| 436    * @type {number} |  | 
| 437    * @const |  | 
| 438    */ |  | 
| 439   NO_UNCONDITIONAL: 1, |  | 
| 440 |  | 
| 441   /** |  | 
| 442    * Constant used by getSelectorsForDomain to return only selectors for filters |  | 
| 443    * which specifically match the given host name. |  | 
| 444    * @type {number} |  | 
| 445    * @const |  | 
| 446    */ |  | 
| 447   SPECIFIC_ONLY: 2, |  | 
| 448 |  | 
| 449   /** |  | 
| 450    * Determines from the current filter list which selectors should be applied | 370    * Determines from the current filter list which selectors should be applied | 
| 451    * on a particular host name. | 371    * on a particular host name. | 
| 452    * @param {string} domain | 372    * @param {string} domain | 
| 453    * @param {number} [criteria] | 373    * @param {boolean} [specificOnly] true if generic filters should not apply. | 
| 454    *   One of the following: ElemHide.ALL_MATCHING, ElemHide.NO_UNCONDITIONAL or | 374    * @returns {string[]} List of selectors. | 
| 455    *                         ElemHide.SPECIFIC_ONLY. |  | 
| 456    * @returns {string[]} |  | 
| 457    *   List of selectors. |  | 
| 458    */ | 375    */ | 
| 459   getSelectorsForDomain(domain, criteria = ElemHide.ALL_MATCHING) | 376   getSelectorsForDomain(domain, specificOnly = false) | 
| 460   { | 377   { | 
| 461     let specificOnly = criteria >= ElemHide.SPECIFIC_ONLY; | 378     let specificFilters = getSpecificFiltersForDomain(domain); | 
| 462     let selectors = getConditionalSelectorsForDomain(domain, specificOnly); | 379 | 
| 463 | 380     // If there are no specific filters (nor any specific exceptions), we can | 
| 464     if (criteria < ElemHide.NO_UNCONDITIONAL) | 381     // just return the selectors from all the generic filters modulo any | 
| 465       selectors = getUnconditionalSelectors().concat(selectors); | 382     // generic exceptions. | 
| 466     else if (criteria == ElemHide.NO_UNCONDITIONAL) | 383     if (specificFilters.length == 0) | 
| 467       selectors = selectors.slice(); | 384     { | 
| 468 | 385       return specificOnly ? [] : | 
| 469     return selectors; | 386                getUnconditionalSelectors() | 
|  | 387                .concat(getConditionalGenericSelectors()); | 
|  | 388     } | 
|  | 389 | 
|  | 390     let excluded = new Set(); | 
|  | 391     let selectors = matchSelectors(domain, specificFilters, excluded); | 
|  | 392 | 
|  | 393     if (specificOnly) | 
|  | 394       return selectors; | 
|  | 395 | 
|  | 396     return getUnconditionalSelectors() | 
|  | 397            .concat(selectors, | 
|  | 398                    matchSelectors(domain, [filtersByDomain.get("")], | 
|  | 399                                   excluded)); | 
| 470   } | 400   } | 
| 471 }; | 401 }; | 
| LEFT | RIGHT | 
|---|