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

Side by Side Diff: lib/elemHide.js

Issue 29757591: Issue 6562 - Remove filter keys from the element hiding code (Closed)
Patch Set: Avoid checking currentDomain == "" more than necessary Created May 2, 2018, 4:34 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/filterListener.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, filters by their associated key 28 * Lookup table, active flag, by filter by domain.
29 * @type {Filter[]}
30 */
31 let filterByKey = [];
32
33 /**
34 * Lookup table, keys of the filters by filter
35 * @type {Map.<Filter,number>}
36 */
37 let keyByFilter = new Map();
38
39 /**
40 * Nested lookup table, filter (or false if inactive) by filter key by domain.
41 * (Only contains filters that aren't unconditionally matched for all domains.) 29 * (Only contains filters that aren't unconditionally matched for all domains.)
42 * @type {Map.<string,Map.<number,(Filter|boolean)>>} 30 * @type {Map.<string,Map.<Filter,boolean>>}
43 */ 31 */
44 let filtersByDomain = new Map(); 32 let filtersByDomain = new Map();
45 33
46 /** 34 /**
47 * Lookup table, filter key by selector. (Only used for selectors that are 35 * Lookup table, filter by selector. (Only used for selectors that are
48 * unconditionally matched for all domains.) 36 * unconditionally matched for all domains.)
49 * @type {Map.<string,number>} 37 * @type {Map.<string,Filter>}
50 */ 38 */
51 let filterKeyBySelector = new Map(); 39 let filterBySelector = new Map();
52 40
53 /** 41 /**
54 * This array caches the keys of filterKeyBySelector table (selectors which 42 * This array caches the keys of filterBySelector table (selectors
55 * unconditionally apply on all domains). It will be null if the cache needs to 43 * which unconditionally apply on all domains). It will be null if the
56 * be rebuilt. 44 * cache needs to be rebuilt.
57 */ 45 */
58 let unconditionalSelectors = null; 46 let unconditionalSelectors = null;
59 47
60 /** 48 /**
61 * Object to be used instead when a filter has a blank domains property. 49 * Map to be used instead when a filter has a blank domains property.
62 */ 50 */
63 let defaultDomains = new Map([["", true]]); 51 let defaultDomains = new Map([["", true]]);
64 52
65 /** 53 /**
66 * Set containing known element hiding exceptions 54 * Set containing known element hiding and exception filters
67 * @type {Set.<string>} 55 * @type {Set.<ElemHideBase>}
68 */ 56 */
69 let knownExceptions = new Set(); 57 let knownFilters = new Set();
70 58
71 /** 59 /**
72 * Lookup table, lists of element hiding exceptions by selector 60 * Lookup table, lists of element hiding exceptions by selector
73 * @type {Map.<string,Filter>} 61 * @type {Map.<string,Filter>}
74 */ 62 */
75 let exceptions = new Map(); 63 let exceptions = new Map();
76 64
77 /** 65 /**
78 * Container for element hiding filters 66 * Container for element hiding filters
79 * @class 67 * @class
80 */ 68 */
81 let ElemHide = exports.ElemHide = { 69 let ElemHide = exports.ElemHide = {
82 /** 70 /**
83 * Removes all known filters 71 * Removes all known filters
84 */ 72 */
85 clear() 73 clear()
86 { 74 {
87 for (let collection of [keyByFilter, filtersByDomain, filterKeyBySelector, 75 for (let collection of [filtersByDomain, filterBySelector,
88 knownExceptions, exceptions]) 76 knownFilters, exceptions])
89 { 77 {
90 collection.clear(); 78 collection.clear();
91 } 79 }
92 filterByKey = [];
93 unconditionalSelectors = null; 80 unconditionalSelectors = null;
94 FilterNotifier.emit("elemhideupdate"); 81 FilterNotifier.emit("elemhideupdate");
95 }, 82 },
96 83
97 _addToFiltersByDomain(key, filter) 84 _addToFiltersByDomain(filter)
98 { 85 {
99 let domains = filter.domains || defaultDomains; 86 let domains = filter.domains || defaultDomains;
100 for (let [domain, isIncluded] of domains) 87 for (let [domain, isIncluded] of domains)
101 { 88 {
89 // There's no need to note that a filter is generically disabled.
90 if (!isIncluded && domain == "")
91 continue;
92
102 let filters = filtersByDomain.get(domain); 93 let filters = filtersByDomain.get(domain);
103 if (!filters) 94 if (!filters)
104 filtersByDomain.set(domain, filters = new Map()); 95 filtersByDomain.set(domain, filters = new Map());
105 filters.set(key, isIncluded ? filter : false); 96 filters.set(filter, isIncluded);
106 } 97 }
107 }, 98 },
108 99
109 /** 100 /**
110 * Add a new element hiding filter 101 * Add a new element hiding filter
111 * @param {ElemHideFilter} filter 102 * @param {ElemHideBase} filter
112 */ 103 */
113 add(filter) 104 add(filter)
114 { 105 {
106 if (knownFilters.has(filter))
107 return;
108
115 if (filter instanceof ElemHideException) 109 if (filter instanceof ElemHideException)
116 { 110 {
117 if (knownExceptions.has(filter.text))
118 return;
119
120 let {selector} = filter; 111 let {selector} = filter;
121 let list = exceptions.get(selector); 112 let list = exceptions.get(selector);
122 if (list) 113 if (list)
123 list.push(filter); 114 list.push(filter);
124 else 115 else
125 exceptions.set(selector, [filter]); 116 exceptions.set(selector, [filter]);
126 117
127 // If this is the first exception for a previously unconditionally 118 // If this is the first exception for a previously unconditionally
128 // applied element hiding selector we need to take care to update the 119 // applied element hiding selector we need to take care to update the
129 // lookups. 120 // lookups.
130 let filterKey = filterKeyBySelector.get(selector); 121 let unconditionalFilterForSelector = filterBySelector.get(selector);
131 if (typeof filterKey != "undefined") 122 if (unconditionalFilterForSelector)
132 { 123 {
133 this._addToFiltersByDomain(filterKey, filterByKey[filterKey]); 124 this._addToFiltersByDomain(unconditionalFilterForSelector);
134 filterKeyBySelector.delete(selector); 125 filterBySelector.delete(selector);
135 unconditionalSelectors = null; 126 unconditionalSelectors = null;
136 } 127 }
137 128 }
138 knownExceptions.add(filter.text); 129 else if (!(filter.domains || exceptions.has(filter.selector)))
130 {
131 // The new filter's selector is unconditionally applied to all domains
132 filterBySelector.set(filter.selector, filter);
133 unconditionalSelectors = null;
139 } 134 }
140 else 135 else
141 { 136 {
142 if (keyByFilter.has(filter)) 137 // The new filter's selector only applies to some domains
143 return; 138 this._addToFiltersByDomain(filter);
144
145 let key = filterByKey.push(filter) - 1;
146 keyByFilter.set(filter, key);
147
148 if (!(filter.domains || exceptions.has(filter.selector)))
149 {
150 // The new filter's selector is unconditionally applied to all domains
151 filterKeyBySelector.set(filter.selector, key);
152 unconditionalSelectors = null;
153 }
154 else
155 {
156 // The new filter's selector only applies to some domains
157 this._addToFiltersByDomain(key, filter);
158 }
159 } 139 }
160 140
141 knownFilters.add(filter);
161 FilterNotifier.emit("elemhideupdate"); 142 FilterNotifier.emit("elemhideupdate");
162 }, 143 },
163 144
164 _removeFilterKey(key, filter)
165 {
166 if (filterKeyBySelector.get(filter.selector) == key)
167 {
168 filterKeyBySelector.delete(filter.selector);
169 unconditionalSelectors = null;
170 return;
171 }
172
173 // We haven't found this filter in unconditional filters, look in
174 // filtersByDomain.
175 let domains = filter.domains || defaultDomains;
176 for (let domain of domains.keys())
177 {
178 let filters = filtersByDomain.get(domain);
179 if (filters)
180 filters.delete(key);
181 }
182 },
183
184 /** 145 /**
185 * Removes an element hiding filter 146 * Removes an element hiding filter
186 * @param {ElemHideFilter} filter 147 * @param {ElemHideBase} filter
187 */ 148 */
188 remove(filter) 149 remove(filter)
189 { 150 {
151 if (!knownFilters.has(filter))
152 return;
153
154 // Whitelisting filters
190 if (filter instanceof ElemHideException) 155 if (filter instanceof ElemHideException)
191 { 156 {
192 if (!knownExceptions.has(filter.text))
193 return;
194
195 let list = exceptions.get(filter.selector); 157 let list = exceptions.get(filter.selector);
196 let index = list.indexOf(filter); 158 let index = list.indexOf(filter);
197 if (index >= 0) 159 if (index >= 0)
198 list.splice(index, 1); 160 list.splice(index, 1);
199 knownExceptions.delete(filter.text);
200 } 161 }
162 // Unconditially applied element hiding filters
163 else if (filterBySelector.get(filter.selector) === filter)
164 {
165 filterBySelector.delete(filter.selector);
166 unconditionalSelectors = null;
167 }
168 // Conditionally applied element hiding filters
201 else 169 else
202 { 170 {
203 let key = keyByFilter.get(filter); 171 let domains = filter.domains || defaultDomains;
204 if (typeof key == "undefined") 172 for (let domain of domains.keys())
205 return; 173 {
206 174 let filters = filtersByDomain.get(domain);
207 delete filterByKey[key]; 175 if (filters)
208 keyByFilter.delete(filter); 176 filters.delete(filter);
209 this._removeFilterKey(key, filter); 177 }
210 } 178 }
211 179
180 knownFilters.delete(filter);
212 FilterNotifier.emit("elemhideupdate"); 181 FilterNotifier.emit("elemhideupdate");
213 }, 182 },
214 183
215 /** 184 /**
216 * Checks whether an exception rule is registered for a filter on a particular 185 * Checks whether an exception rule is registered for a filter on a particular
217 * domain. 186 * domain.
218 * @param {Filter} filter 187 * @param {Filter} filter
219 * @param {string} docDomain 188 * @param {string} docDomain
220 * @return {ElemHideException} 189 * @return {ElemHideException}
221 */ 190 */
222 getException(filter, docDomain) 191 getException(filter, docDomain)
223 { 192 {
224 let list = exceptions.get(filter.selector); 193 let list = exceptions.get(filter.selector);
225 if (!list) 194 if (!list)
226 return null; 195 return null;
227 196
228 for (let i = list.length - 1; i >= 0; i--) 197 for (let i = list.length - 1; i >= 0; i--)
229 { 198 {
230 if (list[i].isActiveOnDomain(docDomain)) 199 if (list[i].isActiveOnDomain(docDomain))
231 return list[i]; 200 return list[i];
232 } 201 }
233 202
234 return null; 203 return null;
235 }, 204 },
236 205
237 /** 206 /**
238 * Retrieves an element hiding filter by the corresponding protocol key
239 * @param {number} key
240 * @return {Filter}
241 */
242 getFilterByKey(key)
243 {
244 return (key in filterByKey ? filterByKey[key] : null);
245 },
246
247 /**
248 * Returns a list of selectors that apply on each website unconditionally. 207 * Returns a list of selectors that apply on each website unconditionally.
249 * @returns {string[]} 208 * @returns {string[]}
250 */ 209 */
251 getUnconditionalSelectors() 210 getUnconditionalSelectors()
252 { 211 {
253 if (!unconditionalSelectors) 212 if (!unconditionalSelectors)
254 unconditionalSelectors = [...filterKeyBySelector.keys()]; 213 unconditionalSelectors = [...filterBySelector.keys()];
255 return unconditionalSelectors.slice(); 214 return unconditionalSelectors.slice();
256 }, 215 },
257 216
258 /** 217 /**
259 * Constant used by getSelectorsForDomain to return all selectors applying to 218 * Constant used by getSelectorsForDomain to return all selectors applying to
260 * a particular hostname. 219 * a particular hostname.
261 */ 220 */
262 ALL_MATCHING: 0, 221 ALL_MATCHING: 0,
263 222
264 /** 223 /**
(...skipping 21 matching lines...) Expand all
286 getSelectorsForDomain(domain, criteria) 245 getSelectorsForDomain(domain, criteria)
287 { 246 {
288 let selectors = []; 247 let selectors = [];
289 248
290 if (typeof criteria == "undefined") 249 if (typeof criteria == "undefined")
291 criteria = ElemHide.ALL_MATCHING; 250 criteria = ElemHide.ALL_MATCHING;
292 if (criteria < ElemHide.NO_UNCONDITIONAL) 251 if (criteria < ElemHide.NO_UNCONDITIONAL)
293 selectors = this.getUnconditionalSelectors(); 252 selectors = this.getUnconditionalSelectors();
294 253
295 let specificOnly = (criteria >= ElemHide.SPECIFIC_ONLY); 254 let specificOnly = (criteria >= ElemHide.SPECIFIC_ONLY);
296 let seenFilters = new Set(); 255 let excludedFilters = new Set();
256
297 let currentDomain = domain ? domain.toUpperCase() : ""; 257 let currentDomain = domain ? domain.toUpperCase() : "";
258 let currentDomainIsGeneric = currentDomain == "";
Manish Jethani 2018/05/02 18:11:40 Comparison with an empty string really should not
kzar 2018/05/03 15:36:00 OK, Done.
259
260 // This code is a performance hot-spot, which is why we've made certain
261 // micro-optimisations. Please be careful before making changes.
298 while (true) 262 while (true)
299 { 263 {
300 if (specificOnly && currentDomain == "") 264 if (specificOnly && currentDomainIsGeneric)
301 break; 265 break;
302 266
303 let filters = filtersByDomain.get(currentDomain); 267 let filters = filtersByDomain.get(currentDomain);
304 if (filters) 268 if (filters)
305 { 269 {
306 for (let [filterKey, filter] of filters) 270 for (let [filter, isIncluded] of filters)
307 { 271 {
308 if (seenFilters.has(filterKey)) 272 if (excludedFilters.size > 0 && excludedFilters.has(filter))
Manish Jethani 2018/05/02 18:11:40 We only need to do this check for included filters
kzar 2018/05/03 15:36:01 Good point, Done.
309 continue; 273 continue;
310 seenFilters.add(filterKey);
311 274
312 if (filter && !this.getException(filter, domain)) 275 if (isIncluded)
313 selectors.push(filter.selector); 276 {
277 if (!this.getException(filter, domain))
278 selectors.push(filter.selector);
279 }
280 else if (!currentDomainIsGeneric)
Manish Jethani 2018/05/02 18:11:40 currentDomain will always be non-empty if the filt
kzar 2018/05/03 15:36:00 OK, Done.
281 excludedFilters.add(filter);
314 } 282 }
315 } 283 }
316 284
317 if (currentDomain == "") 285 if (currentDomainIsGeneric)
318 break; 286 break;
319 287
320 let nextDot = currentDomain.indexOf("."); 288 let nextDot = currentDomain.indexOf(".");
321 currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); 289 if (nextDot == -1)
290 {
291 currentDomain = "";
292 currentDomainIsGeneric = true;
293 }
294 else
295 currentDomain = currentDomain.substr(nextDot + 1);
322 } 296 }
323 297
324 return selectors; 298 return selectors;
325 } 299 }
326 }; 300 };
OLDNEW
« no previous file with comments | « no previous file | test/filterListener.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld