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