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: Look up filters before deciding path Created May 9, 2018, 7:55 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 14 matching lines...) Expand all
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 * Set containing selectors with generic exceptions
67 * @type {Set.<string>}
68 */
69 let genericExceptionSelectors = new Set();
70
71 /*
72 * Returns a list of domain-specific filters matching a domain
73 * @param {string} [domain]
74 * @returns {Array.<?Map.<Filter,boolean>>}
75 */
76 function getSpecificFiltersForDomain(domain)
77 {
78 let filtersList = [];
79
80 if (domain)
81 domain = domain.toUpperCase();
82
83 while (domain)
84 {
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);
89 if (typeof filters != "undefined")
90 filtersList.push(filters);
91
92 let nextDot = domain.indexOf(".");
93 domain = nextDot == -1 ? null : domain.substring(nextDot + 1);
94 }
95
96 return filtersList;
97 }
98
99 /**
100 * Returns a list of selectors from a given list of filters that apply on a
101 * domain
102 * @param {string} [domain]
103 * @param {Array.<?Map.<Filter,boolean>>} filtersList
104 * @returns {string[]}
105 */
106 function getConditionalSelectorsForDomain(domain, filtersList)
107 {
108 let selectors = [];
109
110 let excluded = new Set();
111
112 // This code is a performance hot-spot, which is why we've made certain
113 // micro-optimisations. Please be careful before making changes.
114 for (let i = 0; i < filtersList.length; i++)
115 {
116 if (!filtersList[i])
117 continue;
118
119 for (let [filter, isIncluded] of filtersList[i])
120 {
121 if (!isIncluded)
122 {
123 excluded.add(filter);
124 }
125 else if ((excluded.size == 0 || !excluded.has(filter)) &&
126 !ElemHide.getException(filter, domain))
127 {
128 selectors.push(filter.selector);
129 }
130 }
131 }
132
133 return selectors;
134 }
135
136 /*
137 * Returns a list of selectors that apply on any unknown domain
138 * @returns {string[]}
139 */
140 function getConditionalGenericSelectors()
141 {
142 let selectors = [];
143
144 let filters = filtersByDomain.get("");
145 if (!filters)
146 return selectors;
147
148 for (let {selector} of filters.keys())
149 {
150 if (genericExceptionSelectors.size == 0 ||
151 !genericExceptionSelectors.has(selector))
152 {
153 selectors.push(selector);
154 }
155 }
156
157 return selectors;
158 }
159
65 /** 160 /**
66 * Returns a list of selectors that apply on each website unconditionally. 161 * Returns a list of selectors that apply on each website unconditionally.
67 * @returns {string[]} 162 * @returns {string[]}
68 */ 163 */
69 function getUnconditionalSelectors() 164 function getUnconditionalSelectors()
70 { 165 {
71 if (!unconditionalSelectors) 166 if (!unconditionalSelectors)
72 unconditionalSelectors = [...filterBySelector.keys()]; 167 unconditionalSelectors = [...filterBySelector.keys()];
73 168
74 return unconditionalSelectors; 169 return unconditionalSelectors;
75 } 170 }
76 171
77 /** 172 /**
78 * Container for element hiding filters 173 * Container for element hiding filters
79 * @class 174 * @class
80 */ 175 */
81 let ElemHide = exports.ElemHide = { 176 let ElemHide = exports.ElemHide = {
82 /** 177 /**
83 * Removes all known filters 178 * Removes all known filters
84 */ 179 */
85 clear() 180 clear()
86 { 181 {
87 for (let collection of [filtersByDomain, filterBySelector, 182 for (let collection of [filtersByDomain, filterBySelector,
88 knownFilters, exceptions]) 183 knownFilters, exceptions,
184 genericExceptionSelectors])
89 { 185 {
90 collection.clear(); 186 collection.clear();
91 } 187 }
92 unconditionalSelectors = null; 188 unconditionalSelectors = null;
93 FilterNotifier.emit("elemhideupdate"); 189 FilterNotifier.emit("elemhideupdate");
94 }, 190 },
95 191
96 _addToFiltersByDomain(filter) 192 _addToFiltersByDomain(filter)
97 { 193 {
98 let domains = filter.domains || defaultDomains; 194 let domains = filter.domains || defaultDomains;
99 for (let [domain, isIncluded] of domains) 195 if (filter instanceof ElemHideException)
100 { 196 {
101 // There's no need to note that a filter is generically disabled. 197 for (let domain of domains.keys())
102 if (!isIncluded && domain == "") 198 {
103 continue; 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;
104 213
105 let filters = filtersByDomain.get(domain); 214 let filters = filtersByDomain.get(domain);
106 if (!filters) 215 if (!filters)
107 filtersByDomain.set(domain, filters = new Map()); 216 filtersByDomain.set(domain, filters = new Map());
108 filters.set(filter, isIncluded); 217 filters.set(filter, isIncluded);
218 }
109 } 219 }
110 }, 220 },
111 221
112 /** 222 /**
113 * Add a new element hiding filter 223 * Add a new element hiding filter
114 * @param {ElemHideBase} filter 224 * @param {ElemHideBase} filter
115 */ 225 */
116 add(filter) 226 add(filter)
117 { 227 {
118 if (knownFilters.has(filter)) 228 if (knownFilters.has(filter))
119 return; 229 return;
120 230
121 if (filter instanceof ElemHideException) 231 if (filter instanceof ElemHideException)
122 { 232 {
123 let {selector} = filter; 233 let {selector, domains} = filter;
234
124 let list = exceptions.get(selector); 235 let list = exceptions.get(selector);
125 if (list) 236 if (list)
126 list.push(filter); 237 list.push(filter);
127 else 238 else
128 exceptions.set(selector, [filter]); 239 exceptions.set(selector, [filter]);
129 240
241 if (domains)
242 this._addToFiltersByDomain(filter);
243
244 if (filter.isGeneric())
245 genericExceptionSelectors.add(filter.selector);
246
130 // If this is the first exception for a previously unconditionally 247 // If this is the first exception for a previously unconditionally
131 // applied element hiding selector we need to take care to update the 248 // applied element hiding selector we need to take care to update the
132 // lookups. 249 // lookups.
133 let unconditionalFilterForSelector = filterBySelector.get(selector); 250 let unconditionalFilterForSelector = filterBySelector.get(selector);
134 if (unconditionalFilterForSelector) 251 if (unconditionalFilterForSelector)
135 { 252 {
136 this._addToFiltersByDomain(unconditionalFilterForSelector); 253 this._addToFiltersByDomain(unconditionalFilterForSelector);
137 filterBySelector.delete(selector); 254 filterBySelector.delete(selector);
138 unconditionalSelectors = null; 255 unconditionalSelectors = null;
139 } 256 }
(...skipping 23 matching lines...) Expand all
163 if (!knownFilters.has(filter)) 280 if (!knownFilters.has(filter))
164 return; 281 return;
165 282
166 // Whitelisting filters 283 // Whitelisting filters
167 if (filter instanceof ElemHideException) 284 if (filter instanceof ElemHideException)
168 { 285 {
169 let list = exceptions.get(filter.selector); 286 let list = exceptions.get(filter.selector);
170 let index = list.indexOf(filter); 287 let index = list.indexOf(filter);
171 if (index >= 0) 288 if (index >= 0)
172 list.splice(index, 1); 289 list.splice(index, 1);
290
291 if (filter.isGeneric())
292 genericExceptionSelectors.delete(filter.selector);
173 } 293 }
174 // Unconditially applied element hiding filters 294 // Unconditially applied element hiding filters
175 else if (filterBySelector.get(filter.selector) == filter) 295 else if (filterBySelector.get(filter.selector) == filter)
176 { 296 {
177 filterBySelector.delete(filter.selector); 297 filterBySelector.delete(filter.selector);
178 unconditionalSelectors = null; 298 unconditionalSelectors = null;
179 } 299 }
180 // Conditionally applied element hiding filters 300 // Conditionally applied element hiding filters
181 else 301 else
182 { 302 {
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
243 * @returns {string[]} 363 * @returns {string[]}
244 * List of selectors. 364 * List of selectors.
245 */ 365 */
246 getSelectorsForDomain(domain, criteria) 366 getSelectorsForDomain(domain, criteria)
247 { 367 {
248 let selectors = []; 368 let selectors = [];
249 369
250 if (typeof criteria == "undefined") 370 if (typeof criteria == "undefined")
251 criteria = ElemHide.ALL_MATCHING; 371 criteria = ElemHide.ALL_MATCHING;
252 372
253 let specificOnly = (criteria >= ElemHide.SPECIFIC_ONLY); 373 let specificOnly = criteria >= ElemHide.SPECIFIC_ONLY;
254 let excluded = new Set(); 374 let filtersList = getSpecificFiltersForDomain(domain);
255 let currentDomain = domain ? domain.toUpperCase() : "";
256 375
257 // This code is a performance hot-spot, which is why we've made certain 376 if (filtersList.length > 0)
258 // micro-optimisations. Please be careful before making changes.
259 while (true)
260 { 377 {
261 if (specificOnly && currentDomain == "") 378 // We only got domain-specific filters, now add the generic filters.
262 break; 379 if (!specificOnly)
380 filtersList.push(filtersByDomain.get(""));
263 381
264 let filters = filtersByDomain.get(currentDomain); 382 selectors = getConditionalSelectorsForDomain(domain, filtersList);
265 if (filters) 383 }
266 { 384 else if (!specificOnly)
267 for (let [filter, isIncluded] of filters) 385 {
268 { 386 // Since there are no domain-specific filters matching this domain, we
269 if (!isIncluded) 387 // can follow an optimized code path where we only check for generic
270 { 388 // filters and generic exceptions.
271 excluded.add(filter); 389 selectors = getConditionalGenericSelectors();
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
281 if (currentDomain == "")
282 break;
283
284 let nextDot = currentDomain.indexOf(".");
285 currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1);
286 } 390 }
287 391
288 if (criteria < ElemHide.NO_UNCONDITIONAL) 392 if (criteria < ElemHide.NO_UNCONDITIONAL)
289 selectors = getUnconditionalSelectors().concat(selectors); 393 selectors = getUnconditionalSelectors().concat(selectors);
290 394
291 return selectors; 395 return selectors;
292 } 396 }
293 }; 397 };
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