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 |