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