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

Delta Between Two Patch Sets: chrome/content/elemHideEmulation.js

Issue 29383960: Issue 3143 - Filter elements with :-abp-has() (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore
Left Patch Set: Addressed the comments. Created June 7, 2017, 2:12 p.m.
Right Patch Set: Fix reportError and the error message Created June 13, 2017, 1:52 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « chrome/content/.eslintrc.json ('k') | test/browser/elemHideEmulation.js » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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-2017 eyeo GmbH 3 * Copyright (C) 2006-2017 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
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 /* globals filterToRegExp */ 18 /* globals filterToRegExp */
19 19
20 "use strict"; 20 "use strict";
21 21
22 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; 22 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i;
23 23
24 let reportError = () => {};
25
24 function splitSelector(selector) 26 function splitSelector(selector)
25 { 27 {
26 if (selector.indexOf(",") == -1) 28 if (selector.indexOf(",") == -1)
27 return [selector]; 29 return [selector];
28 30
29 let selectors = []; 31 let selectors = [];
30 let start = 0; 32 let start = 0;
31 let level = 0; 33 let level = 0;
32 let sep = ""; 34 let sep = "";
33 35
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
71 return i + 1; 73 return i + 1;
72 return 0; 74 return 0;
73 } 75 }
74 76
75 function makeSelector(node, selector) 77 function makeSelector(node, selector)
76 { 78 {
77 if (!node.parentElement) 79 if (!node.parentElement)
78 { 80 {
79 let newSelector = ":root"; 81 let newSelector = ":root";
80 if (selector) 82 if (selector)
81 newSelector += " > "; 83 newSelector += " > " + selector;
82 return newSelector + selector; 84 return newSelector;
83 } 85 }
84 let idx = positionInParent(node); 86 let idx = positionInParent(node);
85 if (idx > 0) 87 if (idx > 0)
86 { 88 {
87 let newSelector = `${node.tagName}:nth-child(${idx})`; 89 let newSelector = `${node.tagName}:nth-child(${idx})`;
88 if (selector) 90 if (selector)
89 newSelector += " > "; 91 newSelector += " > " + selector;
90 return makeSelector(node.parentElement, newSelector + selector); 92 return makeSelector(node.parentElement, newSelector);
91 } 93 }
92 94
93 return selector; 95 return selector;
94 } 96 }
95 97
96 function parseSelectorContent(content, startIndex) 98 function parseSelectorContent(content, startIndex)
97 { 99 {
98 let parens = 1; 100 let parens = 1;
99 let quote = null; 101 let quote = null;
100 let i = startIndex; 102 let i = startIndex;
(...skipping 17 matching lines...) Expand all
118 else if (c == ")") 120 else if (c == ")")
119 { 121 {
120 parens--; 122 parens--;
121 if (parens == 0) 123 if (parens == 0)
122 break; 124 break;
123 } 125 }
124 } 126 }
125 127
126 if (parens > 0) 128 if (parens > 0)
127 return null; 129 return null;
128 return {text: content.substr(startIndex, i - startIndex), end: i}; 130 return {text: content.substring(startIndex, i), end: i};
Wladimir Palant 2017/06/07 14:53:42 content.substring(startIndex, i) will do.
hub 2017/06/08 00:17:22 ok
129 } 131 }
130 132
131 /** Parse the selector 133 /** Parse the selector
132 * @param {string} selector the selector to parse 134 * @param {string} selector the selector to parse
133 * @return {Object} selectors is an array of objects, 135 * @return {Object} selectors is an array of objects,
134 * or null in case of errors. hide is true if we'll hide 136 * or null in case of errors. hide is true if we'll hide
135 * elements instead of styles.. 137 * elements instead of styles..
136 */ 138 */
137 function parseSelector(selector) 139 function parseSelector(selector)
138 { 140 {
139 if (selector.length == 0) 141 if (selector.length == 0)
140 return {selectors: [], hide: false}; 142 return [];
141 143
142 let match = abpSelectorRegexp.exec(selector); 144 let match = abpSelectorRegexp.exec(selector);
143 if (!match) 145 if (!match)
144 return {selectors: [new PlainSelector(selector)], hide: false}; 146 return [new PlainSelector(selector)];
145 147
146 let hide = false;
147 let selectors = []; 148 let selectors = [];
148 if (match.index > 0) 149 if (match.index > 0)
149 selectors.push(new PlainSelector(selector.substr(0, match.index))); 150 selectors.push(new PlainSelector(selector.substr(0, match.index)));
150 151
151 let startIndex = match.index + match[0].length; 152 let startIndex = match.index + match[0].length;
152 let content = parseSelectorContent(selector, startIndex); 153 let content = parseSelectorContent(selector, startIndex);
153 if (content == null) 154 if (!content)
154 { 155 {
155 console.error(new SyntaxError("Failed parsing AdBlock Plus " + 156 reportError(new SyntaxError("Failed to parse Adblock Plus " +
156 `selector ${selector}, didn't ` + 157 `selector ${selector}, ` +
157 "find closing parenthesis.")); 158 "due to unmatched parentheses."));
158 return {selectors: null, hide: false}; 159 return null;
159 } 160 }
160 if (match[1] == "properties") 161 if (match[1] == "properties")
161 selectors.push(new PropsSelector(content.text)); 162 selectors.push(new PropsSelector(content.text));
162 else if (match[1] == "has") 163 else if (match[1] == "has")
163 { 164 {
164 let hasSelector = new HasSelector(content.text); 165 let hasSelector = new HasSelector(content.text);
165 if (!hasSelector.valid()) 166 if (!hasSelector.valid())
166 return {selectors: null, hide: false}; 167 return null;
167 selectors.push(hasSelector); 168 selectors.push(hasSelector);
168 hide = true;
169 } 169 }
170 else 170 else
171 { 171 {
172 // this is an error, can't parse selector. 172 // this is an error, can't parse selector.
173 console.error(new SyntaxError("Failed parsing AdBlock Plus " + 173 reportError(new SyntaxError("Failed to parse Adblock Plus " +
174 `selector ${selector}, invalid ` + 174 `selector ${selector}, invalid ` +
175 `pseudo-class -abp-${match[1]}().`)); 175 `pseudo-class :-abp-${match[1]}().`));
176 return {selectors: null, hide: false}; 176 return null;
177 } 177 }
178 178
179 let suffix = parseSelector(selector.substr(content.end + 1)); 179 let suffix = parseSelector(selector.substr(content.end + 1));
180 if (suffix.selectors == null) 180 if (suffix == null)
181 return {selectors: null, hide: false}; 181 return null;
182 182
183 selectors.push(...suffix.selectors); 183 selectors.push(...suffix);
184 hide = hide || suffix.hide; 184
185 185 return selectors;
186 return {selectors, hide}; 186 }
187 } 187
188 188 /** Stringified style objects
189 function stringifyStyle(style) 189 * @typedef {Object} StringifiedStyle
190 * @property {string} style CSS style represented by a string.
191 * @property {string[]} subSelectors selectors the CSS properties apply to.
192 */
193
194 /**
195 * Produce a string representation of the stylesheet entry.
196 * @param {CSSStyleRule} rule the CSS style rule.
197 * @return {StringifiedStyle} the stringified style.
198 */
199 function stringifyStyle(rule)
190 { 200 {
191 let styles = []; 201 let styles = [];
192 for (let i = 0; i < style.length; i++) 202 for (let i = 0; i < rule.style.length; i++)
193 { 203 {
194 let property = style.item(i); 204 let property = rule.style.item(i);
195 let value = style.getPropertyValue(property); 205 let value = rule.style.getPropertyValue(property);
196 let priority = style.getPropertyPriority(property); 206 let priority = rule.style.getPropertyPriority(property);
197 styles.push(property + ": " + value + (priority ? " !" + priority : "") + 207 styles.push(`${property}: ${value}${priority ? " !" + priority : ""};`);
198 ";");
199 } 208 }
200 styles.sort(); 209 styles.sort();
201 return styles.join(" "); 210 return {
211 style: styles.join(" "),
212 subSelectors: splitSelector(rule.selectorText)
213 };
202 } 214 }
203 215
204 function* evaluate(chain, index, prefix, subtree, styles) 216 function* evaluate(chain, index, prefix, subtree, styles)
205 { 217 {
206 if (index >= chain.length) 218 if (index >= chain.length)
207 { 219 {
208 yield prefix; 220 yield prefix;
209 return; 221 return;
210 } 222 }
211 for (let [selector, element] of 223 for (let [selector, element] of
212 chain[index].getSelectors(prefix, subtree, styles)) 224 chain[index].getSelectors(prefix, subtree, styles))
213 yield* evaluate(chain, index + 1, selector, element, styles); 225 yield* evaluate(chain, index + 1, selector, element, styles);
214 } 226 }
215 227
216 function PlainSelector(selector) 228 function PlainSelector(selector)
217 { 229 {
218 this._selector = selector; 230 this._selector = selector;
219 } 231 }
220 232
221 PlainSelector.prototype = { 233 PlainSelector.prototype = {
222 /** 234 /**
223 * Generator function returning a pair of selector 235 * Generator function returning a pair of selector
224 * string and subtree. 236 * string and subtree.
225 * @param {string} prefix the prefix for the selector. 237 * @param {string} prefix the prefix for the selector.
226 * @param {Node} subtree the subtree we work on. 238 * @param {Node} subtree the subtree we work on.
227 * @param {...StringifiedStyle} styles the stringified style objects. 239 * @param {StringifiedStyle[]} styles the stringified style objects.
Wladimir Palant 2017/06/07 14:53:42 No, ...StringifiedStyle would mean there is a vari
hub 2017/06/08 00:17:21 Done.
228 */ 240 */
229 *getSelectors(prefix, subtree, styles) 241 *getSelectors(prefix, subtree, styles)
230 { 242 {
231 yield [prefix + this._selector, subtree]; 243 yield [prefix + this._selector, subtree];
232 } 244 }
233 }; 245 };
234 246
235 const incompletePrefixRegexp = /[\s>+~]$/; 247 const incompletePrefixRegexp = /[\s>+~]$/;
236 248
237 function HasSelector(selector) 249 function HasSelector(selector)
238 { 250 {
239 let inner = parseSelector(selector); 251 this._innerSelectors = parseSelector(selector);
240 this._innerSelectors = inner ? inner.selectors : null;
241 } 252 }
242 253
243 HasSelector.prototype = { 254 HasSelector.prototype = {
255 requiresHiding: true,
256
244 valid() 257 valid()
245 { 258 {
246 return this._innerSelectors != null; 259 return this._innerSelectors != null;
247 }, 260 },
248 261
249 *getSelectors(prefix, subtree, styles) 262 *getSelectors(prefix, subtree, styles)
250 { 263 {
251 for (let element of this.getElements(prefix, subtree, styles)) 264 for (let element of this.getElements(prefix, subtree, styles))
252 yield [makeSelector(element, ""), element]; 265 yield [makeSelector(element, ""), element];
253 }, 266 },
254 267
255 /** 268 /**
256 * Generator function returning selected elements. 269 * Generator function returning selected elements.
257 * @param {string} prefix the prefix for the selector. 270 * @param {string} prefix the prefix for the selector.
258 * @param {Node} subtree the subtree we work on. 271 * @param {Node} subtree the subtree we work on.
259 * @param {...StringifiedStyle} styles the stringified style objects. 272 * @param {StringifiedStyle[]} styles the stringified style objects.
260 */ 273 */
261 *getElements(prefix, subtree, styles) 274 *getElements(prefix, subtree, styles)
262 { 275 {
263 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? 276 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
264 prefix + "*" : prefix; 277 prefix + "*" : prefix;
265 let elements = subtree.querySelectorAll(actualPrefix); 278 let elements = subtree.querySelectorAll(actualPrefix);
266 for (let element of elements) 279 for (let element of elements)
267 { 280 {
268 let newPrefix = makeSelector(element, ""); 281 let newPrefix = makeSelector(element, "");
269 let iter = evaluate(this._innerSelectors, 0, newPrefix + " ", 282 let iter = evaluate(this._innerSelectors, 0, newPrefix + " ",
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
350 363
351 let rules = stylesheet.cssRules; 364 let rules = stylesheet.cssRules;
352 if (!rules) 365 if (!rules)
353 continue; 366 continue;
354 367
355 for (let rule of rules) 368 for (let rule of rules)
356 { 369 {
357 if (rule.type != rule.STYLE_RULE) 370 if (rule.type != rule.STYLE_RULE)
358 continue; 371 continue;
359 372
360 /* Stringified style objects 373 cssStyles.push(stringifyStyle(rule));
Wladimir Palant 2017/06/07 14:53:42 This should be /** (with two stars).
hub 2017/06/08 00:17:22 Done.
361 * @typedef {Object} StringifiedStyle
362 * @property {string} style stringified style
363 * @property {...string} subSelectors associated selectors
364 */
Wladimir Palant 2017/06/07 14:53:43 It would make sense to have this type declared bef
hub 2017/06/08 00:17:22 stringifyStyle return a string. This is where we b
365
366 let style = stringifyStyle(rule.style);
367 let subSelectors = splitSelector(rule.selectorText);
368 cssStyles.push({style, subSelectors});
369 } 374 }
370 } 375 }
371 376
377 let {document} = this.window;
372 for (let pattern of this.patterns) 378 for (let pattern of this.patterns)
373 { 379 {
374 for (let selector of evaluate(pattern.selectors, 380 for (let selector of evaluate(pattern.selectors,
375 0, "", document, cssStyles)) 381 0, "", document, cssStyles))
376 { 382 {
377 if (!pattern.hide) 383 if (!pattern.selectors.some(s => s.requiresHiding))
378 { 384 {
379 selectors.push(selector); 385 selectors.push(selector);
380 selectorFilters.push(pattern.text); 386 selectorFilters.push(pattern.text);
381 } 387 }
382 else 388 else
383 { 389 {
384 for (let element of document.querySelectorAll(selector)) 390 for (let element of document.querySelectorAll(selector))
385 { 391 {
386 elements.push(element); 392 elements.push(element);
387 elementFilters.push(pattern.text); 393 elementFilters.push(pattern.text);
(...skipping 10 matching lines...) Expand all
398 { 404 {
399 let stylesheet = event.target.sheet; 405 let stylesheet = event.target.sheet;
400 if (stylesheet) 406 if (stylesheet)
401 this.addSelectors([stylesheet]); 407 this.addSelectors([stylesheet]);
402 }, 408 },
403 409
404 apply() 410 apply()
405 { 411 {
406 this.getFiltersFunc(patterns => 412 this.getFiltersFunc(patterns =>
407 { 413 {
414 let oldReportError = reportError;
415 reportError = error => this.window.console.error(error);
416
408 this.patterns = []; 417 this.patterns = [];
409 for (let pattern of patterns) 418 for (let pattern of patterns)
410 { 419 {
411 let {selectors, hide} = parseSelector(pattern.selector); 420 let selectors = parseSelector(pattern.selector);
412 if (selectors != null && selectors.length > 0) 421 if (selectors != null && selectors.length > 0)
413 this.patterns.push({selectors, hide, text: pattern.text}); 422 this.patterns.push({selectors, text: pattern.text});
414 } 423 }
415 424
416 if (this.patterns.length > 0) 425 if (this.patterns.length > 0)
417 { 426 {
418 let {document} = this.window; 427 let {document} = this.window;
419 this.addSelectors(document.styleSheets); 428 this.addSelectors(document.styleSheets);
420 document.addEventListener("load", this.onLoad.bind(this), true); 429 document.addEventListener("load", this.onLoad.bind(this), true);
421 } 430 }
431 reportError = oldReportError;
422 }); 432 });
423 } 433 }
424 }; 434 };
LEFTRIGHT

Powered by Google App Engine
This is Rietveld