Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: lib/elemHide.js

Issue 29773570: Issue 6652 - Implement fast selector lookups for unknown domains (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Merge getConditionalGenericSelectors into getConditionalSelectorsForDomain Created May 9, 2018, 9:31 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | test/elemHide.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 "use strict"; 18 "use strict";
19 19
20 /** 20 /**
21 * @fileOverview Element hiding implementation. 21 * @fileOverview Element hiding implementation.
22 */ 22 */
23 23
24 const {ElemHideException} = require("./filterClasses"); 24 const {ElemHideException} = require("./filterClasses");
25 const {FilterNotifier} = require("./filterNotifier"); 25 const {FilterNotifier} = require("./filterNotifier");
26 26
27 /** 27 /**
28 * Lookup table, active flag, by filter by domain. 28 * Lookup table, active flag, by filter by domain.
29 * (Only contains filters that aren't unconditionally matched for all domains.) 29 * (Only contains filters that aren't unconditionally matched for all domains.)
30 * @type {Map.<string,Map.<Filter,boolean>>} 30 * @type {Map.<string,?Map.<Filter,boolean>>}
31 */ 31 */
32 let filtersByDomain = new Map(); 32 let filtersByDomain = new Map();
33 33
34 /** 34 /**
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
(...skipping 10 matching lines...) Expand all
51 let defaultDomains = new Map([["", true]]); 51 let defaultDomains = new Map([["", true]]);
52 52
53 /** 53 /**
54 * Set containing known element hiding and exception filters 54 * Set containing known element hiding and exception filters
55 * @type {Set.<ElemHideBase>} 55 * @type {Set.<ElemHideBase>}
56 */ 56 */
57 let knownFilters = new Set(); 57 let knownFilters = new Set();
58 58
59 /** 59 /**
60 * Lookup table, lists of element hiding exceptions by selector 60 * Lookup table, lists of element hiding exceptions by selector
61 * @type {Map.<string,Filter>} 61 * @type {Map.<string,Filter[]>}
62 */ 62 */
63 let exceptions = new Map(); 63 let exceptions = new Map();
64 64
65 /*
66 * Lookup table, lists of generic element hiding exceptions by selector
67 * @type {Map.<string,Filter[]>}
68 */
69 let genericExceptions = new Map();
Manish Jethani 2018/05/09 21:35:44 This needs to be a map actually, because we want t
70
71 /**
72 * Checks whether a filter has an exception
73 * @param {Filter} filter
74 * @param {string} [domain]
75 * @returns {boolean}
76 */
77 function hasException(filter, domain)
78 {
79 if (!domain)
80 return genericExceptions.has(filter.selector);
81
82 return !!ElemHide.getException(filter, domain);
83 }
84
85 /*
86 * Returns a list of domain-specific filters matching a domain
87 * @param {string} [domain]
88 * @returns {Array.<?Map.<Filter,boolean>>}
89 */
90 function getSpecificFiltersForDomain(domain)
91 {
92 let filtersList = [];
93
94 if (domain)
95 domain = domain.toUpperCase();
96
97 while (domain)
98 {
99 // Note that we also push null values into the list, because
100 // ElemHide.getSelectorsForDomain still needs to know if there are any
101 // entries for the domain.
102 let filters = filtersByDomain.get(domain);
103 if (typeof filters != "undefined")
104 filtersList.push(filters);
105
106 let nextDot = domain.indexOf(".");
107 domain = nextDot == -1 ? null : domain.substring(nextDot + 1);
108 }
109
110 return filtersList;
111 }
112
113 /**
114 * Returns a list of selectors from a given list of filters that apply on a
115 * domain
116 * @param {string} [domain]
117 * @param {Array.<?Map.<Filter,boolean>>} filtersList
118 * @param {boolean} genericOnly
119 * @returns {string[]}
120 */
121 function getConditionalSelectorsForDomain(domain, filtersList, genericOnly)
122 {
123 let selectors = [];
124
125 let excluded = !genericOnly ? new Set() : null;
126 let domainForExceptionCheck = !genericOnly ? domain : null;
127
128 // This code is a performance hot-spot, which is why we've made certain
129 // micro-optimisations. Please be careful before making changes.
130 for (let i = 0; i < filtersList.length; i++)
131 {
132 if (!filtersList[i])
133 continue;
134
135 for (let [filter, isIncluded] of filtersList[i])
136 {
137 if (!isIncluded)
138 {
139 excluded.add(filter);
140 }
141 else if ((!excluded || excluded.size == 0 || !excluded.has(filter)) &&
142 !hasException(filter, domainForExceptionCheck))
143 {
144 selectors.push(filter.selector);
145 }
146 }
147 }
148
149 return selectors;
150 }
151
65 /** 152 /**
66 * Returns a list of selectors that apply on each website unconditionally. 153 * Returns a list of selectors that apply on each website unconditionally.
67 * @returns {string[]} 154 * @returns {string[]}
68 */ 155 */
69 function getUnconditionalSelectors() 156 function getUnconditionalSelectors()
70 { 157 {
71 if (!unconditionalSelectors) 158 if (!unconditionalSelectors)
72 unconditionalSelectors = [...filterBySelector.keys()]; 159 unconditionalSelectors = [...filterBySelector.keys()];
73 160
74 return unconditionalSelectors; 161 return unconditionalSelectors;
75 } 162 }
76 163
77 /** 164 /**
78 * Container for element hiding filters 165 * Container for element hiding filters
79 * @class 166 * @class
80 */ 167 */
81 let ElemHide = exports.ElemHide = { 168 let ElemHide = exports.ElemHide = {
82 /** 169 /**
83 * Removes all known filters 170 * Removes all known filters
84 */ 171 */
85 clear() 172 clear()
86 { 173 {
87 for (let collection of [filtersByDomain, filterBySelector, 174 for (let collection of [filtersByDomain, filterBySelector,
88 knownFilters, exceptions]) 175 knownFilters, exceptions,
176 genericExceptions])
89 { 177 {
90 collection.clear(); 178 collection.clear();
91 } 179 }
92 unconditionalSelectors = null; 180 unconditionalSelectors = null;
93 FilterNotifier.emit("elemhideupdate"); 181 FilterNotifier.emit("elemhideupdate");
94 }, 182 },
95 183
96 _addToFiltersByDomain(filter) 184 _addToFiltersByDomain(filter)
97 { 185 {
98 let domains = filter.domains || defaultDomains; 186 let domains = filter.domains || defaultDomains;
99 for (let [domain, isIncluded] of domains) 187 if (filter instanceof ElemHideException)
100 { 188 {
101 // There's no need to note that a filter is generically disabled. 189 for (let domain of domains.keys())
102 if (!isIncluded && domain == "") 190 {
103 continue; 191 // Add an entry for each domain, but without any filters. This makes
192 // the domain "known" and helps us avoid certain optimizations that
193 // would otherwise yield incorrect results.
194 if (domain != "" && !filtersByDomain.has(domain))
195 filtersByDomain.set(domain, null);
196 }
197 }
198 else
199 {
200 for (let [domain, isIncluded] of domains)
201 {
202 // There's no need to note that a filter is generically disabled.
203 if (!isIncluded && domain == "")
204 continue;
104 205
105 let filters = filtersByDomain.get(domain); 206 let filters = filtersByDomain.get(domain);
106 if (!filters) 207 if (!filters)
107 filtersByDomain.set(domain, filters = new Map()); 208 filtersByDomain.set(domain, filters = new Map());
108 filters.set(filter, isIncluded); 209 filters.set(filter, isIncluded);
210 }
109 } 211 }
110 }, 212 },
111 213
112 /** 214 /**
113 * Add a new element hiding filter 215 * Add a new element hiding filter
114 * @param {ElemHideBase} filter 216 * @param {ElemHideBase} filter
115 */ 217 */
116 add(filter) 218 add(filter)
117 { 219 {
118 if (knownFilters.has(filter)) 220 if (knownFilters.has(filter))
119 return; 221 return;
120 222
121 if (filter instanceof ElemHideException) 223 if (filter instanceof ElemHideException)
122 { 224 {
123 let {selector} = filter; 225 let {selector, domains} = filter;
226
124 let list = exceptions.get(selector); 227 let list = exceptions.get(selector);
125 if (list) 228 if (list)
126 list.push(filter); 229 list.push(filter);
127 else 230 else
128 exceptions.set(selector, [filter]); 231 exceptions.set(selector, [filter]);
129 232
233 if (domains)
234 this._addToFiltersByDomain(filter);
235
236 if (filter.isGeneric())
237 {
238 list = genericExceptions.get(selector);
239 if (list)
240 list.push(filter);
241 else
242 genericExceptions.set(selector, [filter]);
243 }
244
130 // If this is the first exception for a previously unconditionally 245 // If this is the first exception for a previously unconditionally
131 // applied element hiding selector we need to take care to update the 246 // applied element hiding selector we need to take care to update the
132 // lookups. 247 // lookups.
133 let unconditionalFilterForSelector = filterBySelector.get(selector); 248 let unconditionalFilterForSelector = filterBySelector.get(selector);
134 if (unconditionalFilterForSelector) 249 if (unconditionalFilterForSelector)
135 { 250 {
136 this._addToFiltersByDomain(unconditionalFilterForSelector); 251 this._addToFiltersByDomain(unconditionalFilterForSelector);
137 filterBySelector.delete(selector); 252 filterBySelector.delete(selector);
138 unconditionalSelectors = null; 253 unconditionalSelectors = null;
139 } 254 }
(...skipping 23 matching lines...) Expand all
163 if (!knownFilters.has(filter)) 278 if (!knownFilters.has(filter))
164 return; 279 return;
165 280
166 // Whitelisting filters 281 // Whitelisting filters
167 if (filter instanceof ElemHideException) 282 if (filter instanceof ElemHideException)
168 { 283 {
169 let list = exceptions.get(filter.selector); 284 let list = exceptions.get(filter.selector);
170 let index = list.indexOf(filter); 285 let index = list.indexOf(filter);
171 if (index >= 0) 286 if (index >= 0)
172 list.splice(index, 1); 287 list.splice(index, 1);
288
289 if (filter.isGeneric())
290 {
291 list = genericExceptions.get(filter.selector);
292 index = list.indexOf(filter);
293 if (index >= 0)
294 list.splice(index, 1);
295
296 if (list.length == 0)
297 genericExceptions.delete(filter.selector);
298 }
173 } 299 }
174 // Unconditially applied element hiding filters 300 // Unconditially applied element hiding filters
175 else if (filterBySelector.get(filter.selector) == filter) 301 else if (filterBySelector.get(filter.selector) == filter)
176 { 302 {
177 filterBySelector.delete(filter.selector); 303 filterBySelector.delete(filter.selector);
178 unconditionalSelectors = null; 304 unconditionalSelectors = null;
179 } 305 }
180 // Conditionally applied element hiding filters 306 // Conditionally applied element hiding filters
181 else 307 else
182 { 308 {
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
238 * on a particular host name. 364 * on a particular host name.
239 * @param {string} domain 365 * @param {string} domain
240 * @param {number} [criteria] 366 * @param {number} [criteria]
241 * One of the following: ElemHide.ALL_MATCHING, ElemHide.NO_UNCONDITIONAL or 367 * One of the following: ElemHide.ALL_MATCHING, ElemHide.NO_UNCONDITIONAL or
242 * ElemHide.SPECIFIC_ONLY. 368 * ElemHide.SPECIFIC_ONLY.
243 * @returns {string[]} 369 * @returns {string[]}
244 * List of selectors. 370 * List of selectors.
245 */ 371 */
246 getSelectorsForDomain(domain, criteria) 372 getSelectorsForDomain(domain, criteria)
247 { 373 {
248 let selectors = [];
249
250 if (typeof criteria == "undefined") 374 if (typeof criteria == "undefined")
251 criteria = ElemHide.ALL_MATCHING; 375 criteria = ElemHide.ALL_MATCHING;
252 376
253 let specificOnly = (criteria >= ElemHide.SPECIFIC_ONLY); 377 let specificOnly = criteria >= ElemHide.SPECIFIC_ONLY;
254 let excluded = new Set(); 378 let filtersList = getSpecificFiltersForDomain(domain);
255 let currentDomain = domain ? domain.toUpperCase() : "";
256 379
257 // This code is a performance hot-spot, which is why we've made certain 380 let genericOnly = filtersList.length == 0;
258 // micro-optimisations. Please be careful before making changes.
259 while (true)
260 {
261 if (specificOnly && currentDomain == "")
262 break;
263 381
264 let filters = filtersByDomain.get(currentDomain); 382 if (!specificOnly)
265 if (filters) 383 filtersList.push(filtersByDomain.get(""));
266 {
267 for (let [filter, isIncluded] of filters)
268 {
269 if (!isIncluded)
270 {
271 excluded.add(filter);
272 }
273 else if ((excluded.size == 0 || !excluded.has(filter)) &&
274 !this.getException(filter, domain))
275 {
276 selectors.push(filter.selector);
277 }
278 }
279 }
280 384
281 if (currentDomain == "") 385 let selectors = getConditionalSelectorsForDomain(domain, filtersList,
282 break; 386 genericOnly);
283
284 let nextDot = currentDomain.indexOf(".");
285 currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1);
286 }
287 387
288 if (criteria < ElemHide.NO_UNCONDITIONAL) 388 if (criteria < ElemHide.NO_UNCONDITIONAL)
289 selectors = getUnconditionalSelectors().concat(selectors); 389 selectors = getUnconditionalSelectors().concat(selectors);
290 390
291 return selectors; 391 return selectors;
292 } 392 }
293 }; 393 };
OLDNEW
« no previous file with comments | « no previous file | test/elemHide.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld