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, firstOnly) |
| 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 if (firstOnly) |
| 65 break; |
| 66 } |
| 67 } |
| 68 return matches; |
| 69 } |
| 70 }; |
| 71 |
| 72 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc, hideElement
sFunc) |
45 { | 73 { |
46 this.window = window; | 74 this.window = window; |
47 this.getFiltersFunc = getFiltersFunc; | 75 this.getFiltersFunc = getFiltersFunc; |
48 this.addSelectorsFunc = addSelectorsFunc; | 76 this.addSelectorsFunc = addSelectorsFunc; |
| 77 this.hideElementsFunc = hideElementsFunc; |
49 } | 78 } |
50 | 79 |
51 ElemHideEmulation.prototype = { | 80 ElemHideEmulation.prototype = { |
52 stringifyStyle: function(style) | 81 stringifyStyle: function(style) |
53 { | 82 { |
54 var styles = []; | 83 var styles = []; |
55 for (var i = 0; i < style.length; i++) | 84 for (var i = 0; i < style.length; i++) |
56 { | 85 { |
57 var property = style.item(i); | 86 var property = style.item(i); |
58 var value = style.getPropertyValue(property); | 87 var value = style.getPropertyValue(property); |
(...skipping 28 matching lines...) Expand all Loading... |
87 if (!rules) | 116 if (!rules) |
88 return; | 117 return; |
89 | 118 |
90 for (var i = 0; i < rules.length; i++) | 119 for (var i = 0; i < rules.length; i++) |
91 { | 120 { |
92 var rule = rules[i]; | 121 var rule = rules[i]; |
93 if (rule.type != rule.STYLE_RULE) | 122 if (rule.type != rule.STYLE_RULE) |
94 continue; | 123 continue; |
95 | 124 |
96 var style = this.stringifyStyle(rule.style); | 125 var style = this.stringifyStyle(rule.style); |
97 for (var j = 0; j < this.patterns.length; j++) | 126 for (var j = 0; j < this.propSelPatterns.length; j++) |
98 { | 127 { |
99 var pattern = this.patterns[j]; | 128 var pattern = this.propSelPatterns[j]; |
100 if (pattern.regexp.test(style)) | 129 if (pattern.regexp.test(style)) |
101 { | 130 { |
102 var subSelectors = splitSelector(rule.selectorText); | 131 var subSelectors = splitSelector(rule.selectorText); |
103 for (var k = 0; k < subSelectors.length; k++) | 132 for (var k = 0; k < subSelectors.length; k++) |
104 { | 133 { |
105 var subSelector = subSelectors[k]; | 134 var subSelector = subSelectors[k]; |
106 selectors.push(pattern.prefix + subSelector + pattern.suffix); | 135 selectors.push(pattern.prefix + subSelector + pattern.suffix); |
107 filters.push(pattern.text); | 136 filters.push(pattern.text); |
108 } | 137 } |
109 } | 138 } |
110 } | 139 } |
111 } | 140 } |
112 }, | 141 }, |
113 | 142 |
| 143 findPseudoClassHasElements: function(stylesheets, elements, filters) |
| 144 { |
| 145 for (var i = 0; i < this.pseudoHasPatterns.length; i++) |
| 146 { |
| 147 var pattern = this.pseudoHasPatterns[i]; |
| 148 |
| 149 var haveEl = document.querySelectorAll(pattern.prefix); |
| 150 for (var j = 0; j < haveEl.length; j++) |
| 151 { |
| 152 var matched = pattern.elementMatcher.match(haveEl[j], !pattern.suffix); |
| 153 if (matched.length == 0) |
| 154 continue; |
| 155 |
| 156 if (pattern.suffix) |
| 157 { |
| 158 matched.forEach(function(e) |
| 159 { |
| 160 var sel = pattern.suffix; |
| 161 // XXX we should have a more elegant way |
| 162 // also startsWith isn't available in PhantomJS. |
| 163 if (sel.substr(0, 1) == ">") |
| 164 sel = sel.substr(1); |
| 165 var subElements = e.querySelectorAll(sel); |
| 166 for (var k = 0; k < subElements.length; k++) |
| 167 { |
| 168 elements.push(subElements[i]); |
| 169 filters.push(pattern.text); |
| 170 } |
| 171 }); |
| 172 } |
| 173 else |
| 174 { |
| 175 elements.push(haveEl[j]); |
| 176 filters.push(pattern.text); |
| 177 } |
| 178 } |
| 179 } |
| 180 }, |
| 181 |
114 addSelectors: function(stylesheets) | 182 addSelectors: function(stylesheets) |
115 { | 183 { |
116 var selectors = []; | 184 var selectors = []; |
117 var filters = []; | 185 var filters = []; |
118 for (var i = 0; i < stylesheets.length; i++) | 186 for (var i = 0; i < stylesheets.length; i++) |
119 this.findSelectors(stylesheets[i], selectors, filters); | 187 this.findSelectors(stylesheets[i], selectors, filters); |
120 this.addSelectorsFunc(selectors, filters); | 188 this.addSelectorsFunc(selectors, filters); |
121 }, | 189 }, |
122 | 190 |
| 191 hideElements: function(stylesheets) |
| 192 { |
| 193 var elements = []; |
| 194 var filters = []; |
| 195 this.findPseudoClassHasElements(stylesheets, elements, filters); |
| 196 this.hideElementsFunc(elements, filters); |
| 197 }, |
| 198 |
123 onLoad: function(event) | 199 onLoad: function(event) |
124 { | 200 { |
125 var stylesheet = event.target.sheet; | 201 var stylesheet = event.target.sheet; |
126 if (stylesheet) | 202 if (stylesheet) |
127 this.addSelectors([stylesheet]); | 203 this.addSelectors([stylesheet]); |
| 204 this.hideElements(); |
| 205 }, |
| 206 |
| 207 parsePattern: function(pattern) |
| 208 { |
| 209 // we should catch the :has() pseudo class first. |
| 210 var match = pseudoClassHasSelectorRegExp.exec(pattern.selector); |
| 211 if (match) |
| 212 { |
| 213 return { |
| 214 type: "has", |
| 215 text: pattern.text, |
| 216 elementMatcher: new PseudoHasMatcher(match[1]), |
| 217 prefix: pattern.selector.substr(0, match.index).trim(), |
| 218 suffix: pattern.selector.substr(match.index + match[0].length).trim() |
| 219 }; |
| 220 } |
| 221 |
| 222 match = propertySelectorRegExp.exec(pattern.selector); |
| 223 if (match) |
| 224 { |
| 225 var regexpString; |
| 226 var propertyExpression = match[2]; |
| 227 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && |
| 228 propertyExpression[propertyExpression.length - 1] == "/") |
| 229 regexpString = propertyExpression.slice(1, -1) |
| 230 .replace("\\x7B ", "{").replace("\\x7D ", "}"); |
| 231 else |
| 232 regexpString = filterToRegExp(propertyExpression); |
| 233 return { |
| 234 type: "props", |
| 235 text: pattern.text, |
| 236 regexp: new RegExp(regexpString, "i"), |
| 237 prefix: pattern.selector.substr(0, match.index), |
| 238 suffix: pattern.selector.substr(match.index + match[0].length) |
| 239 }; |
| 240 } |
128 }, | 241 }, |
129 | 242 |
130 apply: function() | 243 apply: function() |
131 { | 244 { |
132 this.getFiltersFunc(function(patterns) | 245 this.getFiltersFunc(function(patterns) |
133 { | 246 { |
134 this.patterns = []; | 247 this.propSelPatterns = []; |
| 248 this.pseudoHasPatterns = []; |
135 for (var i = 0; i < patterns.length; i++) | 249 for (var i = 0; i < patterns.length; i++) |
136 { | 250 { |
137 var pattern = patterns[i]; | 251 var pattern = patterns[i]; |
138 var match = propertySelectorRegExp.exec(pattern.selector); | 252 var parsed = this.parsePattern(pattern); |
139 if (!match) | 253 if (parsed == undefined) |
140 continue; | 254 continue; |
141 | 255 if (parsed.type == "props") |
142 var propertyExpression = match[2]; | 256 { |
143 var regexpString; | 257 this.propSelPatterns.push(parsed); |
144 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && | 258 } |
145 propertyExpression[propertyExpression.length - 1] == "/") | 259 else if (parsed.type == "has") |
146 regexpString = propertyExpression.slice(1, -1) | 260 { |
147 .replace("\\x7B ", "{").replace("\\x7D ", "}"); | 261 this.pseudoHasPatterns.push(parsed); |
148 else | 262 } |
149 regexpString = filterToRegExp(propertyExpression); | |
150 | |
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 } | 263 } |
158 | 264 |
159 if (this.patterns.length > 0) | 265 if (this.pseudoHasPatterns.length > 0 || this.propSelPatterns.length > 0) |
160 { | 266 { |
161 var document = this.window.document; | 267 var document = this.window.document; |
162 this.addSelectors(document.styleSheets); | 268 this.addSelectors(document.styleSheets); |
| 269 this.hideElements(); |
163 document.addEventListener("load", this.onLoad.bind(this), true); | 270 document.addEventListener("load", this.onLoad.bind(this), true); |
164 } | 271 } |
165 }.bind(this)); | 272 }.bind(this)); |
166 } | 273 } |
167 }; | 274 }; |
OLD | NEW |