Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: lib/matcher.js

Issue 29933592: Issue 7089 - Match filters by type for non-default types (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Created Nov. 1, 2018, 11:36 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | test/matcher.js » ('j') | test/matcher.js » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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;
OLDNEW
« no previous file with comments | « no previous file | test/matcher.js » ('j') | test/matcher.js » ('J')

Powered by Google App Engine
This is Rietveld