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