| 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 |