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: Used cached list for generic-friendly domains Created May 11, 2018, 9:51 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 genericFriendlyDomains.add(domain);
Manish Jethani 2018/05/11 10:00:32 Most domains will fall in this category. We might
Manish Jethani 2018/05/11 10:09:09 Done.
176 }
177
178 return selectors.concat(genericSelectors);
179 } 174 }
180 175
181 /** 176 /**
182 * Returns a list of selectors that apply on any unknown domain 177 * Returns a list of selectors that apply on any unknown domain
183 * @returns {string[]} 178 * @returns {string[]}
184 */ 179 */
185 function getConditionalGenericSelectors() 180 function getConditionalGenericSelectors()
186 { 181 {
187 if (conditionalGenericSelectors) 182 if (conditionalGenericSelectors)
188 return conditionalGenericSelectors; 183 return conditionalGenericSelectors;
(...skipping 22 matching lines...) Expand all
211 if (!unconditionalSelectors) 206 if (!unconditionalSelectors)
212 unconditionalSelectors = [...filterBySelector.keys()]; 207 unconditionalSelectors = [...filterBySelector.keys()];
213 208
214 return unconditionalSelectors; 209 return unconditionalSelectors;
215 } 210 }
216 211
217 /** 212 /**
218 * Container for element hiding filters 213 * Container for element hiding filters
219 * @class 214 * @class
220 */ 215 */
221 let ElemHide = exports.ElemHide = { 216 exports.ElemHide = {
222 /** 217 /**
223 * Removes all known filters 218 * Removes all known filters
224 */ 219 */
225 clear() 220 clear()
226 { 221 {
227 for (let collection of [filtersByDomain, filterBySelector, 222 for (let collection of [filtersByDomain, filterBySelector,
228 knownFilters, exceptions, 223 knownFilters, exceptions,
229 genericExceptions, genericFriendlyDomains]) 224 genericExceptions])
230 { 225 {
231 collection.clear(); 226 collection.clear();
232 } 227 }
233 unconditionalSelectors = null; 228 unconditionalSelectors = null;
234 conditionalGenericSelectors = null; 229 conditionalGenericSelectors = null;
235 FilterNotifier.emit("elemhideupdate"); 230 FilterNotifier.emit("elemhideupdate");
236 },
237
238 _addToFiltersByDomain(filter)
239 {
240 let domains = filter.domains || defaultDomains;
241 if (filter instanceof ElemHideException)
242 {
243 for (let domain of domains.keys())
244 {
245 // Add an entry for each domain, but without any filters. This makes
246 // the domain "known" and helps us avoid certain optimizations that
247 // would otherwise yield incorrect results.
248 if (domain != "" && !filtersByDomain.has(domain))
249 filtersByDomain.set(domain, null);
250 }
251 }
252 else
253 {
254 for (let [domain, isIncluded] of domains)
255 {
256 // There's no need to note that a filter is generically disabled.
257 if (!isIncluded && domain == "")
258 continue;
259
260 let filters = filtersByDomain.get(domain);
261 if (!filters)
262 filtersByDomain.set(domain, filters = new Map());
263 filters.set(filter, isIncluded);
264 }
265 }
266 }, 231 },
267 232
268 /** 233 /**
269 * Add a new element hiding filter 234 * Add a new element hiding filter
270 * @param {ElemHideBase} filter 235 * @param {ElemHideBase} filter
271 */ 236 */
272 add(filter) 237 add(filter)
273 { 238 {
274 if (knownFilters.has(filter)) 239 if (knownFilters.has(filter))
275 return; 240 return;
276 241
277 conditionalGenericSelectors = null; 242 conditionalGenericSelectors = null;
278 genericFriendlyDomains.clear();
279 243
280 if (filter instanceof ElemHideException) 244 if (filter instanceof ElemHideException)
281 { 245 {
282 let {selector, domains} = filter; 246 let {selector, domains} = filter;
283 247
284 let list = exceptions.get(selector); 248 let list = exceptions.get(selector);
285 if (list) 249 if (list)
286 list.push(filter); 250 list.push(filter);
287 else 251 else
288 exceptions.set(selector, [filter]); 252 exceptions.set(selector, [filter]);
289 253
290 if (domains) 254 if (domains)
291 this._addToFiltersByDomain(filter); 255 addToFiltersByDomain(filter);
292 256
293 if (filter.isGeneric()) 257 if (filter.isGeneric())
294 { 258 {
295 list = genericExceptions.get(selector); 259 list = genericExceptions.get(selector);
296 if (list) 260 if (list)
297 list.push(filter); 261 list.push(filter);
298 else 262 else
299 genericExceptions.set(selector, [filter]); 263 genericExceptions.set(selector, [filter]);
300 } 264 }
301 265
302 // If this is the first exception for a previously unconditionally 266 // If this is the first exception for a previously unconditionally
303 // 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
304 // lookups. 268 // lookups.
305 let unconditionalFilterForSelector = filterBySelector.get(selector); 269 let unconditionalFilterForSelector = filterBySelector.get(selector);
306 if (unconditionalFilterForSelector) 270 if (unconditionalFilterForSelector)
307 { 271 {
308 this._addToFiltersByDomain(unconditionalFilterForSelector); 272 addToFiltersByDomain(unconditionalFilterForSelector);
309 filterBySelector.delete(selector); 273 filterBySelector.delete(selector);
310 unconditionalSelectors = null; 274 unconditionalSelectors = null;
311 } 275 }
312 } 276 }
313 else if (!(filter.domains || exceptions.has(filter.selector))) 277 else if (!(filter.domains || exceptions.has(filter.selector)))
314 { 278 {
315 // The new filter's selector is unconditionally applied to all domains 279 // The new filter's selector is unconditionally applied to all domains
316 filterBySelector.set(filter.selector, filter); 280 filterBySelector.set(filter.selector, filter);
317 unconditionalSelectors = null; 281 unconditionalSelectors = null;
318 } 282 }
319 else 283 else
320 { 284 {
321 // The new filter's selector only applies to some domains 285 // The new filter's selector only applies to some domains
322 this._addToFiltersByDomain(filter); 286 addToFiltersByDomain(filter);
323 } 287 }
324 288
325 knownFilters.add(filter); 289 knownFilters.add(filter);
326 FilterNotifier.emit("elemhideupdate"); 290 FilterNotifier.emit("elemhideupdate");
327 }, 291 },
328 292
329 /** 293 /**
330 * Removes an element hiding filter 294 * Removes an element hiding filter
331 * @param {ElemHideBase} filter 295 * @param {ElemHideBase} filter
332 */ 296 */
333 remove(filter) 297 remove(filter)
334 { 298 {
335 if (!knownFilters.has(filter)) 299 if (!knownFilters.has(filter))
336 return; 300 return;
337 301
338 conditionalGenericSelectors = null; 302 conditionalGenericSelectors = null;
339 genericFriendlyDomains.clear();
340 303
341 // Whitelisting filters 304 // Whitelisting filters
342 if (filter instanceof ElemHideException) 305 if (filter instanceof ElemHideException)
343 { 306 {
344 let list = exceptions.get(filter.selector); 307 let list = exceptions.get(filter.selector);
345 let index = list.indexOf(filter); 308 let index = list.indexOf(filter);
346 if (index >= 0) 309 if (index >= 0)
347 list.splice(index, 1); 310 list.splice(index, 1);
348 311
349 if (filter.isGeneric()) 312 if (filter.isGeneric())
350 { 313 {
351 list = genericExceptions.get(filter.selector); 314 list = genericExceptions.get(filter.selector);
352 index = list.indexOf(filter); 315 index = list.indexOf(filter);
353 if (index >= 0) 316 if (index >= 0)
354 list.splice(index, 1); 317 list.splice(index, 1);
355 318
319 // It's important to delete the entry here so the selector no longer
320 // appears to have any generic exceptions.
356 if (list.length == 0) 321 if (list.length == 0)
357 genericExceptions.delete(filter.selector); 322 genericExceptions.delete(filter.selector);
358 } 323 }
359 } 324 }
360 // Unconditially applied element hiding filters 325 // Unconditially applied element hiding filters
361 else if (filterBySelector.get(filter.selector) == filter) 326 else if (filterBySelector.get(filter.selector) == filter)
362 { 327 {
363 filterBySelector.delete(filter.selector); 328 filterBySelector.delete(filter.selector);
364 unconditionalSelectors = null; 329 unconditionalSelectors = null;
365 } 330 }
(...skipping 29 matching lines...) Expand all
395 for (let i = list.length - 1; i >= 0; i--) 360 for (let i = list.length - 1; i >= 0; i--)
396 { 361 {
397 if (list[i].isActiveOnDomain(docDomain)) 362 if (list[i].isActiveOnDomain(docDomain))
398 return list[i]; 363 return list[i];
399 } 364 }
400 365
401 return null; 366 return null;
402 }, 367 },
403 368
404 /** 369 /**
405 * Constant used by getSelectorsForDomain to return all selectors applying to
406 * a particular hostname.
407 * @type {number}
408 * @const
409 */
410 ALL_MATCHING: 0,
411
412 /**
413 * Constant used by getSelectorsForDomain to exclude selectors which apply to
414 * all websites without exception.
415 * @type {number}
416 * @const
417 */
418 NO_UNCONDITIONAL: 1,
419
420 /**
421 * Constant used by getSelectorsForDomain to return only selectors for filters
422 * which specifically match the given host name.
423 * @type {number}
424 * @const
425 */
426 SPECIFIC_ONLY: 2,
427
428 /**
429 * Determines from the current filter list which selectors should be applied 370 * Determines from the current filter list which selectors should be applied
430 * on a particular host name. 371 * on a particular host name.
431 * @param {string} domain 372 * @param {string} domain
432 * @param {number} [criteria] 373 * @param {boolean} [specificOnly] true if generic filters should not apply.
433 * One of the following: ElemHide.ALL_MATCHING, ElemHide.NO_UNCONDITIONAL or 374 * @returns {string[]} List of selectors.
434 * ElemHide.SPECIFIC_ONLY.
435 * @returns {string[]}
436 * List of selectors.
437 */ 375 */
438 getSelectorsForDomain(domain, criteria = ElemHide.ALL_MATCHING) 376 getSelectorsForDomain(domain, specificOnly = false)
439 { 377 {
440 let selectors = []; 378 let specificFilters = getSpecificFiltersForDomain(domain);
441 379
442 let specificOnly = criteria >= ElemHide.SPECIFIC_ONLY; 380 // If there are no specific filters (nor any specific exceptions), we can
443 let filtersList = getSpecificFiltersForDomain(domain); 381 // just return the selectors from all the generic filters modulo any
444 382 // generic exceptions.
445 if (filtersList.length > 0) 383 if (specificFilters.length == 0)
446 { 384 {
447 if (!specificOnly) 385 return specificOnly ? [] :
448 filtersList.push(filtersByDomain.get("")); 386 getUnconditionalSelectors()
449 387 .concat(getConditionalGenericSelectors());
450 selectors = getConditionalSelectorsForDomain(domain, filtersList, 388 }
451 specificOnly); 389
452 } 390 let excluded = new Set();
453 else if (!specificOnly) 391 let selectors = matchSelectors(domain, specificFilters, excluded);
454 { 392
455 selectors = getConditionalGenericSelectors(); 393 if (specificOnly)
456 } 394 return selectors;
457 395
458 if (criteria < ElemHide.NO_UNCONDITIONAL) 396 return getUnconditionalSelectors()
459 selectors = getUnconditionalSelectors().concat(selectors); 397 .concat(selectors,
460 398 matchSelectors(domain, [filtersByDomain.get("")],
461 // If the above logic leaves us with a reference to our internal cache of 399 excluded));
462 // selectors, we make a copy here.
463 if (selectors == conditionalGenericSelectors)
464 selectors = selectors.slice();
465
466 return selectors;
467 } 400 }
468 }; 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