| Left: | ||
| Right: | 
| 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 | 
| (...skipping 19 matching lines...) Expand all Loading... | |
| 30 */ | 30 */ | 
| 31 const keywordRegExp = /[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/; | 31 const keywordRegExp = /[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/; | 
| 32 | 32 | 
| 33 /** | 33 /** | 
| 34 * Regular expression for matching all keywords in a filter. | 34 * Regular expression for matching all keywords in a filter. | 
| 35 * @type {RegExp} | 35 * @type {RegExp} | 
| 36 */ | 36 */ | 
| 37 const allKeywordsRegExp = new RegExp(keywordRegExp, "g"); | 37 const allKeywordsRegExp = new RegExp(keywordRegExp, "g"); | 
| 38 | 38 | 
| 39 /** | 39 /** | 
| 40 * Bitmask for content types that are implied by default in a filter, like | |
| 41 * <code>$script</code>, <code>$image</code>, <code>$stylesheet</code>, and so | |
| 42 * on. | |
| 43 * @type {number} | |
| 44 */ | |
| 45 const DEFAULT_TYPES = RegExpFilter.prototype.contentType; | |
| 46 | |
| 47 /** | |
| 48 * Bitmask for "types" that must always be specified in a filter explicitly, | |
| 49 * like <code>$csp</code>, <code>$popup</code>, <code>$elemhide</code>, and so | |
| 50 * on. | |
| 51 * @type {number} | |
| 52 */ | |
| 53 const NON_DEFAULT_TYPES = ~DEFAULT_TYPES; | |
| 54 | |
| 55 /** | |
| 40 * Bitmask for "types" that are for exception rules only, like | 56 * Bitmask for "types" that are for exception rules only, like | 
| 41 * <code>$document</code>, <code>$elemhide</code>, and so on. | 57 * <code>$document</code>, <code>$elemhide</code>, and so on. | 
| 42 * @type {number} | 58 * @type {number} | 
| 43 */ | 59 */ | 
| 44 const WHITELIST_ONLY_TYPES = RegExpFilter.typeMap.DOCUMENT | | 60 const WHITELIST_ONLY_TYPES = RegExpFilter.typeMap.DOCUMENT | | 
| 45 RegExpFilter.typeMap.ELEMHIDE | | 61 RegExpFilter.typeMap.ELEMHIDE | | 
| 46 RegExpFilter.typeMap.GENERICHIDE | | 62 RegExpFilter.typeMap.GENERICHIDE | | 
| 47 RegExpFilter.typeMap.GENERICBLOCK; | 63 RegExpFilter.typeMap.GENERICBLOCK; | 
| 48 | 64 | 
| 49 /** | 65 /** | 
| 66 * Yields individual non-default types from a filter's type mask. | |
| 67 * @param {number} contentType A filter's type mask. | |
| 68 * @yields {number} | |
| 69 */ | |
| 70 function* nonDefaultTypes(contentType) | |
| 
 
Manish Jethani
2018/11/02 00:33:47
For a filter like `foo$script,image,popup`, this f
 
 | |
| 71 { | |
| 72 for (let mask = contentType & NON_DEFAULT_TYPES, bitIndex = 0; | |
| 73 mask != 0; mask >>>= 1, bitIndex++) | |
| 74 { | |
| 75 if ((mask & 1) != 0) | |
| 76 { | |
| 77 // Note: The zero-fill right shift by zero is necessary for dropping the | |
| 78 // sign. | |
| 79 yield 1 << bitIndex >>> 0; | |
| 80 } | |
| 81 } | |
| 82 } | |
| 83 | |
| 84 /** | |
| 85 * Adds a filter by a given keyword to a map. | |
| 86 * @param {RegExpFilter} filter | |
| 87 * @param {string} keyword | |
| 88 * @param {Map.<string,(RegExpFilter|Set.<RegExpFilter>)>} map | |
| 89 */ | |
| 90 function addFilterByKeyword(filter, keyword, map) | |
| 91 { | |
| 92 let set = map.get(keyword); | |
| 
 
Manish Jethani
2018/11/02 00:33:47
This has been copied and pasted from the add and r
 
 | |
| 93 if (typeof set == "undefined") | |
| 94 { | |
| 95 map.set(keyword, filter); | |
| 96 } | |
| 97 else if (set.size == 1) | |
| 98 { | |
| 99 if (filter != set) | |
| 100 map.set(keyword, new Set([set, filter])); | |
| 101 } | |
| 102 else | |
| 103 { | |
| 104 set.add(filter); | |
| 105 } | |
| 106 } | |
| 107 | |
| 108 /** | |
| 109 * Removes a filter by a given keyword from a map. | |
| 110 * @param {RegExpFilter} filter | |
| 111 * @param {string} keyword | |
| 112 * @param {Map.<string,(RegExpFilter|Set.<RegExpFilter>)>} map | |
| 113 */ | |
| 114 function removeFilterByKeyword(filter, keyword, map) | |
| 115 { | |
| 116 let set = map.get(keyword); | |
| 117 if (typeof set == "undefined") | |
| 118 return; | |
| 119 | |
| 120 if (set.size == 1) | |
| 121 { | |
| 122 if (filter == set) | |
| 123 map.delete(keyword); | |
| 124 } | |
| 125 else | |
| 126 { | |
| 127 set.delete(filter); | |
| 128 | |
| 129 if (set.size == 1) | |
| 130 map.set(keyword, [...set][0]); | |
| 131 } | |
| 132 } | |
| 133 | |
| 134 /** | |
| 50 * Checks whether a particular filter is slow. | 135 * Checks whether a particular filter is slow. | 
| 51 * @param {RegExpFilter} filter | 136 * @param {RegExpFilter} filter | 
| 52 * @returns {boolean} | 137 * @returns {boolean} | 
| 53 */ | 138 */ | 
| 54 function isSlowFilter(filter) | 139 function isSlowFilter(filter) | 
| 55 { | 140 { | 
| 56 return !filter.pattern || !keywordRegExp.test(filter.pattern); | 141 return !filter.pattern || !keywordRegExp.test(filter.pattern); | 
| 57 } | 142 } | 
| 58 | 143 | 
| 59 exports.isSlowFilter = isSlowFilter; | 144 exports.isSlowFilter = isSlowFilter; | 
| (...skipping 11 matching lines...) Expand all Loading... | |
| 71 * @private | 156 * @private | 
| 72 */ | 157 */ | 
| 73 this._simpleFiltersByKeyword = new Map(); | 158 this._simpleFiltersByKeyword = new Map(); | 
| 74 | 159 | 
| 75 /** | 160 /** | 
| 76 * Lookup table for complex filters by their associated keyword | 161 * Lookup table for complex filters by their associated keyword | 
| 77 * @type {Map.<string,(RegExpFilter|Set.<RegExpFilter>)>} | 162 * @type {Map.<string,(RegExpFilter|Set.<RegExpFilter>)>} | 
| 78 * @private | 163 * @private | 
| 79 */ | 164 */ | 
| 80 this._complexFiltersByKeyword = new Map(); | 165 this._complexFiltersByKeyword = new Map(); | 
| 166 | |
| 167 /** | |
| 168 * Lookup table of type-specific lookup tables for complex filters by their | |
| 169 * associated keyword | |
| 170 * @type {Map.<string,Map.<string,(RegExpFilter|Set.<RegExpFilter>)>>} | |
| 171 * @private | |
| 172 */ | |
| 173 this._filterMapsByType = new Map(); | |
| 81 } | 174 } | 
| 82 | 175 | 
| 83 /** | 176 /** | 
| 84 * Removes all known filters | 177 * Removes all known filters | 
| 85 */ | 178 */ | 
| 86 clear() | 179 clear() | 
| 87 { | 180 { | 
| 88 this._simpleFiltersByKeyword.clear(); | 181 this._simpleFiltersByKeyword.clear(); | 
| 89 this._complexFiltersByKeyword.clear(); | 182 this._complexFiltersByKeyword.clear(); | 
| 183 this._filterMapsByType.clear(); | |
| 90 } | 184 } | 
| 91 | 185 | 
| 92 /** | 186 /** | 
| 93 * Adds a filter to the matcher | 187 * Adds a filter to the matcher | 
| 94 * @param {RegExpFilter} filter | 188 * @param {RegExpFilter} filter | 
| 95 */ | 189 */ | 
| 96 add(filter) | 190 add(filter) | 
| 97 { | 191 { | 
| 98 let filtersByKeyword = filter.isLocationOnly() ? | |
| 99 this._simpleFiltersByKeyword : | |
| 100 this._complexFiltersByKeyword; | |
| 101 // Look for a suitable keyword | 192 // Look for a suitable keyword | 
| 102 let keyword = this.findKeyword(filter); | 193 let keyword = this.findKeyword(filter); | 
| 103 let set = filtersByKeyword.get(keyword); | 194 let locationOnly = filter.isLocationOnly(); | 
| 104 if (typeof set == "undefined") | 195 | 
| 196 addFilterByKeyword(filter, keyword, | |
| 197 locationOnly ? this._simpleFiltersByKeyword : | |
| 198 this._complexFiltersByKeyword); | |
| 199 | |
| 200 if (locationOnly) | |
| 201 return; | |
| 202 | |
| 203 for (let type of nonDefaultTypes(filter.contentType)) | |
| 105 { | 204 { | 
| 106 filtersByKeyword.set(keyword, filter); | 205 let map = this._filterMapsByType.get(type); | 
| 107 } | 206 if (!map) | 
| 108 else if (set.size == 1) | 207 this._filterMapsByType.set(type, map = new Map()); | 
| 109 { | 208 | 
| 110 if (filter != set) | 209 addFilterByKeyword(filter, keyword, map); | 
| 111 filtersByKeyword.set(keyword, new Set([set, filter])); | |
| 112 } | |
| 113 else | |
| 114 { | |
| 115 set.add(filter); | |
| 116 } | 210 } | 
| 117 } | 211 } | 
| 118 | 212 | 
| 119 /** | 213 /** | 
| 120 * Removes a filter from the matcher | 214 * Removes a filter from the matcher | 
| 121 * @param {RegExpFilter} filter | 215 * @param {RegExpFilter} filter | 
| 122 */ | 216 */ | 
| 123 remove(filter) | 217 remove(filter) | 
| 124 { | 218 { | 
| 125 let filtersByKeyword = filter.isLocationOnly() ? | |
| 126 this._simpleFiltersByKeyword : | |
| 127 this._complexFiltersByKeyword; | |
| 128 let keyword = this.findKeyword(filter); | 219 let keyword = this.findKeyword(filter); | 
| 129 let set = filtersByKeyword.get(keyword); | 220 let locationOnly = filter.isLocationOnly(); | 
| 130 if (typeof set == "undefined") | 221 | 
| 222 removeFilterByKeyword(filter, keyword, | |
| 223 locationOnly ? this._simpleFiltersByKeyword : | |
| 224 this._complexFiltersByKeyword); | |
| 225 | |
| 226 if (locationOnly) | |
| 131 return; | 227 return; | 
| 132 | 228 | 
| 133 if (set.size == 1) | 229 for (let type of nonDefaultTypes(filter.contentType)) | 
| 134 { | 230 { | 
| 135 if (filter == set) | 231 let map = this._filterMapsByType.get(type); | 
| 136 filtersByKeyword.delete(keyword); | 232 if (map) | 
| 137 } | 233 removeFilterByKeyword(filter, keyword, map); | 
| 138 else | |
| 139 { | |
| 140 set.delete(filter); | |
| 141 | |
| 142 if (set.size == 1) | |
| 143 filtersByKeyword.set(keyword, [...set][0]); | |
| 144 } | 234 } | 
| 145 } | 235 } | 
| 146 | 236 | 
| 147 /** | 237 /** | 
| 148 * Chooses a keyword to be associated with the filter | 238 * Chooses a keyword to be associated with the filter | 
| 149 * @param {Filter} filter | 239 * @param {Filter} filter | 
| 150 * @returns {string} keyword or an empty string if no keyword could be found | 240 * @returns {string} keyword or an empty string if no keyword could be found | 
| 151 * @protected | 241 * @protected | 
| 152 */ | 242 */ | 
| 153 findKeyword(filter) | 243 findKeyword(filter) | 
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 193 * @param {string} [sitekey] | 283 * @param {string} [sitekey] | 
| 194 * @param {boolean} [specificOnly] | 284 * @param {boolean} [specificOnly] | 
| 195 * @returns {?Filter} | 285 * @returns {?Filter} | 
| 196 * @protected | 286 * @protected | 
| 197 */ | 287 */ | 
| 198 checkEntryMatch(keyword, location, typeMask, docDomain, thirdParty, sitekey, | 288 checkEntryMatch(keyword, location, typeMask, docDomain, thirdParty, sitekey, | 
| 199 specificOnly) | 289 specificOnly) | 
| 200 { | 290 { | 
| 201 // We need to skip the simple (location-only) filters if the type mask does | 291 // We need to skip the simple (location-only) filters if the type mask does | 
| 202 // not contain any default content types. | 292 // not contain any default content types. | 
| 203 if ((typeMask & RegExpFilter.prototype.contentType) != 0) | 293 if ((typeMask & DEFAULT_TYPES) != 0) | 
| 204 { | 294 { | 
| 205 let simpleSet = this._simpleFiltersByKeyword.get(keyword); | 295 let simpleSet = this._simpleFiltersByKeyword.get(keyword); | 
| 206 if (simpleSet) | 296 if (simpleSet) | 
| 207 { | 297 { | 
| 208 for (let filter of simpleSet) | 298 for (let filter of simpleSet) | 
| 209 { | 299 { | 
| 210 if (specificOnly && !(filter instanceof WhitelistFilter)) | 300 if (specificOnly && !(filter instanceof WhitelistFilter)) | 
| 211 continue; | 301 continue; | 
| 212 | 302 | 
| 213 if (filter.matchesLocation(location)) | 303 if (filter.matchesLocation(location)) | 
| 214 return filter; | 304 return filter; | 
| 215 } | 305 } | 
| 216 } | 306 } | 
| 217 } | 307 } | 
| 218 | 308 | 
| 219 let complexSet = this._complexFiltersByKeyword.get(keyword); | 309 let complexSet = null; | 
| 310 | |
| 311 // If the type mask contains a non-default type (first condition) and it is | |
| 312 // the only type in the mask (second condition), we can use the | |
| 313 // type-specific map, which typically contains a lot fewer filters. This | |
| 314 // enables faster lookups for whitelisting types like $document, $elemhide, | |
| 315 // and so on, as well as other special types like $csp. | |
| 316 if ((typeMask & NON_DEFAULT_TYPES) != 0 && (typeMask & typeMask - 1) == 0) | |
| 
 
Manish Jethani
2018/11/02 00:33:47
The second condition here basically checks if the
 
hub
2018/12/11 15:34:59
What throw me off here is the evaluation order of
 
 | |
| 317 { | |
| 318 let map = this._filterMapsByType.get(typeMask); | |
| 319 if (map) | |
| 320 complexSet = map.get(keyword); | |
| 321 } | |
| 322 else | |
| 323 { | |
| 324 complexSet = this._complexFiltersByKeyword.get(keyword); | |
| 325 } | |
| 326 | |
| 220 if (complexSet) | 327 if (complexSet) | 
| 221 { | 328 { | 
| 222 for (let filter of complexSet) | 329 for (let filter of complexSet) | 
| 223 { | 330 { | 
| 224 if (specificOnly && filter.isGeneric() && | 331 if (specificOnly && filter.isGeneric() && | 
| 225 !(filter instanceof WhitelistFilter)) | 332 !(filter instanceof WhitelistFilter)) | 
| 226 continue; | 333 continue; | 
| 227 | 334 | 
| 228 if (filter.matches(location, typeMask, docDomain, thirdParty, sitekey)) | 335 if (filter.matches(location, typeMask, docDomain, thirdParty, sitekey)) | 
| 229 return filter; | 336 return filter; | 
| (...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 432 | 539 | 
| 433 exports.CombinedMatcher = CombinedMatcher; | 540 exports.CombinedMatcher = CombinedMatcher; | 
| 434 | 541 | 
| 435 /** | 542 /** | 
| 436 * Shared {@link CombinedMatcher} instance that should usually be used. | 543 * Shared {@link CombinedMatcher} instance that should usually be used. | 
| 437 * @type {CombinedMatcher} | 544 * @type {CombinedMatcher} | 
| 438 */ | 545 */ | 
| 439 let defaultMatcher = new CombinedMatcher(); | 546 let defaultMatcher = new CombinedMatcher(); | 
| 440 | 547 | 
| 441 exports.defaultMatcher = defaultMatcher; | 548 exports.defaultMatcher = defaultMatcher; | 
| OLD | NEW |