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: Clean up Created May 12, 2018, 9:24 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
82 * filters
83 * @type {Set.<string>}
84 */
85 let genericFriendlyDomains = new Set();
86
87 /**
88 * Adds a filter to the lookup table of filters by domain. 81 * Adds a filter to the lookup table of filters by domain.
89 * @param {Filter} filter 82 * @param {Filter} filter
90 */ 83 */
91 function addToFiltersByDomain(filter) 84 function addToFiltersByDomain(filter)
92 { 85 {
93 let domains = filter.domains || defaultDomains; 86 let domains = filter.domains || defaultDomains;
94 if (filter instanceof ElemHideException) 87 if (filter instanceof ElemHideException)
95 { 88 {
96 for (let domain of domains.keys()) 89 for (let domain of domains.keys())
97 { 90 {
(...skipping 14 matching lines...) Expand all
112 105
113 let filters = filtersByDomain.get(domain); 106 let filters = filtersByDomain.get(domain);
114 if (!filters) 107 if (!filters)
115 filtersByDomain.set(domain, filters = new Map()); 108 filtersByDomain.set(domain, filters = new Map());
116 filters.set(filter, isIncluded); 109 filters.set(filter, isIncluded);
117 } 110 }
118 } 111 }
119 } 112 }
120 113
121 /** 114 /**
122 * Checks whether a filter applies on a domain
123 * @param {Filter} filter
124 * @param {string} [domain]
125 * @param {Set.<Filter>} excludeSet
126 */
127 function doesFilterApply(filter, domain, excludeSet)
128 {
129 return (excludeSet.size == 0 || !excludeSet.has(filter)) &&
130 !ElemHide.getException(filter, domain);
131 }
132
133 /**
134 * Returns a list of domain-specific filters matching a domain 115 * Returns a list of domain-specific filters matching a domain
135 * @param {string} [domain] 116 * @param {string} [domain]
136 * @returns {Array.<?Map.<Filter,boolean>>} 117 * @returns {Array.<?Map.<Filter,boolean>>}
137 */ 118 */
138 function getSpecificFiltersForDomain(domain) 119 function getSpecificFiltersForDomain(domain)
139 { 120 {
140 let filtersList = []; 121 let filtersList = [];
141 122
142 if (domain) 123 if (domain)
143 domain = domain.toUpperCase(); 124 domain = domain.toUpperCase();
144 125
145 while (domain) 126 while (domain)
146 { 127 {
147 // Note that we also push null values into the list, because
148 // ElemHide.getSelectorsForDomain still needs to know if there are any
149 // entries for the domain.
150 let filters = filtersByDomain.get(domain); 128 let filters = filtersByDomain.get(domain);
151 if (typeof filters != "undefined") 129 if (typeof filters != "undefined")
152 filtersList.push(filters); 130 filtersList.push(filters);
153 131
154 let nextDot = domain.indexOf("."); 132 let nextDot = domain.indexOf(".");
155 domain = nextDot == -1 ? null : domain.substring(nextDot + 1); 133 domain = nextDot == -1 ? null : domain.substring(nextDot + 1);
156 } 134 }
157 135
158 return filtersList; 136 return filtersList;
159 } 137 }
160 138
161 /** 139 /**
162 * 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
163 * domain 141 * filters
164 * @param {string} [domain] 142 * @param {string} [domain]
165 * @param {Array.<?Map.<Filter,boolean>>} filtersList 143 * @param {Array.<?Map.<Filter,boolean>>} filtersList
166 * @param {?Map.<Filter,boolean>} genericFilters 144 * @param {Set.<Filter>} excludeSet
167 * @returns {string[]} 145 * @returns {string[]}
168 */ 146 */
169 function getConditionalSelectorsForDomain(domain, filtersList, genericFilters) 147 function matchSelectors(domain, filtersList, excludeSet)
170 { 148 {
171 let selectors = []; 149 let matches = [];
172
173 let excluded = new Set();
174 150
175 // 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
176 // micro-optimisations. Please be careful before making changes. 152 // micro-optimisations. Please be careful before making changes.
177 for (let i = 0; i < filtersList.length; i++) 153 for (let i = 0; i < filtersList.length; i++)
178 { 154 {
179 if (!filtersList[i]) 155 let filters = filtersList[i];
180 continue; 156 if (filters)
181 157 {
182 for (let [filter, isIncluded] of filtersList[i]) 158 for (let [filter, isIncluded] of filters)
183 { 159 {
184 if (!isIncluded) 160 if (!isIncluded)
185 excluded.add(filter); 161 {
186 else if (doesFilterApply(filter, domain, excluded)) 162 excludeSet.add(filter);
187 selectors.push(filter.selector); 163 }
188 } 164 else if ((excludeSet.size == 0 || !excludeSet.has(filter)) &&
189 } 165 !exports.ElemHide.getException(filter, domain))
190 166 {
191 if (!genericFilters) 167 matches.push(filter.selector);
192 return selectors; 168 }
193 169 }
194 if (genericFriendlyDomains.has(domain)) 170 }
195 return selectors.concat(getConditionalGenericSelectors()); 171 }
196 172
197 let genericSelectors = []; 173 return matches;
198
199 for (let filter of genericFilters.keys())
200 {
201 if (doesFilterApply(filter, domain, excluded))
202 genericSelectors.push(filter.selector);
203 }
204
205 // If the number of conditional generic selectors that apply on this domain
206 // is the same as the total number of conditional generic selectors, the
207 // domain is "generic friendly". In that case, we mark it is as such for
208 // faster lookups.
209 if (conditionalGenericSelectors &&
210 genericSelectors.length == conditionalGenericSelectors.length)
211 {
212 if (genericFriendlyDomains.size >= 1000)
213 genericFriendlyDomains.clear();
214
215 genericFriendlyDomains.add(domain);
216 }
217
218 return selectors.concat(genericSelectors);
219 } 174 }
220 175
221 /** 176 /**
222 * Returns a list of selectors that apply on any unknown domain 177 * Returns a list of selectors that apply on any unknown domain
223 * @returns {string[]} 178 * @returns {string[]}
224 */ 179 */
225 function getConditionalGenericSelectors() 180 function getConditionalGenericSelectors()
226 { 181 {
227 if (conditionalGenericSelectors) 182 if (conditionalGenericSelectors)
228 return conditionalGenericSelectors; 183 return conditionalGenericSelectors;
(...skipping 22 matching lines...) Expand all
251 if (!unconditionalSelectors) 206 if (!unconditionalSelectors)
252 unconditionalSelectors = [...filterBySelector.keys()]; 207 unconditionalSelectors = [...filterBySelector.keys()];
253 208
254 return unconditionalSelectors; 209 return unconditionalSelectors;
255 } 210 }
256 211
257 /** 212 /**
258 * Container for element hiding filters 213 * Container for element hiding filters
259 * @class 214 * @class
260 */ 215 */
261 let ElemHide = exports.ElemHide = { 216 exports.ElemHide = {
262 /** 217 /**
263 * Removes all known filters 218 * Removes all known filters
264 */ 219 */
265 clear() 220 clear()
266 { 221 {
267 for (let collection of [filtersByDomain, filterBySelector, 222 for (let collection of [filtersByDomain, filterBySelector,
268 knownFilters, exceptions, 223 knownFilters, exceptions,
269 genericExceptions, genericFriendlyDomains]) 224 genericExceptions])
270 { 225 {
271 collection.clear(); 226 collection.clear();
272 } 227 }
273 unconditionalSelectors = null; 228 unconditionalSelectors = null;
274 conditionalGenericSelectors = null; 229 conditionalGenericSelectors = null;
275 FilterNotifier.emit("elemhideupdate"); 230 FilterNotifier.emit("elemhideupdate");
276 }, 231 },
277 232
278 /** 233 /**
279 * Add a new element hiding filter 234 * Add a new element hiding filter
280 * @param {ElemHideBase} filter 235 * @param {ElemHideBase} filter
281 */ 236 */
282 add(filter) 237 add(filter)
283 { 238 {
284 if (knownFilters.has(filter)) 239 if (knownFilters.has(filter))
285 return; 240 return;
286 241
287 conditionalGenericSelectors = null; 242 conditionalGenericSelectors = null;
288 genericFriendlyDomains.clear();
289 243
290 if (filter instanceof ElemHideException) 244 if (filter instanceof ElemHideException)
291 { 245 {
292 let {selector, domains} = filter; 246 let {selector, domains} = filter;
293 247
294 let list = exceptions.get(selector); 248 let list = exceptions.get(selector);
295 if (list) 249 if (list)
296 list.push(filter); 250 list.push(filter);
297 else 251 else
298 exceptions.set(selector, [filter]); 252 exceptions.set(selector, [filter]);
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
339 /** 293 /**
340 * Removes an element hiding filter 294 * Removes an element hiding filter
341 * @param {ElemHideBase} filter 295 * @param {ElemHideBase} filter
342 */ 296 */
343 remove(filter) 297 remove(filter)
344 { 298 {
345 if (!knownFilters.has(filter)) 299 if (!knownFilters.has(filter))
346 return; 300 return;
347 301
348 conditionalGenericSelectors = null; 302 conditionalGenericSelectors = null;
349 genericFriendlyDomains.clear();
350 303
351 // Whitelisting filters 304 // Whitelisting filters
352 if (filter instanceof ElemHideException) 305 if (filter instanceof ElemHideException)
353 { 306 {
354 let list = exceptions.get(filter.selector); 307 let list = exceptions.get(filter.selector);
355 let index = list.indexOf(filter); 308 let index = list.indexOf(filter);
356 if (index >= 0) 309 if (index >= 0)
357 list.splice(index, 1); 310 list.splice(index, 1);
358 311
359 if (filter.isGeneric()) 312 if (filter.isGeneric())
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
407 for (let i = list.length - 1; i >= 0; i--) 360 for (let i = list.length - 1; i >= 0; i--)
408 { 361 {
409 if (list[i].isActiveOnDomain(docDomain)) 362 if (list[i].isActiveOnDomain(docDomain))
410 return list[i]; 363 return list[i];
411 } 364 }
412 365
413 return null; 366 return null;
414 }, 367 },
415 368
416 /** 369 /**
417 * Constant used by getSelectorsForDomain to return all selectors applying to
418 * a particular hostname.
419 * @type {number}
420 * @const
421 */
422 ALL_MATCHING: 0,
423
424 /**
425 * Constant used by getSelectorsForDomain to exclude selectors which apply to
426 * all websites without exception.
427 * @type {number}
428 * @const
429 */
430 NO_UNCONDITIONAL: 1,
431
432 /**
433 * Constant used by getSelectorsForDomain to return only selectors for filters
434 * which specifically match the given host name.
435 * @type {number}
436 * @const
437 */
438 SPECIFIC_ONLY: 2,
439
440 /**
441 * Determines from the current filter list which selectors should be applied 370 * Determines from the current filter list which selectors should be applied
442 * on a particular host name. 371 * on a particular host name.
443 * @param {string} domain 372 * @param {string} domain
444 * @param {number} [criteria] 373 * @param {boolean} [specificOnly] true if generic filters should not apply.
445 * One of the following: ElemHide.ALL_MATCHING, ElemHide.NO_UNCONDITIONAL or 374 * @returns {string[]} List of selectors.
446 * ElemHide.SPECIFIC_ONLY.
447 * @returns {string[]}
448 * List of selectors.
449 */ 375 */
450 getSelectorsForDomain(domain, criteria = ElemHide.ALL_MATCHING) 376 getSelectorsForDomain(domain, specificOnly = false)
451 { 377 {
452 let selectors = []; 378 let specificFilters = getSpecificFiltersForDomain(domain);
453 379
454 let specificOnly = criteria >= ElemHide.SPECIFIC_ONLY; 380 // If there are no specific filters (nor any specific exceptions), we can
455 let filtersList = getSpecificFiltersForDomain(domain); 381 // just return the selectors from all the generic filters modulo any
456 382 // generic exceptions.
457 if (filtersList.length > 0) 383 if (specificFilters.length == 0)
458 { 384 {
459 let genericFilters = !specificOnly ? filtersByDomain.get("") : null; 385 return specificOnly ? [] :
460 386 getUnconditionalSelectors()
461 selectors = getConditionalSelectorsForDomain(domain, filtersList, 387 .concat(getConditionalGenericSelectors());
462 genericFilters); 388 }
463 } 389
464 else if (!specificOnly) 390 let excluded = new Set();
465 { 391 let selectors = matchSelectors(domain, specificFilters, excluded);
466 selectors = getConditionalGenericSelectors(); 392
467 } 393 if (specificOnly)
468 394 return selectors;
469 if (criteria < ElemHide.NO_UNCONDITIONAL) 395
470 selectors = getUnconditionalSelectors().concat(selectors); 396 return getUnconditionalSelectors()
471 397 .concat(selectors,
472 // If the above logic leaves us with a reference to our internal cache of 398 matchSelectors(domain, [filtersByDomain.get("")],
473 // selectors, we make a copy here. 399 excluded));
474 if (selectors == conditionalGenericSelectors)
475 selectors = selectors.slice();
476
477 return selectors;
478 } 400 }
479 }; 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