Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: chrome/content/elemHideEmulation.js

Issue 29383960: Issue 3143 - Filter elements with :-abp-has() (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore
Patch Set: Fixed several bugs. Cleanup code. Created March 17, 2017, 1:25 a.m.
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 | lib/filterClasses.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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 };
OLDNEW
« no previous file with comments | « no previous file | lib/filterClasses.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld