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: Look up filters before deciding path Created May 9, 2018, 7:55 p.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 24 matching lines...) Expand all
35 * Lookup table, filter by selector. (Only used for selectors that are 35 * Lookup table, filter by selector. (Only used for selectors that are
36 * unconditionally matched for all domains.) 36 * unconditionally matched for all domains.)
37 * @type {Map.<string,Filter>} 37 * @type {Map.<string,Filter>}
38 */ 38 */
39 let filterBySelector = new Map(); 39 let filterBySelector = new Map();
40 40
41 /** 41 /**
42 * This array caches the keys of filterBySelector table (selectors 42 * This array caches the keys of filterBySelector table (selectors
43 * which unconditionally apply on all domains). It will be null if the 43 * which unconditionally apply on all domains). It will be null if the
44 * cache needs to be rebuilt. 44 * cache needs to be rebuilt.
45 * @type {?string[]}
45 */ 46 */
46 let unconditionalSelectors = null; 47 let unconditionalSelectors = null;
47 48
48 /** 49 /**
49 * Map to be used instead when a filter has a blank domains property. 50 * Map to be used instead when a filter has a blank domains property.
51 * @type {Map.<string,boolean>}
52 * @const
50 */ 53 */
51 let defaultDomains = new Map([["", true]]); 54 let defaultDomains = new Map([["", true]]);
52 55
53 /** 56 /**
54 * Set containing known element hiding and exception filters 57 * Set containing known element hiding and exception filters
55 * @type {Set.<ElemHideBase>} 58 * @type {Set.<ElemHideBase>}
56 */ 59 */
57 let knownFilters = new Set(); 60 let knownFilters = new Set();
58 61
59 /** 62 /**
60 * Lookup table, lists of element hiding exceptions by selector 63 * Lookup table, lists of element hiding exceptions by selector
61 * @type {Map.<string,Filter>} 64 * @type {Map.<string,Filter[]>}
62 */ 65 */
63 let exceptions = new Map(); 66 let exceptions = new Map();
64 67
65 /* 68 /**
66 * Set containing selectors with generic exceptions 69 * Lookup table, lists of generic element hiding exceptions by selector
67 * @type {Set.<string>} 70 * @type {Map.<string,Filter[]>}
68 */ 71 */
69 let genericExceptionSelectors = new Set(); 72 let genericExceptions = new Map();
70 73
71 /* 74 /**
75 * List of selectors that apply on any unknown domain
76 * @type {?string[]}
77 */
78 let conditionalGenericSelectors = null;
79
80 /**
81 * Adds a filter to the lookup table of filters by domain.
82 * @param {Filter} filter
83 */
84 function addToFiltersByDomain(filter)
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 }
113
114 /**
72 * Returns a list of domain-specific filters matching a domain 115 * Returns a list of domain-specific filters matching a domain
73 * @param {string} [domain] 116 * @param {string} [domain]
74 * @returns {Array.<?Map.<Filter,boolean>>} 117 * @returns {Array.<?Map.<Filter,boolean>>}
75 */ 118 */
76 function getSpecificFiltersForDomain(domain) 119 function getSpecificFiltersForDomain(domain)
77 { 120 {
78 let filtersList = []; 121 let filtersList = [];
79 122
80 if (domain) 123 if (domain)
81 domain = domain.toUpperCase(); 124 domain = domain.toUpperCase();
82 125
83 while (domain) 126 while (domain)
84 { 127 {
85 // Note that we also push null values into the list, because
86 // ElemHide.getSelectorsForDomain still needs to know if there are any
87 // entries for the domain.
88 let filters = filtersByDomain.get(domain); 128 let filters = filtersByDomain.get(domain);
89 if (typeof filters != "undefined") 129 if (typeof filters != "undefined")
90 filtersList.push(filters); 130 filtersList.push(filters);
91 131
92 let nextDot = domain.indexOf("."); 132 let nextDot = domain.indexOf(".");
93 domain = nextDot == -1 ? null : domain.substring(nextDot + 1); 133 domain = nextDot == -1 ? null : domain.substring(nextDot + 1);
94 } 134 }
95 135
96 return filtersList; 136 return filtersList;
97 } 137 }
98 138
99 /** 139 /**
100 * 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
101 * domain 141 * filters
102 * @param {string} [domain] 142 * @param {string} [domain]
103 * @param {Array.<?Map.<Filter,boolean>>} filtersList 143 * @param {Array.<?Map.<Filter,boolean>>} filtersList
144 * @param {Set.<Filter>} excludeSet
104 * @returns {string[]} 145 * @returns {string[]}
105 */ 146 */
106 function getConditionalSelectorsForDomain(domain, filtersList) 147 function matchSelectors(domain, filtersList, excludeSet)
107 { 148 {
108 let selectors = []; 149 let matches = [];
109
110 let excluded = new Set();
111 150
112 // 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
113 // micro-optimisations. Please be careful before making changes. 152 // micro-optimisations. Please be careful before making changes.
114 for (let i = 0; i < filtersList.length; i++) 153 for (let i = 0; i < filtersList.length; i++)
115 { 154 {
116 if (!filtersList[i]) 155 let filters = filtersList[i];
117 continue; 156 if (filters)
118 157 {
119 for (let [filter, isIncluded] of filtersList[i]) 158 for (let [filter, isIncluded] of filters)
120 {
121 if (!isIncluded)
122 { 159 {
123 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 }
124 } 169 }
125 else if ((excluded.size == 0 || !excluded.has(filter)) && 170 }
126 !ElemHide.getException(filter, domain)) 171 }
127 { 172
128 selectors.push(filter.selector); 173 return matches;
129 }
130 }
131 }
132
133 return selectors;
134 } 174 }
135 175
136 /* 176 /**
137 * Returns a list of selectors that apply on any unknown domain 177 * Returns a list of selectors that apply on any unknown domain
138 * @returns {string[]} 178 * @returns {string[]}
139 */ 179 */
140 function getConditionalGenericSelectors() 180 function getConditionalGenericSelectors()
141 { 181 {
142 let selectors = []; 182 if (conditionalGenericSelectors)
183 return conditionalGenericSelectors;
184
185 conditionalGenericSelectors = [];
143 186
144 let filters = filtersByDomain.get(""); 187 let filters = filtersByDomain.get("");
145 if (!filters) 188 if (!filters)
146 return selectors; 189 return conditionalGenericSelectors;
147 190
148 for (let {selector} of filters.keys()) 191 for (let {selector} of filters.keys())
149 { 192 {
150 if (genericExceptionSelectors.size == 0 || 193 if (genericExceptions.size == 0 || !genericExceptions.has(selector))
151 !genericExceptionSelectors.has(selector)) 194 conditionalGenericSelectors.push(selector);
152 { 195 }
153 selectors.push(selector); 196
154 } 197 return conditionalGenericSelectors;
155 }
156
157 return selectors;
158 } 198 }
159 199
160 /** 200 /**
161 * Returns a list of selectors that apply on each website unconditionally. 201 * Returns a list of selectors that apply on each website unconditionally.
162 * @returns {string[]} 202 * @returns {string[]}
163 */ 203 */
164 function getUnconditionalSelectors() 204 function getUnconditionalSelectors()
165 { 205 {
166 if (!unconditionalSelectors) 206 if (!unconditionalSelectors)
167 unconditionalSelectors = [...filterBySelector.keys()]; 207 unconditionalSelectors = [...filterBySelector.keys()];
168 208
169 return unconditionalSelectors; 209 return unconditionalSelectors;
170 } 210 }
171 211
172 /** 212 /**
173 * Container for element hiding filters 213 * Container for element hiding filters
174 * @class 214 * @class
175 */ 215 */
176 let ElemHide = exports.ElemHide = { 216 exports.ElemHide = {
177 /** 217 /**
178 * Removes all known filters 218 * Removes all known filters
179 */ 219 */
180 clear() 220 clear()
181 { 221 {
182 for (let collection of [filtersByDomain, filterBySelector, 222 for (let collection of [filtersByDomain, filterBySelector,
183 knownFilters, exceptions, 223 knownFilters, exceptions,
184 genericExceptionSelectors]) 224 genericExceptions])
185 { 225 {
186 collection.clear(); 226 collection.clear();
187 } 227 }
188 unconditionalSelectors = null; 228 unconditionalSelectors = null;
229 conditionalGenericSelectors = null;
189 FilterNotifier.emit("elemhideupdate"); 230 FilterNotifier.emit("elemhideupdate");
190 },
191
192 _addToFiltersByDomain(filter)
193 {
194 let domains = filter.domains || defaultDomains;
195 if (filter instanceof ElemHideException)
196 {
197 for (let domain of domains.keys())
198 {
199 // Add an entry for each domain, but without any filters. This makes
200 // the domain "known" and helps us avoid certain optimizations that
201 // would otherwise yield incorrect results.
202 if (domain != "" && !filtersByDomain.has(domain))
203 filtersByDomain.set(domain, null);
204 }
205 }
206 else
207 {
208 for (let [domain, isIncluded] of domains)
209 {
210 // There's no need to note that a filter is generically disabled.
211 if (!isIncluded && domain == "")
212 continue;
213
214 let filters = filtersByDomain.get(domain);
215 if (!filters)
216 filtersByDomain.set(domain, filters = new Map());
217 filters.set(filter, isIncluded);
218 }
219 }
220 }, 231 },
221 232
222 /** 233 /**
223 * Add a new element hiding filter 234 * Add a new element hiding filter
224 * @param {ElemHideBase} filter 235 * @param {ElemHideBase} filter
225 */ 236 */
226 add(filter) 237 add(filter)
227 { 238 {
228 if (knownFilters.has(filter)) 239 if (knownFilters.has(filter))
229 return; 240 return;
230 241
242 conditionalGenericSelectors = null;
243
231 if (filter instanceof ElemHideException) 244 if (filter instanceof ElemHideException)
232 { 245 {
233 let {selector, domains} = filter; 246 let {selector, domains} = filter;
234 247
235 let list = exceptions.get(selector); 248 let list = exceptions.get(selector);
236 if (list) 249 if (list)
237 list.push(filter); 250 list.push(filter);
238 else 251 else
239 exceptions.set(selector, [filter]); 252 exceptions.set(selector, [filter]);
240 253
241 if (domains) 254 if (domains)
242 this._addToFiltersByDomain(filter); 255 addToFiltersByDomain(filter);
243 256
244 if (filter.isGeneric()) 257 if (filter.isGeneric())
245 genericExceptionSelectors.add(filter.selector); 258 {
259 list = genericExceptions.get(selector);
260 if (list)
261 list.push(filter);
262 else
263 genericExceptions.set(selector, [filter]);
264 }
246 265
247 // If this is the first exception for a previously unconditionally 266 // If this is the first exception for a previously unconditionally
248 // 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
249 // lookups. 268 // lookups.
250 let unconditionalFilterForSelector = filterBySelector.get(selector); 269 let unconditionalFilterForSelector = filterBySelector.get(selector);
251 if (unconditionalFilterForSelector) 270 if (unconditionalFilterForSelector)
252 { 271 {
253 this._addToFiltersByDomain(unconditionalFilterForSelector); 272 addToFiltersByDomain(unconditionalFilterForSelector);
254 filterBySelector.delete(selector); 273 filterBySelector.delete(selector);
255 unconditionalSelectors = null; 274 unconditionalSelectors = null;
256 } 275 }
257 } 276 }
258 else if (!(filter.domains || exceptions.has(filter.selector))) 277 else if (!(filter.domains || exceptions.has(filter.selector)))
259 { 278 {
260 // The new filter's selector is unconditionally applied to all domains 279 // The new filter's selector is unconditionally applied to all domains
261 filterBySelector.set(filter.selector, filter); 280 filterBySelector.set(filter.selector, filter);
262 unconditionalSelectors = null; 281 unconditionalSelectors = null;
263 } 282 }
264 else 283 else
265 { 284 {
266 // The new filter's selector only applies to some domains 285 // The new filter's selector only applies to some domains
267 this._addToFiltersByDomain(filter); 286 addToFiltersByDomain(filter);
268 } 287 }
269 288
270 knownFilters.add(filter); 289 knownFilters.add(filter);
271 FilterNotifier.emit("elemhideupdate"); 290 FilterNotifier.emit("elemhideupdate");
272 }, 291 },
273 292
274 /** 293 /**
275 * Removes an element hiding filter 294 * Removes an element hiding filter
276 * @param {ElemHideBase} filter 295 * @param {ElemHideBase} filter
277 */ 296 */
278 remove(filter) 297 remove(filter)
279 { 298 {
280 if (!knownFilters.has(filter)) 299 if (!knownFilters.has(filter))
281 return; 300 return;
282 301
302 conditionalGenericSelectors = null;
303
283 // Whitelisting filters 304 // Whitelisting filters
284 if (filter instanceof ElemHideException) 305 if (filter instanceof ElemHideException)
285 { 306 {
286 let list = exceptions.get(filter.selector); 307 let list = exceptions.get(filter.selector);
287 let index = list.indexOf(filter); 308 let index = list.indexOf(filter);
288 if (index >= 0) 309 if (index >= 0)
289 list.splice(index, 1); 310 list.splice(index, 1);
290 311
291 if (filter.isGeneric()) 312 if (filter.isGeneric())
292 genericExceptionSelectors.delete(filter.selector); 313 {
314 list = genericExceptions.get(filter.selector);
315 index = list.indexOf(filter);
316 if (index >= 0)
317 list.splice(index, 1);
318
319 // It's important to delete the entry here so the selector no longer
320 // appears to have any generic exceptions.
321 if (list.length == 0)
322 genericExceptions.delete(filter.selector);
323 }
293 } 324 }
294 // Unconditially applied element hiding filters 325 // Unconditially applied element hiding filters
295 else if (filterBySelector.get(filter.selector) == filter) 326 else if (filterBySelector.get(filter.selector) == filter)
296 { 327 {
297 filterBySelector.delete(filter.selector); 328 filterBySelector.delete(filter.selector);
298 unconditionalSelectors = null; 329 unconditionalSelectors = null;
299 } 330 }
300 // Conditionally applied element hiding filters 331 // Conditionally applied element hiding filters
301 else 332 else
302 { 333 {
303 let domains = filter.domains || defaultDomains; 334 let domains = filter.domains || defaultDomains;
304 for (let domain of domains.keys()) 335 for (let domain of domains.keys())
305 { 336 {
306 let filters = filtersByDomain.get(domain); 337 let filters = filtersByDomain.get(domain);
307 if (filters) 338 if (filters)
308 filters.delete(filter); 339 filters.delete(filter);
309 } 340 }
310 } 341 }
311 342
312 knownFilters.delete(filter); 343 knownFilters.delete(filter);
313 FilterNotifier.emit("elemhideupdate"); 344 FilterNotifier.emit("elemhideupdate");
314 }, 345 },
315 346
316 /** 347 /**
317 * Checks whether an exception rule is registered for a filter on a particular 348 * Checks whether an exception rule is registered for a filter on a particular
318 * domain. 349 * domain.
319 * @param {Filter} filter 350 * @param {Filter} filter
320 * @param {string} docDomain 351 * @param {?string} docDomain
321 * @return {ElemHideException} 352 * @return {?ElemHideException}
322 */ 353 */
323 getException(filter, docDomain) 354 getException(filter, docDomain)
324 { 355 {
325 let list = exceptions.get(filter.selector); 356 let list = exceptions.get(filter.selector);
326 if (!list) 357 if (!list)
327 return null; 358 return null;
328 359
329 for (let i = list.length - 1; i >= 0; i--) 360 for (let i = list.length - 1; i >= 0; i--)
330 { 361 {
331 if (list[i].isActiveOnDomain(docDomain)) 362 if (list[i].isActiveOnDomain(docDomain))
332 return list[i]; 363 return list[i];
333 } 364 }
334 365
335 return null; 366 return null;
336 }, 367 },
337
338 /**
339 * Constant used by getSelectorsForDomain to return all selectors applying to
340 * a particular hostname.
341 */
342 ALL_MATCHING: 0,
343
344 /**
345 * Constant used by getSelectorsForDomain to exclude selectors which apply to
346 * all websites without exception.
347 */
348 NO_UNCONDITIONAL: 1,
349
350 /**
351 * Constant used by getSelectorsForDomain to return only selectors for filters
352 * which specifically match the given host name.
353 */
354 SPECIFIC_ONLY: 2,
355 368
356 /** 369 /**
357 * Determines from the current filter list which selectors should be applied 370 * Determines from the current filter list which selectors should be applied
358 * on a particular host name. 371 * on a particular host name.
359 * @param {string} domain 372 * @param {string} domain
360 * @param {number} [criteria] 373 * @param {boolean} [specificOnly] true if generic filters should not apply.
361 * One of the following: ElemHide.ALL_MATCHING, ElemHide.NO_UNCONDITIONAL or 374 * @returns {string[]} List of selectors.
362 * ElemHide.SPECIFIC_ONLY.
363 * @returns {string[]}
364 * List of selectors.
365 */ 375 */
366 getSelectorsForDomain(domain, criteria) 376 getSelectorsForDomain(domain, specificOnly = false)
367 { 377 {
368 let selectors = []; 378 let specificFilters = getSpecificFiltersForDomain(domain);
369 379
370 if (typeof criteria == "undefined") 380 // If there are no specific filters (nor any specific exceptions), we can
371 criteria = ElemHide.ALL_MATCHING; 381 // just return the selectors from all the generic filters modulo any
372 382 // generic exceptions.
373 let specificOnly = criteria >= ElemHide.SPECIFIC_ONLY; 383 if (specificFilters.length == 0)
374 let filtersList = getSpecificFiltersForDomain(domain); 384 {
375 385 return specificOnly ? [] :
376 if (filtersList.length > 0) 386 getUnconditionalSelectors()
377 { 387 .concat(getConditionalGenericSelectors());
378 // We only got domain-specific filters, now add the generic filters. 388 }
379 if (!specificOnly) 389
380 filtersList.push(filtersByDomain.get("")); 390 let excluded = new Set();
381 391 let selectors = matchSelectors(domain, specificFilters, excluded);
382 selectors = getConditionalSelectorsForDomain(domain, filtersList); 392
383 } 393 if (specificOnly)
384 else if (!specificOnly) 394 return selectors;
385 { 395
386 // Since there are no domain-specific filters matching this domain, we 396 return getUnconditionalSelectors()
387 // can follow an optimized code path where we only check for generic 397 .concat(selectors,
388 // filters and generic exceptions. 398 matchSelectors(domain, [filtersByDomain.get("")],
389 selectors = getConditionalGenericSelectors(); 399 excluded));
390 }
391
392 if (criteria < ElemHide.NO_UNCONDITIONAL)
393 selectors = getUnconditionalSelectors().concat(selectors);
394
395 return selectors;
396 } 400 }
397 }; 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