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

Delta Between Two Patch Sets: lib/elemHide.js

Issue 29773570: Issue 6652 - Implement fast selector lookups for unknown domains (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Left Patch Set: Limit size of generic-friendly domains set Created May 11, 2018, 10:08 a.m.
Right Patch Set: Avoid unnecessary Array.concat Created May 23, 2018, 12:22 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | test/elemHide.js » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
71 */ 71 */
72 let genericExceptions = new Map(); 72 let genericExceptions = new Map();
73 73
74 /** 74 /**
75 * List of selectors that apply on any unknown domain 75 * List of selectors that apply on any unknown domain
76 * @type {?string[]} 76 * @type {?string[]}
77 */ 77 */
78 let conditionalGenericSelectors = null; 78 let conditionalGenericSelectors = null;
79 79
80 /** 80 /**
81 * Domains that are known not to be specifically excluded from any generic 81 * Adds a filter to the lookup table of filters by domain.
82 * filters 82 * @param {Filter} filter
83 * @type {Set.<string>} 83 */
84 */ 84 function addToFiltersByDomain(filter)
85 let genericFriendlyDomains = new Set(); 85 {
86 let domains = filter.domains || defaultDomains;
87 if (filter instanceof ElemHideException)
88 {
89 for (let domain of domains.keys())
90 {
91 // Add an entry for each domain, but without any filters. This makes
92 // the domain "known" and helps us avoid certain optimizations that
93 // would otherwise yield incorrect results.
94 if (domain != "" && !filtersByDomain.has(domain))
95 filtersByDomain.set(domain, null);
96 }
97 }
98 else
99 {
100 for (let [domain, isIncluded] of domains)
101 {
102 // There's no need to note that a filter is generically disabled.
103 if (!isIncluded && domain == "")
104 continue;
105
106 let filters = filtersByDomain.get(domain);
107 if (!filters)
108 filtersByDomain.set(domain, filters = new Map());
109 filters.set(filter, isIncluded);
110 }
111 }
112 }
86 113
87 /** 114 /**
88 * Returns a list of domain-specific filters matching a domain 115 * Returns a list of domain-specific filters matching a domain
89 * @param {string} [domain] 116 * @param {string} [domain]
90 * @returns {Array.<?Map.<Filter,boolean>>} 117 * @returns {Array.<?Map.<Filter,boolean>>}
91 */ 118 */
92 function getSpecificFiltersForDomain(domain) 119 function getSpecificFiltersForDomain(domain)
93 { 120 {
94 let filtersList = []; 121 let filtersList = [];
95 122
96 if (domain) 123 if (domain)
97 domain = domain.toUpperCase(); 124 domain = domain.toUpperCase();
98 125
99 while (domain) 126 while (domain)
100 { 127 {
101 // Note that we also push null values into the list, because
102 // ElemHide.getSelectorsForDomain still needs to know if there are any
103 // entries for the domain.
104 let filters = filtersByDomain.get(domain); 128 let filters = filtersByDomain.get(domain);
105 if (typeof filters != "undefined") 129 if (typeof filters != "undefined")
106 filtersList.push(filters); 130 filtersList.push(filters);
107 131
108 let nextDot = domain.indexOf("."); 132 let nextDot = domain.indexOf(".");
109 domain = nextDot == -1 ? null : domain.substring(nextDot + 1); 133 domain = nextDot == -1 ? null : domain.substring(nextDot + 1);
110 } 134 }
111 135
112 return filtersList; 136 return filtersList;
113 } 137 }
114 138
115 /** 139 /**
116 * Returns a list of selectors from a given list of filters that apply on a 140 * Returns a list of selectors that apply on a domain from a given list of
117 * domain 141 * filters
118 * @param {string} [domain] 142 * @param {string} [domain]
119 * @param {Array.<?Map.<Filter,boolean>>} filtersList 143 * @param {Array.<?Map.<Filter,boolean>>} filtersList
120 * @param {boolean} specificOnly 144 * @param {Set.<Filter>} excludeSet
121 * @returns {string[]} 145 * @returns {string[]}
122 */ 146 */
123 function getConditionalSelectorsForDomain(domain, filtersList, specificOnly) 147 function matchSelectors(domain, filtersList, excludeSet)
124 { 148 {
125 let selectors = []; 149 let matches = [];
126
127 let genericFilters = !specificOnly ? filtersList.pop() : null;
128 let excluded = new Set();
129 150
130 // This code is a performance hot-spot, which is why we've made certain 151 // This code is a performance hot-spot, which is why we've made certain
131 // micro-optimisations. Please be careful before making changes. 152 // micro-optimisations. Please be careful before making changes.
132 for (let i = 0; i < filtersList.length; i++) 153 for (let i = 0; i < filtersList.length; i++)
133 { 154 {
134 if (!filtersList[i]) 155 let filters = filtersList[i];
135 continue; 156 if (filters)
136 157 {
137 for (let [filter, isIncluded] of filtersList[i]) 158 for (let [filter, isIncluded] of filters)
138 {
139 if (!isIncluded)
140 { 159 {
141 excluded.add(filter); 160 if (!isIncluded)
161 {
162 excludeSet.add(filter);
163 }
164 else if ((excludeSet.size == 0 || !excludeSet.has(filter)) &&
165 !exports.ElemHide.getException(filter, domain))
166 {
167 matches.push(filter.selector);
168 }
142 } 169 }
143 else if ((excluded.size == 0 || !excluded.has(filter)) && 170 }
144 !ElemHide.getException(filter, domain)) 171 }
145 { 172
146 selectors.push(filter.selector); 173 return matches;
147 }
148 }
149 }
150
151 if (!genericFilters)
152 return selectors;
153
154 if (genericFriendlyDomains.has(domain))
155 return selectors.concat(getConditionalGenericSelectors());
156
157 let genericSelectors = [];
158
159 for (let filter of genericFilters.keys())
160 {
161 if ((excluded.size == 0 || !excluded.has(filter)) &&
162 !ElemHide.getException(filter, domain))
163 {
164 genericSelectors.push(filter.selector);
165 }
166 }
167
168 // If the number of conditional generic selectors that apply on this domain
169 // is the same as the total number of conditional generic selectors, the
170 // domain is "generic friendly". In that case, we mark it is as such for
171 // faster lookups.
172 if (conditionalGenericSelectors &&
173 genericSelectors.length == conditionalGenericSelectors.length)
174 {
175 if (genericFriendlyDomains.size >= 1000)
176 genericFriendlyDomains.clear();
177
178 genericFriendlyDomains.add(domain);
179 }
180
181 return selectors.concat(genericSelectors);
182 } 174 }
183 175
184 /** 176 /**
185 * Returns a list of selectors that apply on any unknown domain 177 * Returns a list of selectors that apply on any unknown domain
186 * @returns {string[]} 178 * @returns {string[]}
187 */ 179 */
188 function getConditionalGenericSelectors() 180 function getConditionalGenericSelectors()
189 { 181 {
190 if (conditionalGenericSelectors) 182 if (conditionalGenericSelectors)
191 return conditionalGenericSelectors; 183 return conditionalGenericSelectors;
(...skipping 22 matching lines...) Expand all
214 if (!unconditionalSelectors) 206 if (!unconditionalSelectors)
215 unconditionalSelectors = [...filterBySelector.keys()]; 207 unconditionalSelectors = [...filterBySelector.keys()];
216 208
217 return unconditionalSelectors; 209 return unconditionalSelectors;
218 } 210 }
219 211
220 /** 212 /**
221 * Container for element hiding filters 213 * Container for element hiding filters
222 * @class 214 * @class
223 */ 215 */
224 let ElemHide = exports.ElemHide = { 216 exports.ElemHide = {
225 /** 217 /**
226 * Removes all known filters 218 * Removes all known filters
227 */ 219 */
228 clear() 220 clear()
229 { 221 {
230 for (let collection of [filtersByDomain, filterBySelector, 222 for (let collection of [filtersByDomain, filterBySelector,
231 knownFilters, exceptions, 223 knownFilters, exceptions,
232 genericExceptions, genericFriendlyDomains]) 224 genericExceptions])
233 { 225 {
234 collection.clear(); 226 collection.clear();
235 } 227 }
236 unconditionalSelectors = null; 228 unconditionalSelectors = null;
237 conditionalGenericSelectors = null; 229 conditionalGenericSelectors = null;
238 FilterNotifier.emit("elemhideupdate"); 230 FilterNotifier.emit("elemhideupdate");
239 },
240
241 _addToFiltersByDomain(filter)
242 {
243 let domains = filter.domains || defaultDomains;
244 if (filter instanceof ElemHideException)
245 {
246 for (let domain of domains.keys())
247 {
248 // Add an entry for each domain, but without any filters. This makes
249 // the domain "known" and helps us avoid certain optimizations that
250 // would otherwise yield incorrect results.
251 if (domain != "" && !filtersByDomain.has(domain))
252 filtersByDomain.set(domain, null);
253 }
254 }
255 else
256 {
257 for (let [domain, isIncluded] of domains)
258 {
259 // There's no need to note that a filter is generically disabled.
260 if (!isIncluded && domain == "")
261 continue;
262
263 let filters = filtersByDomain.get(domain);
264 if (!filters)
265 filtersByDomain.set(domain, filters = new Map());
266 filters.set(filter, isIncluded);
267 }
268 }
269 }, 231 },
270 232
271 /** 233 /**
272 * Add a new element hiding filter 234 * Add a new element hiding filter
273 * @param {ElemHideBase} filter 235 * @param {ElemHideBase} filter
274 */ 236 */
275 add(filter) 237 add(filter)
276 { 238 {
277 if (knownFilters.has(filter)) 239 if (knownFilters.has(filter))
278 return; 240 return;
279 241
280 conditionalGenericSelectors = null; 242 conditionalGenericSelectors = null;
281 genericFriendlyDomains.clear();
282 243
283 if (filter instanceof ElemHideException) 244 if (filter instanceof ElemHideException)
284 { 245 {
285 let {selector, domains} = filter; 246 let {selector, domains} = filter;
286 247
287 let list = exceptions.get(selector); 248 let list = exceptions.get(selector);
288 if (list) 249 if (list)
289 list.push(filter); 250 list.push(filter);
290 else 251 else
291 exceptions.set(selector, [filter]); 252 exceptions.set(selector, [filter]);
292 253
293 if (domains) 254 if (domains)
294 this._addToFiltersByDomain(filter); 255 addToFiltersByDomain(filter);
295 256
296 if (filter.isGeneric()) 257 if (filter.isGeneric())
297 { 258 {
298 list = genericExceptions.get(selector); 259 list = genericExceptions.get(selector);
299 if (list) 260 if (list)
300 list.push(filter); 261 list.push(filter);
301 else 262 else
302 genericExceptions.set(selector, [filter]); 263 genericExceptions.set(selector, [filter]);
303 } 264 }
304 265
305 // If this is the first exception for a previously unconditionally 266 // If this is the first exception for a previously unconditionally
306 // applied element hiding selector we need to take care to update the 267 // applied element hiding selector we need to take care to update the
307 // lookups. 268 // lookups.
308 let unconditionalFilterForSelector = filterBySelector.get(selector); 269 let unconditionalFilterForSelector = filterBySelector.get(selector);
309 if (unconditionalFilterForSelector) 270 if (unconditionalFilterForSelector)
310 { 271 {
311 this._addToFiltersByDomain(unconditionalFilterForSelector); 272 addToFiltersByDomain(unconditionalFilterForSelector);
312 filterBySelector.delete(selector); 273 filterBySelector.delete(selector);
313 unconditionalSelectors = null; 274 unconditionalSelectors = null;
314 } 275 }
315 } 276 }
316 else if (!(filter.domains || exceptions.has(filter.selector))) 277 else if (!(filter.domains || exceptions.has(filter.selector)))
317 { 278 {
318 // The new filter's selector is unconditionally applied to all domains 279 // The new filter's selector is unconditionally applied to all domains
319 filterBySelector.set(filter.selector, filter); 280 filterBySelector.set(filter.selector, filter);
320 unconditionalSelectors = null; 281 unconditionalSelectors = null;
321 } 282 }
322 else 283 else
323 { 284 {
324 // The new filter's selector only applies to some domains 285 // The new filter's selector only applies to some domains
325 this._addToFiltersByDomain(filter); 286 addToFiltersByDomain(filter);
326 } 287 }
327 288
328 knownFilters.add(filter); 289 knownFilters.add(filter);
329 FilterNotifier.emit("elemhideupdate"); 290 FilterNotifier.emit("elemhideupdate");
330 }, 291 },
331 292
332 /** 293 /**
333 * Removes an element hiding filter 294 * Removes an element hiding filter
334 * @param {ElemHideBase} filter 295 * @param {ElemHideBase} filter
335 */ 296 */
336 remove(filter) 297 remove(filter)
337 { 298 {
338 if (!knownFilters.has(filter)) 299 if (!knownFilters.has(filter))
339 return; 300 return;
340 301
341 conditionalGenericSelectors = null; 302 conditionalGenericSelectors = null;
342 genericFriendlyDomains.clear();
343 303
344 // Whitelisting filters 304 // Whitelisting filters
345 if (filter instanceof ElemHideException) 305 if (filter instanceof ElemHideException)
346 { 306 {
347 let list = exceptions.get(filter.selector); 307 let list = exceptions.get(filter.selector);
348 let index = list.indexOf(filter); 308 let index = list.indexOf(filter);
349 if (index >= 0) 309 if (index >= 0)
350 list.splice(index, 1); 310 list.splice(index, 1);
351 311
352 if (filter.isGeneric()) 312 if (filter.isGeneric())
353 { 313 {
354 list = genericExceptions.get(filter.selector); 314 list = genericExceptions.get(filter.selector);
355 index = list.indexOf(filter); 315 index = list.indexOf(filter);
356 if (index >= 0) 316 if (index >= 0)
357 list.splice(index, 1); 317 list.splice(index, 1);
358 318
319 // It's important to delete the entry here so the selector no longer
320 // appears to have any generic exceptions.
359 if (list.length == 0) 321 if (list.length == 0)
360 genericExceptions.delete(filter.selector); 322 genericExceptions.delete(filter.selector);
361 } 323 }
362 } 324 }
363 // Unconditially applied element hiding filters 325 // Unconditially applied element hiding filters
364 else if (filterBySelector.get(filter.selector) == filter) 326 else if (filterBySelector.get(filter.selector) == filter)
365 { 327 {
366 filterBySelector.delete(filter.selector); 328 filterBySelector.delete(filter.selector);
367 unconditionalSelectors = null; 329 unconditionalSelectors = null;
368 } 330 }
(...skipping 29 matching lines...) Expand all
398 for (let i = list.length - 1; i >= 0; i--) 360 for (let i = list.length - 1; i >= 0; i--)
399 { 361 {
400 if (list[i].isActiveOnDomain(docDomain)) 362 if (list[i].isActiveOnDomain(docDomain))
401 return list[i]; 363 return list[i];
402 } 364 }
403 365
404 return null; 366 return null;
405 }, 367 },
406 368
407 /** 369 /**
408 * Constant used by getSelectorsForDomain to return all selectors applying to
409 * a particular hostname.
410 * @type {number}
411 * @const
412 */
413 ALL_MATCHING: 0,
414
415 /**
416 * Constant used by getSelectorsForDomain to exclude selectors which apply to
417 * all websites without exception.
418 * @type {number}
419 * @const
420 */
421 NO_UNCONDITIONAL: 1,
422
423 /**
424 * Constant used by getSelectorsForDomain to return only selectors for filters
425 * which specifically match the given host name.
426 * @type {number}
427 * @const
428 */
429 SPECIFIC_ONLY: 2,
430
431 /**
432 * Determines from the current filter list which selectors should be applied 370 * Determines from the current filter list which selectors should be applied
433 * on a particular host name. 371 * on a particular host name.
434 * @param {string} domain 372 * @param {string} domain
435 * @param {number} [criteria] 373 * @param {boolean} [specificOnly] true if generic filters should not apply.
436 * One of the following: ElemHide.ALL_MATCHING, ElemHide.NO_UNCONDITIONAL or 374 * @returns {string[]} List of selectors.
437 * ElemHide.SPECIFIC_ONLY.
438 * @returns {string[]}
439 * List of selectors.
440 */ 375 */
441 getSelectorsForDomain(domain, criteria = ElemHide.ALL_MATCHING) 376 getSelectorsForDomain(domain, specificOnly = false)
442 { 377 {
443 let selectors = []; 378 let specificFilters = getSpecificFiltersForDomain(domain);
444 379
445 let specificOnly = criteria >= ElemHide.SPECIFIC_ONLY; 380 // If there are no specific filters (nor any specific exceptions), we can
446 let filtersList = getSpecificFiltersForDomain(domain); 381 // just return the selectors from all the generic filters modulo any
447 382 // generic exceptions.
448 if (filtersList.length > 0) 383 if (specificFilters.length == 0)
449 { 384 {
450 if (!specificOnly) 385 return specificOnly ? [] :
451 filtersList.push(filtersByDomain.get("")); 386 getUnconditionalSelectors()
452 387 .concat(getConditionalGenericSelectors());
453 selectors = getConditionalSelectorsForDomain(domain, filtersList, 388 }
454 specificOnly); 389
455 } 390 let excluded = new Set();
456 else if (!specificOnly) 391 let selectors = matchSelectors(domain, specificFilters, excluded);
457 { 392
458 selectors = getConditionalGenericSelectors(); 393 if (specificOnly)
459 } 394 return selectors;
460 395
461 if (criteria < ElemHide.NO_UNCONDITIONAL) 396 return getUnconditionalSelectors()
462 selectors = getUnconditionalSelectors().concat(selectors); 397 .concat(selectors,
463 398 matchSelectors(domain, [filtersByDomain.get("")],
464 // If the above logic leaves us with a reference to our internal cache of 399 excluded));
465 // selectors, we make a copy here.
466 if (selectors == conditionalGenericSelectors)
467 selectors = selectors.slice();
468
469 return selectors;
470 } 400 }
471 }; 401 };
LEFTRIGHT
« no previous file | test/elemHide.js » ('j') | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld