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: Created Aug. 30, 2018, 3:53 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 class Matcher
28 * Blacklist/whitelist filter matching
29 * @constructor
30 */
31 function Matcher()
32 { 28 {
33 this.clear(); 29 /**
Manish Jethani 2018/08/31 13:01:34 I think this rather sounds like the description of
Jon Sonesen 2018/09/02 16:43:20 Done.
34 } 30 * Blacklist/whitelist filter matching
35 exports.Matcher = Matcher; 31 */
32 constructor()
33 {
34 /**
35 * Lookup table for filters by their associated keyword
36 * @type {Map.<string,(Filter|Filter[])>}
37 */
38 this.filterByKeyword = null;
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 = null;
42 filterByKeyword: null, 45 this.clear();
Manish Jethani 2018/08/31 13:01:35 Let's leave a line before this.clear(). Actually
Jon Sonesen 2018/09/02 16:43:20 Done.
43 46 }
44 /**
45 * Lookup table for keywords by the filter
46 * @type {Map.<Filter,string>}
47 */
48 keywordByFilter: null,
49 47
50 /** 48 /**
51 * Removes all known filters 49 * Removes all known filters
52 */ 50 */
53 clear() 51 clear()
54 { 52 {
55 this.filterByKeyword = new Map(); 53 this.filterByKeyword = new Map();
Manish Jethani 2018/08/31 13:01:35 Since these are Map objects now, we can just call
Jon Sonesen 2018/09/02 16:43:19 Done.
56 this.keywordByFilter = new Map(); 54 this.keywordByFilter = new Map();
57 }, 55 }
58 56
59 /** 57 /**
60 * Adds a filter to the matcher 58 * Adds a filter to the matcher
61 * @param {RegExpFilter} filter 59 * @param {RegExpFilter} filter
62 */ 60 */
63 add(filter) 61 add(filter)
64 { 62 {
65 if (this.keywordByFilter.has(filter)) 63 if (this.keywordByFilter.has(filter))
66 return; 64 return;
67 65
68 // Look for a suitable keyword 66 // Look for a suitable keyword
69 let keyword = this.findKeyword(filter); 67 let keyword = this.findKeyword(filter);
70 let oldEntry = this.filterByKeyword.get(keyword); 68 let oldEntry = this.filterByKeyword.get(keyword);
71 if (typeof oldEntry == "undefined") 69 if (typeof oldEntry == "undefined")
72 this.filterByKeyword.set(keyword, filter); 70 this.filterByKeyword.set(keyword, filter);
73 else if (oldEntry.length == 1) 71 else if (oldEntry.length == 1)
74 this.filterByKeyword.set(keyword, [oldEntry, filter]); 72 this.filterByKeyword.set(keyword, [oldEntry, filter]);
75 else 73 else
76 oldEntry.push(filter); 74 oldEntry.push(filter);
77 this.keywordByFilter.set(filter, keyword); 75 this.keywordByFilter.set(filter, keyword);
78 }, 76 }
79 77
80 /** 78 /**
81 * Removes a filter from the matcher 79 * Removes a filter from the matcher
82 * @param {RegExpFilter} filter 80 * @param {RegExpFilter} filter
83 */ 81 */
84 remove(filter) 82 remove(filter)
85 { 83 {
86 let keyword = this.keywordByFilter.get(filter); 84 let keyword = this.keywordByFilter.get(filter);
87 if (typeof keyword == "undefined") 85 if (typeof keyword == "undefined")
88 return; 86 return;
89 87
90 let list = this.filterByKeyword.get(keyword); 88 let list = this.filterByKeyword.get(keyword);
91 if (list.length <= 1) 89 if (list.length <= 1)
92 this.filterByKeyword.delete(keyword); 90 this.filterByKeyword.delete(keyword);
93 else 91 else
94 { 92 {
95 let index = list.indexOf(filter); 93 let index = list.indexOf(filter);
96 if (index >= 0) 94 if (index >= 0)
97 { 95 {
98 list.splice(index, 1); 96 list.splice(index, 1);
99 if (list.length == 1) 97 if (list.length == 1)
100 this.filterByKeyword.set(keyword, list[0]); 98 this.filterByKeyword.set(keyword, list[0]);
101 } 99 }
102 } 100 }
103 101
104 this.keywordByFilter.delete(filter); 102 this.keywordByFilter.delete(filter);
105 }, 103 }
106 104
107 /** 105 /**
108 * Chooses a keyword to be associated with the filter 106 * Chooses a keyword to be associated with the filter
109 * @param {Filter} filter 107 * @param {Filter} filter
110 * @return {string} keyword or an empty string if no keyword could be found 108 * @return {string} keyword or an empty string if no keyword could be found
Manish Jethani 2018/08/31 13:01:34 Let's s/@return/@returns/ here and throughout the
Jon Sonesen 2018/09/02 16:43:20 Done.
111 */ 109 */
112 findKeyword(filter) 110 findKeyword(filter)
113 { 111 {
114 let result = ""; 112 let result = "";
115 let {text} = filter; 113 let {text} = filter;
116 if (Filter.regexpRegExp.test(text)) 114 if (Filter.regexpRegExp.test(text))
117 return result; 115 return result;
118 116
119 // Remove options 117 // Remove options
120 let match = Filter.optionsRegExp.exec(text); 118 let match = Filter.optionsRegExp.exec(text);
(...skipping 20 matching lines...) Expand all
141 let count = typeof filters != "undefined" ? filters.length : 0; 139 let count = typeof filters != "undefined" ? filters.length : 0;
142 if (count < resultCount || 140 if (count < resultCount ||
143 (count == resultCount && candidate.length > resultLength)) 141 (count == resultCount && candidate.length > resultLength))
144 { 142 {
145 result = candidate; 143 result = candidate;
146 resultCount = count; 144 resultCount = count;
147 resultLength = candidate.length; 145 resultLength = candidate.length;
148 } 146 }
149 } 147 }
150 return result; 148 return result;
151 }, 149 }
152 150
153 /** 151 /**
154 * Checks whether a particular filter is being matched against. 152 * Checks whether a particular filter is being matched against.
155 * @param {RegExpFilter} filter 153 * @param {RegExpFilter} filter
156 * @return {boolean} 154 * @return {boolean}
157 */ 155 */
158 hasFilter(filter) 156 hasFilter(filter)
159 { 157 {
160 return this.keywordByFilter.has(filter); 158 return this.keywordByFilter.has(filter);
161 }, 159 }
162 160
163 /** 161 /**
164 * Returns the keyword used for a filter, null for unknown filters. 162 * Returns the keyword used for a filter, null for unknown filters.
165 * @param {RegExpFilter} filter 163 * @param {RegExpFilter} filter
166 * @return {?string} 164 * @return {?string}
167 */ 165 */
168 getKeywordForFilter(filter) 166 getKeywordForFilter(filter)
169 { 167 {
170 let keyword = this.keywordByFilter.get(filter); 168 let keyword = this.keywordByFilter.get(filter);
171 return typeof keyword != "undefined" ? keyword : null; 169 return typeof keyword != "undefined" ? keyword : null;
172 }, 170 }
173 171
174 /** 172 /**
175 * Checks whether the entries for a particular keyword match a URL 173 * Checks whether the entries for a particular keyword match a URL
176 * @param {string} keyword 174 * @param {string} keyword
177 * @param {string} location 175 * @param {string} location
178 * @param {number} typeMask 176 * @param {number} typeMask
179 * @param {string} docDomain 177 * @param {string} docDomain
Manish Jethani 2018/08/31 13:01:34 docDomain, thirdParty, sitekey, and specificOnly a
Jon Sonesen 2018/09/02 16:43:20 Done.
180 * @param {boolean} thirdParty 178 * @param {boolean} thirdParty
181 * @param {string} sitekey 179 * @param {string} sitekey
182 * @param {boolean} specificOnly 180 * @param {boolean} specificOnly
183 * @return {?Filter} 181 * @return {?Filter}
184 */ 182 */
185 _checkEntryMatch(keyword, location, typeMask, docDomain, thirdParty, sitekey, 183 _checkEntryMatch(keyword, location, typeMask, docDomain, thirdParty, sitekey,
186 specificOnly) 184 specificOnly)
187 { 185 {
188 let list = this.filterByKeyword.get(keyword); 186 let list = this.filterByKeyword.get(keyword);
189 if (typeof list == "undefined") 187 if (typeof list == "undefined")
190 return null; 188 return null;
191 for (let i = 0; i < list.length; i++) 189 for (let i = 0; i < list.length; i++)
192 { 190 {
193 let filter = list[i]; 191 let filter = list[i];
194 192
195 if (specificOnly && filter.isGeneric() && 193 if (specificOnly && filter.isGeneric() &&
196 !(filter instanceof WhitelistFilter)) 194 !(filter instanceof WhitelistFilter))
197 continue; 195 continue;
198 196
199 if (filter.matches(location, typeMask, docDomain, thirdParty, sitekey)) 197 if (filter.matches(location, typeMask, docDomain, thirdParty, sitekey))
200 return filter; 198 return filter;
201 } 199 }
202 return null; 200 return null;
203 }, 201 }
204 202
205 /** 203 /**
206 * Tests whether the URL matches any of the known filters 204 * Tests whether the URL matches any of the known filters
207 * @param {string} location 205 * @param {string} location
208 * URL to be tested 206 * URL to be tested
209 * @param {number} typeMask 207 * @param {number} typeMask
210 * bitmask of content / request types to match 208 * bitmask of content / request types to match
211 * @param {string} docDomain 209 * @param {string} docDomain
Manish Jethani 2018/08/31 13:01:35 See comment above about optional parameters.
Jon Sonesen 2018/09/02 16:43:20 Done.
212 * domain name of the document that loads the URL 210 * domain name of the document that loads the URL
213 * @param {boolean} thirdParty 211 * @param {boolean} thirdParty
214 * should be true if the URL is a third-party request 212 * should be true if the URL is a third-party request
215 * @param {string} sitekey 213 * @param {string} sitekey
216 * public key provided by the document 214 * public key provided by the document
217 * @param {boolean} specificOnly 215 * @param {boolean} specificOnly
218 * should be true if generic matches should be ignored 216 * should be true if generic matches should be ignored
Manish Jethani 2018/08/31 13:01:35 Let's make this <code>true</code>.
Jon Sonesen 2018/09/02 16:43:20 Done.
219 * @return {?RegExpFilter} 217 * @return {?RegExpFilter}
220 * matching filter or null 218 * matching filter or null
Manish Jethani 2018/08/31 13:01:34 Let's make this <code>null</code>.
Jon Sonesen 2018/09/02 16:43:19 Done.
221 */ 219 */
222 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly) 220 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly)
223 { 221 {
224 let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g); 222 let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
225 if (candidates === null) 223 if (candidates === null)
226 candidates = []; 224 candidates = [];
227 candidates.push(""); 225 candidates.push("");
228 for (let i = 0, l = candidates.length; i < l; i++) 226 for (let i = 0, l = candidates.length; i < l; i++)
229 { 227 {
230 let result = this._checkEntryMatch(candidates[i], location, typeMask, 228 let result = this._checkEntryMatch(candidates[i], location, typeMask,
231 docDomain, thirdParty, sitekey, 229 docDomain, thirdParty, sitekey,
232 specificOnly); 230 specificOnly);
233 if (result) 231 if (result)
234 return result; 232 return result;
235 } 233 }
236 234
237 return null; 235 return null;
238 } 236 }
239 }; 237 }
Manish Jethani 2018/08/31 13:01:34 Let's leave a blank line after the class definitio
Jon Sonesen 2018/09/02 16:43:20 Done.
238 exports.Matcher = Matcher;
240 239
241 /**
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 240
255 /**
256 * Maximal number of matching cache entries to be kept
257 * @type {number}
258 */
259 CombinedMatcher.maxCacheEntries = 1000;
260 241
261 CombinedMatcher.prototype = 242 class CombinedMatcher
262 { 243 {
263 /** 244 /**
264 * Matcher for blocking rules. 245 * Combines a matcher for blocking and exception rules, automatically sorts
Manish Jethani 2018/08/31 13:01:34 Let's move this to above the class definition.
265 * @type {Matcher} 246 * rules into two Matcher instances.
Manish Jethani 2018/08/31 13:01:35 Let's change `Matcher` here to `{@link Matcher}` s
247 * @augments Matcher
Manish Jethani 2018/08/31 13:01:34 Let's remove @arguments here, it's outdated.
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
Manish Jethani 2018/08/31 13:01:35 Let's make this `{@link Matcher#matchesAny}`
Jon Sonesen 2018/09/02 16:43:20 Done.
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 * @return {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 * @return {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 * @return {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 * @return {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 }
Manish Jethani 2018/08/31 13:01:34 Let's leave a blank line here.
Jon Sonesen 2018/09/02 16:43:20 Done.
424 exports.CombinedMatcher = CombinedMatcher;
429 425
430 /** 426 /**
431 * Shared CombinedMatcher instance that should usually be used. 427 * Shared CombinedMatcher instance that should usually be used.
Manish Jethani 2018/08/31 13:01:35 Let's make this `{@link CombinedMatcher}`
Jon Sonesen 2018/09/02 16:43:20 Done.
432 * @type {CombinedMatcher} 428 * @type {CombinedMatcher}
433 */ 429 */
434 exports.defaultMatcher = new CombinedMatcher(); 430 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