OLD | NEW |
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 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
49 | 49 |
50 /** | 50 /** |
51 * This array caches the keys of filterBySelector table (selectors | 51 * This array caches the keys of filterBySelector table (selectors |
52 * which unconditionally apply on all domains). It will be null if the | 52 * which unconditionally apply on all domains). It will be null if the |
53 * cache needs to be rebuilt. | 53 * cache needs to be rebuilt. |
54 * @type {?string[]} | 54 * @type {?string[]} |
55 */ | 55 */ |
56 let unconditionalSelectors = null; | 56 let unconditionalSelectors = null; |
57 | 57 |
58 /** | 58 /** |
| 59 * The default style sheet that applies on all domains. This is based on the |
| 60 * value of <code>{@link unconditionalSelectors}</code>. |
| 61 * @type {?string} |
| 62 */ |
| 63 let defaultStyleSheet = null; |
| 64 |
| 65 /** |
59 * Map to be used instead when a filter has a blank domains property. | 66 * Map to be used instead when a filter has a blank domains property. |
60 * @type {Map.<string,boolean>} | 67 * @type {Map.<string,boolean>} |
61 * @const | 68 * @const |
62 */ | 69 */ |
63 let defaultDomains = new Map([["", true]]); | 70 let defaultDomains = new Map([["", true]]); |
64 | 71 |
65 /** | 72 /** |
66 * Set containing known element hiding filters | 73 * Set containing known element hiding filters |
67 * @type {Set.<ElemHideFilter>} | 74 * @type {Set.<ElemHideFilter>} |
68 */ | 75 */ |
(...skipping 24 matching lines...) Expand all Loading... |
93 * @returns {string[]} | 100 * @returns {string[]} |
94 */ | 101 */ |
95 function getUnconditionalSelectors() | 102 function getUnconditionalSelectors() |
96 { | 103 { |
97 if (!unconditionalSelectors) | 104 if (!unconditionalSelectors) |
98 unconditionalSelectors = [...filterBySelector.keys()]; | 105 unconditionalSelectors = [...filterBySelector.keys()]; |
99 | 106 |
100 return unconditionalSelectors; | 107 return unconditionalSelectors; |
101 } | 108 } |
102 | 109 |
| 110 /** |
| 111 * Returns the list of selectors that apply on a given domain from the subset |
| 112 * of filters that do not apply unconditionally on all domains. |
| 113 * |
| 114 * @param {string} domain The domain. |
| 115 * @param {boolean} [specificOnly=false] Whether selectors from generic filters |
| 116 * should be included. |
| 117 * |
| 118 * @returns {Array.<string>} The list of selectors. |
| 119 */ |
| 120 function getConditionalSelectorsForDomain(domain, specificOnly = false) |
| 121 { |
| 122 let selectors = []; |
| 123 |
| 124 let excluded = new Set(); |
| 125 let currentDomain = domain ? domain.replace(/\.+$/, "").toLowerCase() : ""; |
| 126 |
| 127 // This code is a performance hot-spot, which is why we've made certain |
| 128 // micro-optimisations. Please be careful before making changes. |
| 129 while (true) |
| 130 { |
| 131 if (specificOnly && currentDomain == "") |
| 132 break; |
| 133 |
| 134 let filters = filtersByDomain.get(currentDomain); |
| 135 if (filters) |
| 136 { |
| 137 for (let [filter, isIncluded] of filters) |
| 138 { |
| 139 if (!isIncluded) |
| 140 { |
| 141 excluded.add(filter); |
| 142 } |
| 143 else |
| 144 { |
| 145 let {selector} = filter; |
| 146 if ((excluded.size == 0 || !excluded.has(filter)) && |
| 147 !ElemHideExceptions.getException(selector, domain)) |
| 148 { |
| 149 selectors.push(selector); |
| 150 } |
| 151 } |
| 152 } |
| 153 } |
| 154 |
| 155 if (currentDomain == "") |
| 156 break; |
| 157 |
| 158 let nextDot = currentDomain.indexOf("."); |
| 159 currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); |
| 160 } |
| 161 |
| 162 return selectors; |
| 163 } |
| 164 |
| 165 /** |
| 166 * Returns the default style sheet that applies on all domains. |
| 167 * @returns {string} |
| 168 */ |
| 169 function getDefaultStyleSheet() |
| 170 { |
| 171 if (!defaultStyleSheet) |
| 172 defaultStyleSheet = createStyleSheet(getUnconditionalSelectors()); |
| 173 |
| 174 return defaultStyleSheet; |
| 175 } |
| 176 |
103 ElemHideExceptions.on("added", ({selector}) => | 177 ElemHideExceptions.on("added", ({selector}) => |
104 { | 178 { |
105 // If this is the first exception for a previously unconditionally applied | 179 // If this is the first exception for a previously unconditionally applied |
106 // element hiding selector we need to take care to update the lookups. | 180 // element hiding selector we need to take care to update the lookups. |
107 let unconditionalFilterForSelector = filterBySelector.get(selector); | 181 let unconditionalFilterForSelector = filterBySelector.get(selector); |
108 if (unconditionalFilterForSelector) | 182 if (unconditionalFilterForSelector) |
109 { | 183 { |
110 addToFiltersByDomain(unconditionalFilterForSelector); | 184 addToFiltersByDomain(unconditionalFilterForSelector); |
111 filterBySelector.delete(selector); | 185 filterBySelector.delete(selector); |
112 unconditionalSelectors = null; | 186 unconditionalSelectors = null; |
| 187 defaultStyleSheet = null; |
113 } | 188 } |
114 }); | 189 }); |
115 | 190 |
116 /** | 191 /** |
117 * Container for element hiding filters | 192 * Container for element hiding filters |
118 * @class | 193 * @class |
119 */ | 194 */ |
120 exports.ElemHide = { | 195 exports.ElemHide = { |
121 /** | 196 /** |
122 * Removes all known filters | 197 * Removes all known filters |
123 */ | 198 */ |
124 clear() | 199 clear() |
125 { | 200 { |
126 for (let collection of [filtersByDomain, filterBySelector, knownFilters]) | 201 for (let collection of [filtersByDomain, filterBySelector, knownFilters]) |
127 collection.clear(); | 202 collection.clear(); |
128 | 203 |
129 unconditionalSelectors = null; | 204 unconditionalSelectors = null; |
| 205 defaultStyleSheet = null; |
| 206 |
130 filterNotifier.emit("elemhideupdate"); | 207 filterNotifier.emit("elemhideupdate"); |
131 }, | 208 }, |
132 | 209 |
133 /** | 210 /** |
134 * Add a new element hiding filter | 211 * Add a new element hiding filter |
135 * @param {ElemHideFilter} filter | 212 * @param {ElemHideFilter} filter |
136 */ | 213 */ |
137 add(filter) | 214 add(filter) |
138 { | 215 { |
139 if (knownFilters.has(filter)) | 216 if (knownFilters.has(filter)) |
140 return; | 217 return; |
141 | 218 |
142 let {selector} = filter; | 219 let {selector} = filter; |
143 | 220 |
144 if (!(filter.domains || ElemHideExceptions.hasExceptions(selector))) | 221 if (!(filter.domains || ElemHideExceptions.hasExceptions(selector))) |
145 { | 222 { |
146 // The new filter's selector is unconditionally applied to all domains | 223 // The new filter's selector is unconditionally applied to all domains |
147 filterBySelector.set(selector, filter); | 224 filterBySelector.set(selector, filter); |
148 unconditionalSelectors = null; | 225 unconditionalSelectors = null; |
| 226 defaultStyleSheet = null; |
149 } | 227 } |
150 else | 228 else |
151 { | 229 { |
152 // The new filter's selector only applies to some domains | 230 // The new filter's selector only applies to some domains |
153 addToFiltersByDomain(filter); | 231 addToFiltersByDomain(filter); |
154 } | 232 } |
155 | 233 |
156 knownFilters.add(filter); | 234 knownFilters.add(filter); |
157 filterNotifier.emit("elemhideupdate"); | 235 filterNotifier.emit("elemhideupdate"); |
158 }, | 236 }, |
159 | 237 |
160 /** | 238 /** |
161 * Removes an element hiding filter | 239 * Removes an element hiding filter |
162 * @param {ElemHideFilter} filter | 240 * @param {ElemHideFilter} filter |
163 */ | 241 */ |
164 remove(filter) | 242 remove(filter) |
165 { | 243 { |
166 if (!knownFilters.has(filter)) | 244 if (!knownFilters.has(filter)) |
167 return; | 245 return; |
168 | 246 |
169 let {selector} = filter; | 247 let {selector} = filter; |
170 | 248 |
171 // Unconditially applied element hiding filters | 249 // Unconditially applied element hiding filters |
172 if (filterBySelector.get(selector) == filter) | 250 if (filterBySelector.get(selector) == filter) |
173 { | 251 { |
174 filterBySelector.delete(selector); | 252 filterBySelector.delete(selector); |
175 unconditionalSelectors = null; | 253 unconditionalSelectors = null; |
| 254 defaultStyleSheet = null; |
176 } | 255 } |
177 // Conditionally applied element hiding filters | 256 // Conditionally applied element hiding filters |
178 else | 257 else |
179 { | 258 { |
180 let domains = filter.domains || defaultDomains; | 259 let domains = filter.domains || defaultDomains; |
181 for (let domain of domains.keys()) | 260 for (let domain of domains.keys()) |
182 { | 261 { |
183 let filters = filtersByDomain.get(domain); | 262 let filters = filtersByDomain.get(domain); |
184 if (filters) | 263 if (filters) |
185 { | 264 { |
186 filters.delete(filter); | 265 filters.delete(filter); |
187 | 266 |
188 if (filters.size == 0) | 267 if (filters.size == 0) |
189 filtersByDomain.delete(domain); | 268 filtersByDomain.delete(domain); |
190 } | 269 } |
191 } | 270 } |
192 } | 271 } |
193 | 272 |
194 knownFilters.delete(filter); | 273 knownFilters.delete(filter); |
195 filterNotifier.emit("elemhideupdate"); | 274 filterNotifier.emit("elemhideupdate"); |
196 }, | 275 }, |
197 | 276 |
198 /** | 277 /** |
199 * Determines from the current filter list which selectors should be applied | 278 * @typedef {object} ElemHideStyleSheet |
200 * on a particular host name. | 279 * @property {string} code CSS code. |
201 * @param {string} domain | 280 * @property {Array.<string>} selectors List of selectors. |
202 * @param {boolean} [specificOnly] true if generic filters should not apply. | |
203 * @returns {string[]} List of selectors. | |
204 */ | 281 */ |
205 getSelectorsForDomain(domain, specificOnly = false) | 282 |
| 283 /** |
| 284 * Generates a style sheet for a given domain based on the current set of |
| 285 * filters. |
| 286 * |
| 287 * @param {string} domain The domain. |
| 288 * @param {boolean} [specificOnly=false] Whether selectors from generic |
| 289 * filters should be included. |
| 290 * |
| 291 * @returns {ElemHideStyleSheet} An object containing the CSS code and the |
| 292 * list of selectors. |
| 293 */ |
| 294 generateStyleSheetForDomain(domain, specificOnly = false) |
206 { | 295 { |
207 let selectors = []; | 296 let selectors = getConditionalSelectorsForDomain(domain, specificOnly); |
208 | 297 let code = specificOnly ? createStyleSheet(selectors) : |
209 let excluded = new Set(); | 298 (getDefaultStyleSheet() + createStyleSheet(selectors)); |
210 let currentDomain = domain ? domain.replace(/\.+$/, "").toLowerCase() : ""; | |
211 | |
212 // This code is a performance hot-spot, which is why we've made certain | |
213 // micro-optimisations. Please be careful before making changes. | |
214 while (true) | |
215 { | |
216 if (specificOnly && currentDomain == "") | |
217 break; | |
218 | |
219 let filters = filtersByDomain.get(currentDomain); | |
220 if (filters) | |
221 { | |
222 for (let [filter, isIncluded] of filters) | |
223 { | |
224 if (!isIncluded) | |
225 { | |
226 excluded.add(filter); | |
227 } | |
228 else | |
229 { | |
230 let {selector} = filter; | |
231 if ((excluded.size == 0 || !excluded.has(filter)) && | |
232 !ElemHideExceptions.getException(selector, domain)) | |
233 { | |
234 selectors.push(selector); | |
235 } | |
236 } | |
237 } | |
238 } | |
239 | |
240 if (currentDomain == "") | |
241 break; | |
242 | |
243 let nextDot = currentDomain.indexOf("."); | |
244 currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1); | |
245 } | |
246 | 299 |
247 if (!specificOnly) | 300 if (!specificOnly) |
248 selectors = getUnconditionalSelectors().concat(selectors); | 301 selectors = getUnconditionalSelectors().concat(selectors); |
249 | 302 |
250 return selectors; | 303 return {code, selectors}; |
251 } | 304 } |
252 }; | 305 }; |
253 | 306 |
254 /** | 307 /** |
255 * Splits a list of selectors into groups determined by the value of | 308 * Splits a list of selectors into groups determined by the value of |
256 * <code>{@link selectorGroupSize}</code>. | 309 * <code>{@link selectorGroupSize}</code>. |
257 * | 310 * |
258 * @param {Array.<string>} selectors | 311 * @param {Array.<string>} selectors |
259 * @yields {Array.<string>} | 312 * @yields {Array.<string>} |
260 */ | 313 */ |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
302 { | 355 { |
303 let styleSheet = ""; | 356 let styleSheet = ""; |
304 | 357 |
305 for (let selectorGroup of splitSelectors(selectors)) | 358 for (let selectorGroup of splitSelectors(selectors)) |
306 styleSheet += createRule(selectorGroup); | 359 styleSheet += createRule(selectorGroup); |
307 | 360 |
308 return styleSheet; | 361 return styleSheet; |
309 } | 362 } |
310 | 363 |
311 exports.createStyleSheet = createStyleSheet; | 364 exports.createStyleSheet = createStyleSheet; |
OLD | NEW |