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 adding duplicate hiding filters Created April 26, 2018, noon
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') | test/filterListener.js » ('J')
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.<string>}
Manish Jethani 2018/04/27 12:39:36 This is really {Set.<ElemHideBase>}
kzar 2018/04/30 10:11:46 Done.
68 */ 56 */
69 let knownExceptions = new Set(); 57 let knownFilters = new Set();
Manish Jethani 2018/04/27 12:39:36 I wonder if we should make this a WeakSet since we
kzar 2018/04/30 10:11:46 I considered that as well but we keep a reference
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 {
102 let filters = filtersByDomain.get(domain); 89 let filters = filtersByDomain.get(domain);
103 if (!filters) 90 if (!filters)
104 filtersByDomain.set(domain, filters = new Map()); 91 filtersByDomain.set(domain, filters = new Map());
105 filters.set(key, isIncluded ? filter : false); 92 filters.set(filter, isIncluded);
106 } 93 }
107 }, 94 },
108 95
109 /** 96 /**
110 * Add a new element hiding filter 97 * Add a new element hiding filter
111 * @param {ElemHideFilter} filter 98 * @param {ElemHideFilter} filter
Manish Jethani 2018/04/27 12:39:36 This is really {ElemHideBase}
kzar 2018/04/30 10:11:46 Well spotted, Done.
112 */ 99 */
113 add(filter) 100 add(filter)
114 { 101 {
102 if (knownFilters.has(filter))
103 return;
104
115 if (filter instanceof ElemHideException) 105 if (filter instanceof ElemHideException)
116 { 106 {
117 if (knownExceptions.has(filter.text))
118 return;
119
120 let {selector} = filter; 107 let {selector} = filter;
121 let list = exceptions.get(selector); 108 let list = exceptions.get(selector);
122 if (list) 109 if (list)
123 list.push(filter); 110 list.push(filter);
124 else 111 else
125 exceptions.set(selector, [filter]); 112 exceptions.set(selector, [filter]);
126 113
127 // If this is the first exception for a previously unconditionally 114 // If this is the first exception for a previously unconditionally
128 // applied element hiding selector we need to take care to update the 115 // applied element hiding selector we need to take care to update the
129 // lookups. 116 // lookups.
130 let filterKey = filterKeyBySelector.get(selector); 117 let unconditionalFilterForSelector = filterBySelector.get(selector);
131 if (typeof filterKey != "undefined") 118 if (unconditionalFilterForSelector)
132 { 119 {
133 this._addToFiltersByDomain(filterKey, filterByKey[filterKey]); 120 this._addToFiltersByDomain(unconditionalFilterForSelector);
134 filterKeyBySelector.delete(selector); 121 filterBySelector.delete(selector);
135 unconditionalSelectors = null; 122 unconditionalSelectors = null;
136 } 123 }
137 124 }
138 knownExceptions.add(filter.text); 125 else if (!(filter.domains || exceptions.has(filter.selector)))
126 {
127 // The new filter's selector is unconditionally applied to all domains
128 filterBySelector.set(filter.selector, filter);
129 unconditionalSelectors = null;
139 } 130 }
140 else 131 else
141 { 132 {
142 if (keyByFilter.has(filter)) 133 // The new filter's selector only applies to some domains
143 return; 134 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 } 135 }
160 136
137 knownFilters.add(filter);
161 FilterNotifier.emit("elemhideupdate"); 138 FilterNotifier.emit("elemhideupdate");
162 }, 139 },
163 140
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 /** 141 /**
185 * Removes an element hiding filter 142 * Removes an element hiding filter
186 * @param {ElemHideFilter} filter 143 * @param {ElemHideFilter} filter
Manish Jethani 2018/04/27 12:39:36 This is really {ElemHideBase}
187 */ 144 */
188 remove(filter) 145 remove(filter)
189 { 146 {
147 if (!knownFilters.has(filter))
148 return;
149
150 // Whitelisting filters
190 if (filter instanceof ElemHideException) 151 if (filter instanceof ElemHideException)
191 { 152 {
192 if (!knownExceptions.has(filter.text))
193 return;
194
195 let list = exceptions.get(filter.selector); 153 let list = exceptions.get(filter.selector);
196 let index = list.indexOf(filter); 154 let index = list.indexOf(filter);
197 if (index >= 0) 155 if (index >= 0)
198 list.splice(index, 1); 156 list.splice(index, 1);
199 knownExceptions.delete(filter.text);
200 } 157 }
158 // Unconditially applied element hiding filters
159 else if (filterBySelector.get(filter.selector) === filter)
160 {
161 filterBySelector.delete(filter.selector);
162 unconditionalSelectors = null;
163 }
164 // Conditionally applied element hiding filters
201 else 165 else
202 { 166 {
203 let key = keyByFilter.get(filter); 167 let domains = filter.domains || defaultDomains;
204 if (typeof key == "undefined") 168 for (let domain of domains.keys())
205 return; 169 {
206 170 let filters = filtersByDomain.get(domain);
207 delete filterByKey[key]; 171 if (filters)
208 keyByFilter.delete(filter); 172 filters.delete(filter);
209 this._removeFilterKey(key, filter); 173 }
210 } 174 }
211 175
176 knownFilters.delete(filter);
212 FilterNotifier.emit("elemhideupdate"); 177 FilterNotifier.emit("elemhideupdate");
213 }, 178 },
214 179
215 /** 180 /**
216 * Checks whether an exception rule is registered for a filter on a particular 181 * Checks whether an exception rule is registered for a filter on a particular
217 * domain. 182 * domain.
218 * @param {Filter} filter 183 * @param {Filter} filter
219 * @param {string} docDomain 184 * @param {string} docDomain
220 * @return {ElemHideException} 185 * @return {ElemHideException}
221 */ 186 */
222 getException(filter, docDomain) 187 getException(filter, docDomain)
223 { 188 {
224 let list = exceptions.get(filter.selector); 189 let list = exceptions.get(filter.selector);
225 if (!list) 190 if (!list)
226 return null; 191 return null;
227 192
228 for (let i = list.length - 1; i >= 0; i--) 193 for (let i = list.length - 1; i >= 0; i--)
229 { 194 {
230 if (list[i].isActiveOnDomain(docDomain)) 195 if (list[i].isActiveOnDomain(docDomain))
231 return list[i]; 196 return list[i];
232 } 197 }
233 198
234 return null; 199 return null;
235 }, 200 },
236 201
237 /** 202 /**
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. 203 * Returns a list of selectors that apply on each website unconditionally.
249 * @returns {string[]} 204 * @returns {string[]}
250 */ 205 */
251 getUnconditionalSelectors() 206 getUnconditionalSelectors()
252 { 207 {
253 if (!unconditionalSelectors) 208 if (!unconditionalSelectors)
254 unconditionalSelectors = [...filterKeyBySelector.keys()]; 209 unconditionalSelectors = [...filterBySelector.keys()];
255 return unconditionalSelectors.slice(); 210 return unconditionalSelectors.slice();
256 }, 211 },
257 212
258 /** 213 /**
259 * Constant used by getSelectorsForDomain to return all selectors applying to 214 * Constant used by getSelectorsForDomain to return all selectors applying to
260 * a particular hostname. 215 * a particular hostname.
261 */ 216 */
262 ALL_MATCHING: 0, 217 ALL_MATCHING: 0,
263 218
264 /** 219 /**
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
296 let seenFilters = new Set(); 251 let seenFilters = new Set();
297 let currentDomain = domain ? domain.toUpperCase() : ""; 252 let currentDomain = domain ? domain.toUpperCase() : "";
298 while (true) 253 while (true)
299 { 254 {
300 if (specificOnly && currentDomain == "") 255 if (specificOnly && currentDomain == "")
301 break; 256 break;
302 257
303 let filters = filtersByDomain.get(currentDomain); 258 let filters = filtersByDomain.get(currentDomain);
304 if (filters) 259 if (filters)
305 { 260 {
306 for (let [filterKey, filter] of filters) 261 for (let [filter, isIncluded] of filters)
Manish Jethani 2018/04/27 12:39:36 Just a thought, but I wonder if we could do this (
Manish Jethani 2018/04/27 12:41:58 Anyway, I'm all for not changing things here as pa
kzar 2018/04/27 17:01:50 On 2018/04/27 12:39:36, Manish Jethani wrote:
Manish Jethani 2018/04/28 16:22:17 That's what excludedFilterSets in the above code s
kzar 2018/04/30 10:11:46 Well no, excludedFilterSets contained the excluded
Manish Jethani 2018/04/30 17:55:21 Let's do that as part of the other patch.
307 { 262 {
308 if (seenFilters.has(filterKey)) 263 if (seenFilters.has(filter))
309 continue; 264 continue;
310 seenFilters.add(filterKey); 265 seenFilters.add(filter);
311 266
312 if (filter && !this.getException(filter, domain)) 267 if (isIncluded && !this.getException(filter, domain))
313 selectors.push(filter.selector); 268 selectors.push(filter.selector);
314 } 269 }
315 } 270 }
316 271
317 if (currentDomain == "") 272 if (currentDomain == "")
318 break; 273 break;
319 274
320 let nextDot = currentDomain.indexOf("."); 275 let nextDot = currentDomain.indexOf(".");
321 currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); 276 currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1);
322 } 277 }
323 278
324 return selectors; 279 return selectors;
325 } 280 }
326 }; 281 };
OLDNEW
« no previous file with comments | « no previous file | test/filterListener.js » ('j') | test/filterListener.js » ('J')

Powered by Google App Engine
This is Rietveld