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 PS1 Comments Created Sept. 2, 2018, 4: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 | 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 {Filter, WhitelistFilter} = require("./filterClasses"); 25 const {Filter, 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 {text} = filter; 112 let {text} = filter;
116 if (Filter.regexpRegExp.test(text)) 113 if (Filter.regexpRegExp.test(text))
117 return result; 114 return result;
118 115
119 // Remove options 116 // Remove options
120 let match = Filter.optionsRegExp.exec(text); 117 let match = Filter.optionsRegExp.exec(text);
(...skipping 20 matching lines...) Expand all
141 let count = typeof filters != "undefined" ? filters.length : 0; 138 let count = typeof filters != "undefined" ? filters.length : 0;
142 if (count < resultCount || 139 if (count < resultCount ||
143 (count == resultCount && candidate.length > resultLength)) 140 (count == resultCount && candidate.length > resultLength))
144 { 141 {
145 result = candidate; 142 result = candidate;
146 resultCount = count; 143 resultCount = count;
147 resultLength = candidate.length; 144 resultLength = candidate.length;
148 } 145 }
149 } 146 }
150 return result; 147 return result;
151 }, 148 }
152 149
153 /** 150 /**
154 * Checks whether a particular filter is being matched against. 151 * Checks whether a particular filter is being matched against.
155 * @param {RegExpFilter} filter 152 * @param {RegExpFilter} filter
156 * @return {boolean} 153 * @returns {boolean}
157 */ 154 */
158 hasFilter(filter) 155 hasFilter(filter)
159 { 156 {
160 return this.keywordByFilter.has(filter); 157 return this.keywordByFilter.has(filter);
161 }, 158 }
162 159
163 /** 160 /**
164 * Returns the keyword used for a filter, null for unknown filters. 161 * Returns the keyword used for a filter, null for unknown filters.
165 * @param {RegExpFilter} filter 162 * @param {RegExpFilter} filter
166 * @return {?string} 163 * @returns {?string}
167 */ 164 */
168 getKeywordForFilter(filter) 165 getKeywordForFilter(filter)
169 { 166 {
170 let keyword = this.keywordByFilter.get(filter); 167 let keyword = this.keywordByFilter.get(filter);
171 return typeof keyword != "undefined" ? keyword : null; 168 return typeof keyword != "undefined" ? keyword : null;
172 }, 169 }
173 170
174 /** 171 /**
175 * Checks whether the entries for a particular keyword match a URL 172 * Checks whether the entries for a particular keyword match a URL
176 * @param {string} keyword 173 * @param {string} keyword
177 * @param {string} location 174 * @param {string} location
178 * @param {number} typeMask 175 * @param {number} typeMask
179 * @param {string} docDomain 176 * @param {string} [docDomain]
180 * @param {boolean} thirdParty 177 * @param {boolean} [thirdParty]
181 * @param {string} sitekey 178 * @param {string} [sitekey]
182 * @param {boolean} specificOnly 179 * @param {boolean} [specificOnly]
183 * @return {?Filter} 180 * @returns {?Filter}
184 */ 181 */
185 _checkEntryMatch(keyword, location, typeMask, docDomain, thirdParty, sitekey, 182 _checkEntryMatch(keyword, location, typeMask, docDomain, thirdParty, sitekey,
186 specificOnly) 183 specificOnly)
187 { 184 {
188 let list = this.filterByKeyword.get(keyword); 185 let list = this.filterByKeyword.get(keyword);
189 if (typeof list == "undefined") 186 if (typeof list == "undefined")
190 return null; 187 return null;
191 for (let i = 0; i < list.length; i++) 188 for (let i = 0; i < list.length; i++)
192 { 189 {
193 let filter = list[i]; 190 let filter = list[i];
194 191
195 if (specificOnly && filter.isGeneric() && 192 if (specificOnly && filter.isGeneric() &&
196 !(filter instanceof WhitelistFilter)) 193 !(filter instanceof WhitelistFilter))
197 continue; 194 continue;
198 195
199 if (filter.matches(location, typeMask, docDomain, thirdParty, sitekey)) 196 if (filter.matches(location, typeMask, docDomain, thirdParty, sitekey))
200 return filter; 197 return filter;
201 } 198 }
202 return null; 199 return null;
203 }, 200 }
204 201
205 /** 202 /**
206 * Tests whether the URL matches any of the known filters 203 * Tests whether the URL matches any of the known filters
207 * @param {string} location 204 * @param {string} location
208 * URL to be tested 205 * URL to be tested
209 * @param {number} typeMask 206 * @param {number} typeMask
210 * bitmask of content / request types to match 207 * bitmask of content / request types to match
211 * @param {string} docDomain 208 * @param {string} [docDomain]
212 * domain name of the document that loads the URL 209 * domain name of the document that loads the URL
213 * @param {boolean} thirdParty 210 * @param {boolean} [thirdParty]
214 * should be true if the URL is a third-party request 211 * should be true if the URL is a third-party request
215 * @param {string} sitekey 212 * @param {string} [sitekey]
216 * public key provided by the document 213 * public key provided by the document
217 * @param {boolean} specificOnly 214 * @param {boolean} [specificOnly]
218 * should be true if generic matches should be ignored 215 * should be <code>true</code> if generic matches should be ignored
219 * @return {?RegExpFilter} 216 * @returns {?RegExpFilter}
220 * matching filter or null 217 * matching filter or <code>null</code>
221 */ 218 */
222 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly) 219 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly)
223 { 220 {
224 let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g); 221 let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
225 if (candidates === null) 222 if (candidates === null)
226 candidates = []; 223 candidates = [];
227 candidates.push(""); 224 candidates.push("");
228 for (let i = 0, l = candidates.length; i < l; i++) 225 for (let i = 0, l = candidates.length; i < l; i++)
229 { 226 {
230 let result = this._checkEntryMatch(candidates[i], location, typeMask, 227 let result = this._checkEntryMatch(candidates[i], location, typeMask,
231 docDomain, thirdParty, sitekey, 228 docDomain, thirdParty, sitekey,
232 specificOnly); 229 specificOnly);
233 if (result) 230 if (result)
234 return result; 231 return result;
235 } 232 }
236 233
237 return null; 234 return null;
238 } 235 }
239 }; 236 }
240 237
241 /** 238 exports.Matcher = Matcher;
242 * Combines a matcher for blocking and exception rules, automatically sorts
243 * rules into two Matcher instances.
244 * @constructor
245 * @augments Matcher
246 */
247 function CombinedMatcher()
248 {
249 this.blacklist = new Matcher();
250 this.whitelist = new Matcher();
251 this.resultCache = new Map();
252 }
253 exports.CombinedMatcher = CombinedMatcher;
254 239
255 /**
256 * Maximal number of matching cache entries to be kept
257 * @type {number}
258 */
259 CombinedMatcher.maxCacheEntries = 1000;
260 240
261 CombinedMatcher.prototype = 241
242 class CombinedMatcher
262 { 243 {
263 /** 244 /**
264 * Matcher for blocking rules. 245 * Combines a matcher for blocking and exception rules, automatically sorts
265 * @type {Matcher} 246 * rules into two Matcher instances.
247 * @augments Matcher
266 */ 248 */
267 blacklist: null, 249 constructor()
250 {
251 /**
252 * Maximal number of matching cache entries to be kept
253 * @type {number}
254 */
255 this.maxCacheEntries = 1000;
268 256
269 /** 257 /**
270 * Matcher for exception rules. 258 * Matcher for blocking rules.
271 * @type {Matcher} 259 * @type {Matcher}
272 */ 260 */
273 whitelist: null, 261 this.blacklist = new Matcher();
274 262
275 /** 263 /**
276 * Lookup table of previous matchesAny results 264 * Matcher for exception rules.
277 * @type {Map.<string,Filter>} 265 * @type {Matcher}
278 */ 266 */
279 resultCache: null, 267 this.whitelist = new Matcher();
268
269 /**
270 * Lookup table of previous matchesAny results
271 * @type {Map.<string,Filter>}
272 */
273 this.resultCache = new Map();
274 }
280 275
281 /** 276 /**
282 * @see Matcher#clear 277 * @see Matcher#clear
283 */ 278 */
284 clear() 279 clear()
285 { 280 {
286 this.blacklist.clear(); 281 this.blacklist.clear();
287 this.whitelist.clear(); 282 this.whitelist.clear();
288 this.resultCache.clear(); 283 this.resultCache.clear();
289 }, 284 }
290 285
291 /** 286 /**
292 * @see Matcher#add 287 * @see Matcher#add
293 * @param {Filter} filter 288 * @param {Filter} filter
294 */ 289 */
295 add(filter) 290 add(filter)
296 { 291 {
297 if (filter instanceof WhitelistFilter) 292 if (filter instanceof WhitelistFilter)
298 this.whitelist.add(filter); 293 this.whitelist.add(filter);
299 else 294 else
300 this.blacklist.add(filter); 295 this.blacklist.add(filter);
301 296
302 this.resultCache.clear(); 297 this.resultCache.clear();
303 }, 298 }
304 299
305 /** 300 /**
306 * @see Matcher#remove 301 * @see Matcher#remove
307 * @param {Filter} filter 302 * @param {Filter} filter
308 */ 303 */
309 remove(filter) 304 remove(filter)
310 { 305 {
311 if (filter instanceof WhitelistFilter) 306 if (filter instanceof WhitelistFilter)
312 this.whitelist.remove(filter); 307 this.whitelist.remove(filter);
313 else 308 else
314 this.blacklist.remove(filter); 309 this.blacklist.remove(filter);
315 310
316 this.resultCache.clear(); 311 this.resultCache.clear();
317 }, 312 }
318 313
319 /** 314 /**
320 * @see Matcher#findKeyword 315 * @see Matcher#findKeyword
321 * @param {Filter} filter 316 * @param {Filter} filter
322 * @return {string} keyword 317 * @returns {string} keyword
323 */ 318 */
324 findKeyword(filter) 319 findKeyword(filter)
325 { 320 {
326 if (filter instanceof WhitelistFilter) 321 if (filter instanceof WhitelistFilter)
327 return this.whitelist.findKeyword(filter); 322 return this.whitelist.findKeyword(filter);
328 return this.blacklist.findKeyword(filter); 323 return this.blacklist.findKeyword(filter);
329 }, 324 }
330 325
331 /** 326 /**
332 * @see Matcher#hasFilter 327 * @see Matcher#hasFilter
333 * @param {Filter} filter 328 * @param {Filter} filter
334 * @return {boolean} 329 * @returns {boolean}
335 */ 330 */
336 hasFilter(filter) 331 hasFilter(filter)
337 { 332 {
338 if (filter instanceof WhitelistFilter) 333 if (filter instanceof WhitelistFilter)
339 return this.whitelist.hasFilter(filter); 334 return this.whitelist.hasFilter(filter);
340 return this.blacklist.hasFilter(filter); 335 return this.blacklist.hasFilter(filter);
341 }, 336 }
342 337
343 /** 338 /**
344 * @see Matcher#getKeywordForFilter 339 * @see Matcher#getKeywordForFilter
345 * @param {Filter} filter 340 * @param {Filter} filter
346 * @return {string} keyword 341 * @returns {string} keyword
347 */ 342 */
348 getKeywordForFilter(filter) 343 getKeywordForFilter(filter)
349 { 344 {
350 if (filter instanceof WhitelistFilter) 345 if (filter instanceof WhitelistFilter)
351 return this.whitelist.getKeywordForFilter(filter); 346 return this.whitelist.getKeywordForFilter(filter);
352 return this.blacklist.getKeywordForFilter(filter); 347 return this.blacklist.getKeywordForFilter(filter);
353 }, 348 }
354 349
355 /** 350 /**
356 * Checks whether a particular filter is slow 351 * Checks whether a particular filter is slow
357 * @param {RegExpFilter} filter 352 * @param {RegExpFilter} filter
358 * @return {boolean} 353 * @returns {boolean}
359 */ 354 */
360 isSlowFilter(filter) 355 isSlowFilter(filter)
361 { 356 {
362 let matcher = ( 357 let matcher = (
363 filter instanceof WhitelistFilter ? this.whitelist : this.blacklist 358 filter instanceof WhitelistFilter ? this.whitelist : this.blacklist
364 ); 359 );
365 if (matcher.hasFilter(filter)) 360 if (matcher.hasFilter(filter))
366 return !matcher.getKeywordForFilter(filter); 361 return !matcher.getKeywordForFilter(filter);
367 return !matcher.findKeyword(filter); 362 return !matcher.findKeyword(filter);
368 }, 363 }
369 364
370 /** 365 /**
371 * Optimized filter matching testing both whitelist and blacklist matchers 366 * Optimized filter matching testing both whitelist and blacklist matchers
372 * simultaneously. For parameters see 367 * simultaneously. For parameters see
373 {@link Matcher#matchesAny Matcher.matchesAny()}. 368 {@link Matcher#matchesAny Matcher.matchesAny()}.
374 * @see Matcher#matchesAny 369 * @see Matcher#matchesAny
375 * @inheritdoc 370 * @inheritdoc
376 */ 371 */
377 matchesAnyInternal(location, typeMask, docDomain, thirdParty, sitekey, 372 matchesAnyInternal(location, typeMask, docDomain, thirdParty, sitekey,
378 specificOnly) 373 specificOnly)
(...skipping 14 matching lines...) Expand all
393 return result; 388 return result;
394 if (blacklistHit === null) 389 if (blacklistHit === null)
395 { 390 {
396 blacklistHit = this.blacklist._checkEntryMatch( 391 blacklistHit = this.blacklist._checkEntryMatch(
397 substr, location, typeMask, docDomain, thirdParty, sitekey, 392 substr, location, typeMask, docDomain, thirdParty, sitekey,
398 specificOnly 393 specificOnly
399 ); 394 );
400 } 395 }
401 } 396 }
402 return blacklistHit; 397 return blacklistHit;
403 }, 398 }
404 399
405 /** 400 /**
406 * @see Matcher#matchesAny 401 * @see Matcher#matchesAny
407 * @inheritdoc 402 * @inheritdoc
408 */ 403 */
409 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly) 404 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly)
410 { 405 {
411 let key = location + " " + typeMask + " " + docDomain + " " + thirdParty + 406 let key = location + " " + typeMask + " " + docDomain + " " + thirdParty +
412 " " + sitekey + " " + specificOnly; 407 " " + sitekey + " " + specificOnly;
413 408
414 let result = this.resultCache.get(key); 409 let result = this.resultCache.get(key);
415 if (typeof result != "undefined") 410 if (typeof result != "undefined")
416 return result; 411 return result;
417 412
418 result = this.matchesAnyInternal(location, typeMask, docDomain, 413 result = this.matchesAnyInternal(location, typeMask, docDomain,
419 thirdParty, sitekey, specificOnly); 414 thirdParty, sitekey, specificOnly);
420 415
421 if (this.resultCache.size >= CombinedMatcher.maxCacheEntries) 416 if (this.resultCache.size >= CombinedMatcher.maxCacheEntries)
422 this.resultCache.clear(); 417 this.resultCache.clear();
423 418
424 this.resultCache.set(key, result); 419 this.resultCache.set(key, result);
425 420
426 return result; 421 return result;
427 } 422 }
428 }; 423 }
424
425 exports.CombinedMatcher = CombinedMatcher;
429 426
430 /** 427 /**
431 * Shared CombinedMatcher instance that should usually be used. 428 * Shared CombinedMatcher instance that should usually be used.
432 * @type {CombinedMatcher} 429 * @type {CombinedMatcher}
433 */ 430 */
434 exports.defaultMatcher = new CombinedMatcher(); 431 exports.defaultMatcher = new CombinedMatcher();
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