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

Side by Side Diff: lib/matcher.js

Issue 29869571: Issue 6741 - Use ES2015 classes in lib/matcher.js (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Address PS3 comments Created Sept. 5, 2018, 2:01 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 | no next file » | no next file with comments »
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
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 "use strict"; 18 "use strict";
19 19
20 /** 20 /**
21 * @fileOverview Matcher class implementing matching addresses against 21 * @fileOverview Matcher class implementing matching addresses against
22 * a list of filters. 22 * a list of filters.
23 */ 23 */
24 24
25 const {WhitelistFilter} = require("./filterClasses"); 25 const {WhitelistFilter} = require("./filterClasses");
26 26
27 /** 27 /**
28 * Blacklist/whitelist filter matching 28 * Blacklist/whitelist filter matching
29 * @constructor
30 */ 29 */
31 function Matcher() 30 class Matcher
32 { 31 {
33 this.clear(); 32 constructor()
34 } 33 {
35 exports.Matcher = Matcher; 34 /**
35 * Lookup table for filters by their associated keyword
36 * @type {Map.<string,(Filter|Filter[])>}
37 */
38 this.filterByKeyword = new Map();
36 39
37 Matcher.prototype = { 40 /**
38 /** 41 * Lookup table for keywords by the filter
39 * Lookup table for filters by their associated keyword 42 * @type {Map.<Filter,string>}
40 * @type {Map.<string,(Filter|Filter[])>} 43 */
41 */ 44 this.keywordByFilter = new Map();
42 filterByKeyword: null, 45 }
43
44 /**
45 * Lookup table for keywords by the filter
46 * @type {Map.<Filter,string>}
47 */
48 keywordByFilter: null,
49 46
50 /** 47 /**
51 * Removes all known filters 48 * Removes all known filters
52 */ 49 */
53 clear() 50 clear()
54 { 51 {
55 this.filterByKeyword = new Map(); 52 this.filterByKeyword.clear();
56 this.keywordByFilter = new Map(); 53 this.keywordByFilter.clear();
57 }, 54 }
58 55
59 /** 56 /**
60 * Adds a filter to the matcher 57 * Adds a filter to the matcher
61 * @param {RegExpFilter} filter 58 * @param {RegExpFilter} filter
62 */ 59 */
63 add(filter) 60 add(filter)
64 { 61 {
65 if (this.keywordByFilter.has(filter)) 62 if (this.keywordByFilter.has(filter))
66 return; 63 return;
67 64
68 // Look for a suitable keyword 65 // Look for a suitable keyword
69 let keyword = this.findKeyword(filter); 66 let keyword = this.findKeyword(filter);
70 let oldEntry = this.filterByKeyword.get(keyword); 67 let oldEntry = this.filterByKeyword.get(keyword);
71 if (typeof oldEntry == "undefined") 68 if (typeof oldEntry == "undefined")
72 this.filterByKeyword.set(keyword, filter); 69 this.filterByKeyword.set(keyword, filter);
73 else if (oldEntry.length == 1) 70 else if (oldEntry.length == 1)
74 this.filterByKeyword.set(keyword, [oldEntry, filter]); 71 this.filterByKeyword.set(keyword, [oldEntry, filter]);
75 else 72 else
76 oldEntry.push(filter); 73 oldEntry.push(filter);
77 this.keywordByFilter.set(filter, keyword); 74 this.keywordByFilter.set(filter, keyword);
78 }, 75 }
79 76
80 /** 77 /**
81 * Removes a filter from the matcher 78 * Removes a filter from the matcher
82 * @param {RegExpFilter} filter 79 * @param {RegExpFilter} filter
83 */ 80 */
84 remove(filter) 81 remove(filter)
85 { 82 {
86 let keyword = this.keywordByFilter.get(filter); 83 let keyword = this.keywordByFilter.get(filter);
87 if (typeof keyword == "undefined") 84 if (typeof keyword == "undefined")
88 return; 85 return;
89 86
90 let list = this.filterByKeyword.get(keyword); 87 let list = this.filterByKeyword.get(keyword);
91 if (list.length <= 1) 88 if (list.length <= 1)
92 this.filterByKeyword.delete(keyword); 89 this.filterByKeyword.delete(keyword);
93 else 90 else
94 { 91 {
95 let index = list.indexOf(filter); 92 let index = list.indexOf(filter);
96 if (index >= 0) 93 if (index >= 0)
97 { 94 {
98 list.splice(index, 1); 95 list.splice(index, 1);
99 if (list.length == 1) 96 if (list.length == 1)
100 this.filterByKeyword.set(keyword, list[0]); 97 this.filterByKeyword.set(keyword, list[0]);
101 } 98 }
102 } 99 }
103 100
104 this.keywordByFilter.delete(filter); 101 this.keywordByFilter.delete(filter);
105 }, 102 }
106 103
107 /** 104 /**
108 * Chooses a keyword to be associated with the filter 105 * Chooses a keyword to be associated with the filter
109 * @param {Filter} filter 106 * @param {Filter} filter
110 * @return {string} keyword or an empty string if no keyword could be found 107 * @returns {string} keyword or an empty string if no keyword could be found
111 */ 108 */
112 findKeyword(filter) 109 findKeyword(filter)
113 { 110 {
114 let result = ""; 111 let result = "";
115 let {pattern} = filter; 112 let {pattern} = filter;
116 if (pattern == null) 113 if (pattern == null)
117 return result; 114 return result;
118 115
119 let candidates = pattern.toLowerCase().match( 116 let candidates = pattern.toLowerCase().match(
120 /[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g 117 /[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g
(...skipping 11 matching lines...) Expand all
132 let count = typeof filters != "undefined" ? filters.length : 0; 129 let count = typeof filters != "undefined" ? filters.length : 0;
133 if (count < resultCount || 130 if (count < resultCount ||
134 (count == resultCount && candidate.length > resultLength)) 131 (count == resultCount && candidate.length > resultLength))
135 { 132 {
136 result = candidate; 133 result = candidate;
137 resultCount = count; 134 resultCount = count;
138 resultLength = candidate.length; 135 resultLength = candidate.length;
139 } 136 }
140 } 137 }
141 return result; 138 return result;
142 }, 139 }
143 140
144 /** 141 /**
145 * Checks whether a particular filter is being matched against. 142 * Checks whether a particular filter is being matched against.
146 * @param {RegExpFilter} filter 143 * @param {RegExpFilter} filter
147 * @return {boolean} 144 * @returns {boolean}
148 */ 145 */
149 hasFilter(filter) 146 hasFilter(filter)
150 { 147 {
151 return this.keywordByFilter.has(filter); 148 return this.keywordByFilter.has(filter);
152 }, 149 }
153 150
154 /** 151 /**
155 * Returns the keyword used for a filter, null for unknown filters. 152 * Returns the keyword used for a filter, <code>null</code>
153 * for unknown filters.
156 * @param {RegExpFilter} filter 154 * @param {RegExpFilter} filter
157 * @return {?string} 155 * @returns {?string}
158 */ 156 */
159 getKeywordForFilter(filter) 157 getKeywordForFilter(filter)
160 { 158 {
161 let keyword = this.keywordByFilter.get(filter); 159 let keyword = this.keywordByFilter.get(filter);
162 return typeof keyword != "undefined" ? keyword : null; 160 return typeof keyword != "undefined" ? keyword : null;
163 }, 161 }
164 162
165 /** 163 /**
166 * Checks whether the entries for a particular keyword match a URL 164 * Checks whether the entries for a particular keyword match a URL
167 * @param {string} keyword 165 * @param {string} keyword
168 * @param {string} location 166 * @param {string} location
169 * @param {number} typeMask 167 * @param {number} typeMask
170 * @param {string} docDomain 168 * @param {string} [docDomain]
171 * @param {boolean} thirdParty 169 * @param {boolean} [thirdParty]
172 * @param {string} sitekey 170 * @param {string} [sitekey]
173 * @param {boolean} specificOnly 171 * @param {boolean} [specificOnly]
174 * @return {?Filter} 172 * @returns {?Filter}
175 */ 173 */
176 _checkEntryMatch(keyword, location, typeMask, docDomain, thirdParty, sitekey, 174 _checkEntryMatch(keyword, location, typeMask, docDomain, thirdParty, sitekey,
177 specificOnly) 175 specificOnly)
178 { 176 {
179 let list = this.filterByKeyword.get(keyword); 177 let list = this.filterByKeyword.get(keyword);
180 if (typeof list == "undefined") 178 if (typeof list == "undefined")
181 return null; 179 return null;
182 for (let i = 0; i < list.length; i++) 180 for (let i = 0; i < list.length; i++)
183 { 181 {
184 let filter = list[i]; 182 let filter = list[i];
185 183
186 if (specificOnly && filter.isGeneric() && 184 if (specificOnly && filter.isGeneric() &&
187 !(filter instanceof WhitelistFilter)) 185 !(filter instanceof WhitelistFilter))
188 continue; 186 continue;
189 187
190 if (filter.matches(location, typeMask, docDomain, thirdParty, sitekey)) 188 if (filter.matches(location, typeMask, docDomain, thirdParty, sitekey))
191 return filter; 189 return filter;
192 } 190 }
193 return null; 191 return null;
194 }, 192 }
195 193
196 /** 194 /**
197 * Tests whether the URL matches any of the known filters 195 * Tests whether the URL matches any of the known filters
198 * @param {string} location 196 * @param {string} location
199 * URL to be tested 197 * URL to be tested
200 * @param {number} typeMask 198 * @param {number} typeMask
201 * bitmask of content / request types to match 199 * bitmask of content / request types to match
202 * @param {string} docDomain 200 * @param {string} [docDomain]
203 * domain name of the document that loads the URL 201 * domain name of the document that loads the URL
204 * @param {boolean} thirdParty 202 * @param {boolean} [thirdParty]
205 * should be true if the URL is a third-party request 203 * should be true if the URL is a third-party request
206 * @param {string} sitekey 204 * @param {string} [sitekey]
207 * public key provided by the document 205 * public key provided by the document
208 * @param {boolean} specificOnly 206 * @param {boolean} [specificOnly]
209 * should be true if generic matches should be ignored 207 * should be <code>true</code> if generic matches should be ignored
210 * @return {?RegExpFilter} 208 * @returns {?RegExpFilter}
211 * matching filter or null 209 * matching filter or <code>null</code>
212 */ 210 */
213 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly) 211 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly)
214 { 212 {
215 let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g); 213 let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
216 if (candidates === null) 214 if (candidates === null)
217 candidates = []; 215 candidates = [];
218 candidates.push(""); 216 candidates.push("");
219 for (let i = 0, l = candidates.length; i < l; i++) 217 for (let i = 0, l = candidates.length; i < l; i++)
220 { 218 {
221 let result = this._checkEntryMatch(candidates[i], location, typeMask, 219 let result = this._checkEntryMatch(candidates[i], location, typeMask,
222 docDomain, thirdParty, sitekey, 220 docDomain, thirdParty, sitekey,
223 specificOnly); 221 specificOnly);
224 if (result) 222 if (result)
225 return result; 223 return result;
226 } 224 }
227 225
228 return null; 226 return null;
229 } 227 }
230 }; 228 }
229
230 exports.Matcher = Matcher;
231
231 232
232 /** 233 /**
233 * Combines a matcher for blocking and exception rules, automatically sorts 234 * Combines a matcher for blocking and exception rules, automatically sorts
234 * rules into two Matcher instances. 235 * rules into two {@link Matcher} instances.
235 * @constructor
236 * @augments Matcher
237 */ 236 */
238 function CombinedMatcher() 237 class CombinedMatcher
239 { 238 {
240 this.blacklist = new Matcher(); 239 constructor()
241 this.whitelist = new Matcher(); 240 {
242 this.resultCache = new Map(); 241 /**
243 } 242 * Maximal number of matching cache entries to be kept
244 exports.CombinedMatcher = CombinedMatcher; 243 * @type {number}
244 */
245 this.maxCacheEntries = 1000;
245 246
246 /** 247 /**
247 * Maximal number of matching cache entries to be kept 248 * Matcher for blocking rules.
248 * @type {number} 249 * @type {Matcher}
249 */ 250 */
250 CombinedMatcher.maxCacheEntries = 1000; 251 this.blacklist = new Matcher();
251 252
252 CombinedMatcher.prototype = 253 /**
253 { 254 * Matcher for exception rules.
254 /** 255 * @type {Matcher}
255 * Matcher for blocking rules. 256 */
256 * @type {Matcher} 257 this.whitelist = new Matcher();
257 */
258 blacklist: null,
259 258
260 /** 259 /**
261 * Matcher for exception rules. 260 * Lookup table of previous {@link Matcher#matchesAny} results
262 * @type {Matcher} 261 * @type {Map.<string,Filter>}
263 */ 262 */
264 whitelist: null, 263 this.resultCache = new Map();
265 264 }
266 /**
267 * Lookup table of previous matchesAny results
268 * @type {Map.<string,Filter>}
269 */
270 resultCache: null,
271 265
272 /** 266 /**
273 * @see Matcher#clear 267 * @see Matcher#clear
274 */ 268 */
275 clear() 269 clear()
276 { 270 {
277 this.blacklist.clear(); 271 this.blacklist.clear();
278 this.whitelist.clear(); 272 this.whitelist.clear();
279 this.resultCache.clear(); 273 this.resultCache.clear();
280 }, 274 }
281 275
282 /** 276 /**
283 * @see Matcher#add 277 * @see Matcher#add
284 * @param {Filter} filter 278 * @param {Filter} filter
285 */ 279 */
286 add(filter) 280 add(filter)
287 { 281 {
288 if (filter instanceof WhitelistFilter) 282 if (filter instanceof WhitelistFilter)
289 this.whitelist.add(filter); 283 this.whitelist.add(filter);
290 else 284 else
291 this.blacklist.add(filter); 285 this.blacklist.add(filter);
292 286
293 this.resultCache.clear(); 287 this.resultCache.clear();
294 }, 288 }
295 289
296 /** 290 /**
297 * @see Matcher#remove 291 * @see Matcher#remove
298 * @param {Filter} filter 292 * @param {Filter} filter
299 */ 293 */
300 remove(filter) 294 remove(filter)
301 { 295 {
302 if (filter instanceof WhitelistFilter) 296 if (filter instanceof WhitelistFilter)
303 this.whitelist.remove(filter); 297 this.whitelist.remove(filter);
304 else 298 else
305 this.blacklist.remove(filter); 299 this.blacklist.remove(filter);
306 300
307 this.resultCache.clear(); 301 this.resultCache.clear();
308 }, 302 }
309 303
310 /** 304 /**
311 * @see Matcher#findKeyword 305 * @see Matcher#findKeyword
312 * @param {Filter} filter 306 * @param {Filter} filter
313 * @return {string} keyword 307 * @returns {string} keyword
314 */ 308 */
315 findKeyword(filter) 309 findKeyword(filter)
316 { 310 {
317 if (filter instanceof WhitelistFilter) 311 if (filter instanceof WhitelistFilter)
318 return this.whitelist.findKeyword(filter); 312 return this.whitelist.findKeyword(filter);
319 return this.blacklist.findKeyword(filter); 313 return this.blacklist.findKeyword(filter);
320 }, 314 }
321 315
322 /** 316 /**
323 * @see Matcher#hasFilter 317 * @see Matcher#hasFilter
324 * @param {Filter} filter 318 * @param {Filter} filter
325 * @return {boolean} 319 * @returns {boolean}
326 */ 320 */
327 hasFilter(filter) 321 hasFilter(filter)
328 { 322 {
329 if (filter instanceof WhitelistFilter) 323 if (filter instanceof WhitelistFilter)
330 return this.whitelist.hasFilter(filter); 324 return this.whitelist.hasFilter(filter);
331 return this.blacklist.hasFilter(filter); 325 return this.blacklist.hasFilter(filter);
332 }, 326 }
333 327
334 /** 328 /**
335 * @see Matcher#getKeywordForFilter 329 * @see Matcher#getKeywordForFilter
336 * @param {Filter} filter 330 * @param {Filter} filter
337 * @return {string} keyword 331 * @returns {string} keyword
338 */ 332 */
339 getKeywordForFilter(filter) 333 getKeywordForFilter(filter)
340 { 334 {
341 if (filter instanceof WhitelistFilter) 335 if (filter instanceof WhitelistFilter)
342 return this.whitelist.getKeywordForFilter(filter); 336 return this.whitelist.getKeywordForFilter(filter);
343 return this.blacklist.getKeywordForFilter(filter); 337 return this.blacklist.getKeywordForFilter(filter);
344 }, 338 }
345 339
346 /** 340 /**
347 * Checks whether a particular filter is slow 341 * Checks whether a particular filter is slow
348 * @param {RegExpFilter} filter 342 * @param {RegExpFilter} filter
349 * @return {boolean} 343 * @returns {boolean}
350 */ 344 */
351 isSlowFilter(filter) 345 isSlowFilter(filter)
352 { 346 {
353 let matcher = ( 347 let matcher = (
354 filter instanceof WhitelistFilter ? this.whitelist : this.blacklist 348 filter instanceof WhitelistFilter ? this.whitelist : this.blacklist
355 ); 349 );
356 if (matcher.hasFilter(filter)) 350 if (matcher.hasFilter(filter))
357 return !matcher.getKeywordForFilter(filter); 351 return !matcher.getKeywordForFilter(filter);
358 return !matcher.findKeyword(filter); 352 return !matcher.findKeyword(filter);
359 }, 353 }
360 354
361 /** 355 /**
362 * Optimized filter matching testing both whitelist and blacklist matchers 356 * Optimized filter matching testing both whitelist and blacklist matchers
363 * simultaneously. For parameters see 357 * simultaneously. For parameters see
364 {@link Matcher#matchesAny Matcher.matchesAny()}. 358 {@link Matcher#matchesAny Matcher.matchesAny()}.
365 * @see Matcher#matchesAny 359 * @see Matcher#matchesAny
366 * @inheritdoc 360 * @inheritdoc
367 */ 361 */
368 matchesAnyInternal(location, typeMask, docDomain, thirdParty, sitekey, 362 matchesAnyInternal(location, typeMask, docDomain, thirdParty, sitekey,
369 specificOnly) 363 specificOnly)
(...skipping 14 matching lines...) Expand all
384 return result; 378 return result;
385 if (blacklistHit === null) 379 if (blacklistHit === null)
386 { 380 {
387 blacklistHit = this.blacklist._checkEntryMatch( 381 blacklistHit = this.blacklist._checkEntryMatch(
388 substr, location, typeMask, docDomain, thirdParty, sitekey, 382 substr, location, typeMask, docDomain, thirdParty, sitekey,
389 specificOnly 383 specificOnly
390 ); 384 );
391 } 385 }
392 } 386 }
393 return blacklistHit; 387 return blacklistHit;
394 }, 388 }
395 389
396 /** 390 /**
397 * @see Matcher#matchesAny 391 * @see Matcher#matchesAny
398 * @inheritdoc 392 * @inheritdoc
399 */ 393 */
400 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly) 394 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly)
401 { 395 {
402 let key = location + " " + typeMask + " " + docDomain + " " + thirdParty + 396 let key = location + " " + typeMask + " " + docDomain + " " + thirdParty +
403 " " + sitekey + " " + specificOnly; 397 " " + sitekey + " " + specificOnly;
404 398
405 let result = this.resultCache.get(key); 399 let result = this.resultCache.get(key);
406 if (typeof result != "undefined") 400 if (typeof result != "undefined")
407 return result; 401 return result;
408 402
409 result = this.matchesAnyInternal(location, typeMask, docDomain, 403 result = this.matchesAnyInternal(location, typeMask, docDomain,
410 thirdParty, sitekey, specificOnly); 404 thirdParty, sitekey, specificOnly);
411 405
412 if (this.resultCache.size >= CombinedMatcher.maxCacheEntries) 406 if (this.resultCache.size >= CombinedMatcher.maxCacheEntries)
413 this.resultCache.clear(); 407 this.resultCache.clear();
414 408
415 this.resultCache.set(key, result); 409 this.resultCache.set(key, result);
416 410
417 return result; 411 return result;
418 } 412 }
419 }; 413 }
414
415 exports.CombinedMatcher = CombinedMatcher;
420 416
421 /** 417 /**
422 * Shared CombinedMatcher instance that should usually be used. 418 * Shared {@link CombinedMatcher} instance that should usually be used.
423 * @type {CombinedMatcher} 419 * @type {CombinedMatcher}
424 */ 420 */
425 exports.defaultMatcher = new CombinedMatcher(); 421 let defaultMatcher = new CombinedMatcher();
Manish Jethani 2018/09/05 19:34:47 Nit: Hey, can we also add a blank line here? I did
Jon Sonesen 2018/09/07 03:06:44 Done.
Manish Jethani 2018/09/07 03:14:59 Looks like you forgot to upload the patch?
Manish Jethani 2018/09/07 03:18:02 Sorry, I just saw your email. Can you also upload
422 exports.defaultMatcher = defaultMatcher;
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld