| OLD | NEW |
| 1 // We are currently limited to ECMAScript 5 in this file, because it is being | 1 // We are currently limited to ECMAScript 5 in this file, because it is being |
| 2 // used in the browser tests. See https://issues.adblockplus.org/ticket/4796 | 2 // used in the browser tests. See https://issues.adblockplus.org/ticket/4796 |
| 3 | 3 |
| 4 var propertySelectorRegExp = /\[\-abp\-properties=(["'])([^"']+)\1\]/; | 4 var propertySelectorRegExp = /\[\-abp\-properties=(["'])([^"']+)\1\]/; |
| 5 var pseudoClassHasSelectorRegExp = /:has\((.*)\)/; |
| 5 | 6 |
| 6 function splitSelector(selector) | 7 function splitSelector(selector) |
| 7 { | 8 { |
| 8 if (selector.indexOf(",") == -1) | 9 if (selector.indexOf(",") == -1) |
| 9 return [selector]; | 10 return [selector]; |
| 10 | 11 |
| 11 var selectors = []; | 12 var selectors = []; |
| 12 var start = 0; | 13 var start = 0; |
| 13 var level = 0; | 14 var level = 0; |
| 14 var sep = ""; | 15 var sep = ""; |
| (...skipping 19 matching lines...) Expand all Loading... |
| 34 selectors.push(selector.substring(start, i)); | 35 selectors.push(selector.substring(start, i)); |
| 35 start = i + 1; | 36 start = i + 1; |
| 36 } | 37 } |
| 37 } | 38 } |
| 38 } | 39 } |
| 39 | 40 |
| 40 selectors.push(selector.substring(start)); | 41 selectors.push(selector.substring(start)); |
| 41 return selectors; | 42 return selectors; |
| 42 } | 43 } |
| 43 | 44 |
| 44 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc) | 45 // matcher for the pseudo CSS4 class :has |
| 46 // For those browser that don't have it yet. |
| 47 function PseudoHasMatcher(selector) |
| 48 { |
| 49 this.hasSelector = selector; |
| 50 } |
| 51 |
| 52 PseudoHasMatcher.prototype = { |
| 53 match: function(elem) |
| 54 { |
| 55 var matches = []; |
| 56 // look up for all elements that match the :has(). |
| 57 var children = elem.children; |
| 58 for (var i = 0; i < children.length; i++) |
| 59 { |
| 60 var hasElem = elem.querySelector(this.hasSelector); |
| 61 if (hasElem != null) |
| 62 { |
| 63 matches.push(hasElem); |
| 64 } |
| 65 } |
| 66 return matches; |
| 67 } |
| 68 }; |
| 69 |
| 70 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc, hideElement
sFunc) |
| 45 { | 71 { |
| 46 this.window = window; | 72 this.window = window; |
| 47 this.getFiltersFunc = getFiltersFunc; | 73 this.getFiltersFunc = getFiltersFunc; |
| 48 this.addSelectorsFunc = addSelectorsFunc; | 74 this.addSelectorsFunc = addSelectorsFunc; |
| 75 this.hideElementsFunc = hideElementsFunc; |
| 49 } | 76 } |
| 50 | 77 |
| 51 ElemHideEmulation.prototype = { | 78 ElemHideEmulation.prototype = { |
| 52 stringifyStyle: function(style) | 79 stringifyStyle: function(style) |
| 53 { | 80 { |
| 54 var styles = []; | 81 var styles = []; |
| 55 for (var i = 0; i < style.length; i++) | 82 for (var i = 0; i < style.length; i++) |
| 56 { | 83 { |
| 57 var property = style.item(i); | 84 var property = style.item(i); |
| 58 var value = style.getPropertyValue(property); | 85 var value = style.getPropertyValue(property); |
| (...skipping 28 matching lines...) Expand all Loading... |
| 87 if (!rules) | 114 if (!rules) |
| 88 return; | 115 return; |
| 89 | 116 |
| 90 for (var i = 0; i < rules.length; i++) | 117 for (var i = 0; i < rules.length; i++) |
| 91 { | 118 { |
| 92 var rule = rules[i]; | 119 var rule = rules[i]; |
| 93 if (rule.type != rule.STYLE_RULE) | 120 if (rule.type != rule.STYLE_RULE) |
| 94 continue; | 121 continue; |
| 95 | 122 |
| 96 var style = this.stringifyStyle(rule.style); | 123 var style = this.stringifyStyle(rule.style); |
| 97 for (var j = 0; j < this.patterns.length; j++) | 124 for (var j = 0; j < this.propSelPatterns.length; j++) |
| 98 { | 125 { |
| 99 var pattern = this.patterns[j]; | 126 var pattern = this.propSelPatterns[j]; |
| 100 if (pattern.regexp.test(style)) | 127 if (pattern.regexp.test(style)) |
| 101 { | 128 { |
| 102 var subSelectors = splitSelector(rule.selectorText); | 129 var subSelectors = splitSelector(rule.selectorText); |
| 103 for (var k = 0; k < subSelectors.length; k++) | 130 for (var k = 0; k < subSelectors.length; k++) |
| 104 { | 131 { |
| 105 var subSelector = subSelectors[k]; | 132 var subSelector = subSelectors[k]; |
| 106 selectors.push(pattern.prefix + subSelector + pattern.suffix); | 133 selectors.push(pattern.prefix + subSelector + pattern.suffix); |
| 107 filters.push(pattern.text); | 134 filters.push(pattern.text); |
| 108 } | 135 } |
| 109 } | 136 } |
| 110 } | 137 } |
| 111 } | 138 } |
| 112 }, | 139 }, |
| 113 | 140 |
| 141 findPseudoClassHasSelectors: function(selectors, filters) |
| 142 { |
| 143 for (var i = 0; i < this.pseudoHasPatterns.length; i++) |
| 144 { |
| 145 var pattern = this.pseudoHasPatterns[i]; |
| 146 |
| 147 var prefixes = document.querySelectorAll(pattern.prefix); |
| 148 for (var j = 0; j < prefixes.length; j++) |
| 149 { |
| 150 var matched = pattern.elementMatcher.match(prefixes[j]); |
| 151 if (matched.length == 0) |
| 152 { |
| 153 continue; |
| 154 } |
| 155 // XXX make sure we don't have performance problems here |
| 156 matched.forEach(function(e) |
| 157 { |
| 158 var subSelector = e.id; |
| 159 if (!subSelector) |
| 160 { |
| 161 var findUniqueId = function() |
| 162 { |
| 163 var id = "elemHideEmulationHide-" + |
| 164 Math.floor(Math.random() * 10000); |
| 165 if (!document.getElementById(id)) |
| 166 return id; |
| 167 return findUniqueId(); |
| 168 }; |
| 169 subSelector = findUniqueId(); |
| 170 e.id = subSelector; |
| 171 } |
| 172 var newSelector = pattern.prefix ? pattern.prefix + " > " : ""; |
| 173 newSelector += "#" + subSelector; |
| 174 newSelector += pattern.suffix ? " > " + pattern.suffix : ""; |
| 175 selectors.push(newSelector); |
| 176 filters.push(pattern.text); |
| 177 }); |
| 178 } |
| 179 } |
| 180 }, |
| 181 |
| 182 findPseudoClassHasElements: function(elements, filters) |
| 183 { |
| 184 for (var i = 0; i < this.pseudoHasPatterns.length; i++) |
| 185 { |
| 186 var pattern = this.pseudoHasPatterns[i]; |
| 187 |
| 188 var prefixes = document.querySelectorAll(pattern.prefix); |
| 189 for (var j = 0; j < prefixes.length; j++) |
| 190 { |
| 191 var matched = pattern.elementMatcher.match(prefixes[j]); |
| 192 if (matched.length == 0) |
| 193 { |
| 194 continue; |
| 195 } |
| 196 |
| 197 matched.forEach(function(e) |
| 198 { |
| 199 var toHide = []; |
| 200 var hidingPatterns = [] |
| 201 if (pattern.suffix) |
| 202 { |
| 203 hidingPatterns.push(pattern.text); |
| 204 console.log('pattern.suffix', pattern.suffix); |
| 205 var sel = pattern.suffix; |
| 206 // XXX we should have a more elegant way |
| 207 // also startsWith isn't available in PhantomJS. |
| 208 if (sel.substr(0, 1) == ">") |
| 209 { |
| 210 sel = sel.substr(1); |
| 211 } |
| 212 var subElements = e.querySelectorAll(sel); |
| 213 for (var k = 0; k < subElements.length; k++) |
| 214 { |
| 215 elements.push(subElements[i]); |
| 216 filters.push(pattern.text); |
| 217 } |
| 218 } |
| 219 else |
| 220 { |
| 221 elements.push(e); |
| 222 filters.push(pattern.text); |
| 223 } |
| 224 }); |
| 225 } |
| 226 } |
| 227 }, |
| 228 |
| 114 addSelectors: function(stylesheets) | 229 addSelectors: function(stylesheets) |
| 115 { | 230 { |
| 116 var selectors = []; | 231 var selectors = []; |
| 117 var filters = []; | 232 var filters = []; |
| 118 for (var i = 0; i < stylesheets.length; i++) | 233 for (var i = 0; i < stylesheets.length; i++) |
| 119 this.findSelectors(stylesheets[i], selectors, filters); | 234 this.findSelectors(stylesheets[i], selectors, filters); |
| 120 this.addSelectorsFunc(selectors, filters); | 235 this.addSelectorsFunc(selectors, filters); |
| 121 }, | 236 }, |
| 122 | 237 |
| 238 hideElements: function() |
| 239 { |
| 240 var elements = []; |
| 241 var filters = []; |
| 242 this.findPseudoClassHasElements(elements, filters); |
| 243 console.log("hideElements", elements.length); |
| 244 this.hideElementsFunc(elements, filters); |
| 245 }, |
| 246 |
| 123 onLoad: function(event) | 247 onLoad: function(event) |
| 124 { | 248 { |
| 125 var stylesheet = event.target.sheet; | 249 var stylesheet = event.target.sheet; |
| 126 if (stylesheet) | 250 if (stylesheet) |
| 127 this.addSelectors([stylesheet]); | 251 this.addSelectors([stylesheet]); |
| 252 this.hideElements(); |
| 128 }, | 253 }, |
| 129 | 254 |
| 130 apply: function() | 255 apply: function() |
| 131 { | 256 { |
| 132 this.getFiltersFunc(function(patterns) | 257 this.getFiltersFunc(function(patterns) |
| 133 { | 258 { |
| 134 this.patterns = []; | 259 this.propSelPatterns = []; |
| 260 this.pseudoHasPatterns = []; |
| 135 for (var i = 0; i < patterns.length; i++) | 261 for (var i = 0; i < patterns.length; i++) |
| 136 { | 262 { |
| 137 var pattern = patterns[i]; | 263 var pattern = patterns[i]; |
| 138 var match = propertySelectorRegExp.exec(pattern.selector); | 264 var match = propertySelectorRegExp.exec(pattern.selector); |
| 139 if (!match) | 265 if (match) |
| 140 continue; | 266 { |
| 267 var regexpMatcher; |
| 268 var regexpString; |
| 269 var propertyExpression = match[2]; |
| 270 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && |
| 271 propertyExpression[propertyExpression.length - 1] == "/") |
| 272 regexpString = propertyExpression.slice(1, -1) |
| 273 .replace("\\x7B ", "{").replace("\\x7D ", "}"); |
| 274 else |
| 275 regexpString = filterToRegExp(propertyExpression); |
| 276 regexpMatcher = new RegExp(regexpString, "i"); |
| 277 this.propSelPatterns.push({ |
| 278 text: pattern.text, |
| 279 regexp: regexpMatcher, |
| 280 prefix: pattern.selector.substr(0, match.index), |
| 281 suffix: pattern.selector.substr(match.index + match[0].length) |
| 282 }); |
| 283 } |
| 284 else |
| 285 { |
| 286 var elementMatcher; |
| 287 match = pseudoClassHasSelectorRegExp.exec(pattern.selector); |
| 288 if (!match) |
| 289 continue; |
| 141 | 290 |
| 142 var propertyExpression = match[2]; | 291 var pseudoHasSelector = match[1]; |
| 143 var regexpString; | 292 elementMatcher = new PseudoHasMatcher(pseudoHasSelector); |
| 144 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && | 293 this.pseudoHasPatterns.push({ |
| 145 propertyExpression[propertyExpression.length - 1] == "/") | 294 text: pattern.text, |
| 146 regexpString = propertyExpression.slice(1, -1) | 295 elementMatcher: elementMatcher, |
| 147 .replace("\\x7B ", "{").replace("\\x7D ", "}"); | 296 prefix: pattern.selector.substr(0, match.index).trim(), |
| 148 else | 297 suffix: pattern.selector.substr(match.index + match[0].length).trim(
) |
| 149 regexpString = filterToRegExp(propertyExpression); | 298 }); |
| 299 } |
| 150 | 300 |
| 151 this.patterns.push({ | |
| 152 text: pattern.text, | |
| 153 regexp: new RegExp(regexpString, "i"), | |
| 154 prefix: pattern.selector.substr(0, match.index), | |
| 155 suffix: pattern.selector.substr(match.index + match[0].length) | |
| 156 }); | |
| 157 } | 301 } |
| 158 | 302 |
| 159 if (this.patterns.length > 0) | 303 if (this.pseudoHasPatterns.length > 0 || this.propSelPatterns.length > 0) |
| 160 { | 304 { |
| 161 var document = this.window.document; | 305 var document = this.window.document; |
| 162 this.addSelectors(document.styleSheets); | 306 this.addSelectors(document.styleSheets); |
| 307 this.hideElements(); |
| 163 document.addEventListener("load", this.onLoad.bind(this), true); | 308 document.addEventListener("load", this.onLoad.bind(this), true); |
| 164 } | 309 } |
| 165 }.bind(this)); | 310 }.bind(this)); |
| 166 } | 311 } |
| 167 }; | 312 }; |
| OLD | NEW |