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: Rebased on master. Handled all the feedback. Created June 1, 2017, 6:21 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) 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; 102 let i = startIndex;
101 for (i = 0; i < content.length; i++) 103 for (; i < content.length; i++)
102 { 104 {
103 let c = content[i]; 105 let c = content[i];
104 if (c == "\\") 106 if (c == "\\")
105 { 107 {
106 // Ignore escaped characters 108 // Ignore escaped characters
107 i++; 109 i++;
108 } 110 }
109 else if (quote) 111 else if (quote)
110 { 112 {
111 if (c == quote) 113 if (c == quote)
112 quote = null; 114 quote = null;
113 } 115 }
114 else if (c == "'" || c == '"') 116 else if (c == "'" || c == '"')
115 quote = c; 117 quote = c;
116 else if (c == "(") 118 else if (c == "(")
117 parens++; 119 parens++;
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(0, i), end: i}; 130 return {text: content.substring(startIndex, i), end: i};
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 * @param {Number} level the depth level. 0 is top
134 * @return {Object} selectors is an array of objects, 135 * @return {Object} selectors is an array of objects,
135 * 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
136 * elements instead of styles.. 137 * elements instead of styles..
137 */ 138 */
138 function parseSelector(selector, level = 0) 139 function parseSelector(selector)
Wladimir Palant 2017/06/07 08:32:58 Level parameter is no longer used.
hub 2017/06/07 14:15:08 I meant to remove it. it is gone now.
139 { 140 {
140 if (selector.length == 0) 141 if (selector.length == 0)
141 return {selectors: [], hide: false}; 142 return [];
142 143
143 let match = abpSelectorRegexp.exec(selector); 144 let match = abpSelectorRegexp.exec(selector);
144 if (!match) 145 if (!match)
145 return {selectors: [new PlainSelector(selector)], hide: false}; 146 return [new PlainSelector(selector)];
146 147
147 let hide = false;
148 let selectors = []; 148 let selectors = [];
149 let suffixStart = match.index; 149 if (match.index > 0)
150 if (suffixStart > 0)
151 selectors.push(new PlainSelector(selector.substr(0, match.index))); 150 selectors.push(new PlainSelector(selector.substr(0, match.index)));
Wladimir Palant 2017/06/07 08:32:58 I meant - suffixStart variable shouldn't be declar
hub 2017/06/07 14:15:08 done
152 151
153 let startIndex = match.index + match[0].length; 152 let startIndex = match.index + match[0].length;
154 let content = parseSelectorContent(selector.substr(startIndex)); 153 let content = parseSelectorContent(selector, startIndex);
155 if (content == null) 154 if (!content)
156 { 155 {
157 console.error(new SyntaxError("Failed parsing AdBlock Plus " + 156 reportError(new SyntaxError("Failed to parse Adblock Plus " +
158 `selector ${selector}, didn't ` + 157 `selector ${selector}, ` +
159 "find closing parenthesis.")); 158 "due to unmatched parentheses."));
160 return {selectors: null, hide: false}; 159 return null;
161 } 160 }
162 if (match[1] == "properties") 161 if (match[1] == "properties")
163 selectors.push(new PropsSelector(content.text)); 162 selectors.push(new PropsSelector(content.text));
164 else if (match[1] == "has") 163 else if (match[1] == "has")
165 { 164 {
166 let hasSelector = new HasSelector(content.text); 165 let hasSelector = new HasSelector(content.text);
167 if (!hasSelector.valid()) 166 if (!hasSelector.valid())
168 return {selectors: null, hide: false}; 167 return null;
169 selectors.push(hasSelector); 168 selectors.push(hasSelector);
170 hide = true;
171 } 169 }
172 else 170 else
173 { 171 {
174 // this is an error, can't parse selector. 172 // this is an error, can't parse selector.
175 console.error(new SyntaxError("Failed parsing AdBlock Plus " + 173 reportError(new SyntaxError("Failed to parse Adblock Plus " +
176 `selector ${selector}, invalid ` + 174 `selector ${selector}, invalid ` +
177 `pseudo-class -abp-${match[1]}().`)); 175 `pseudo-class :-abp-${match[1]}().`));
178 return {selectors: null, hide: false}; 176 return null;
179 } 177 }
180 178
181 suffixStart = startIndex + content.end + 1; 179 let suffix = parseSelector(selector.substr(content.end + 1));
182 180 if (suffix == null)
183 let suffix = parseSelector(selector.substr(suffixStart), level); 181 return null;
184 if (suffix.selectors == null) 182
185 return {selectors: null, hide: false}; 183 selectors.push(...suffix);
186 184
187 selectors.push(...suffix.selectors); 185 return selectors;
188 hide |= suffix.hide; 186 }
Wladimir Palant 2017/06/07 08:32:58 Using numerical operators on boolean values isn't
hub 2017/06/07 14:15:09 The problem with moving this out is that the decis
Wladimir Palant 2017/06/07 14:53:41 Assuming that I understood this sentence correctly
hub 2017/06/08 00:17:19 done
189 187
190 return {selectors, hide}; 188 /** Stringified style objects
191 } 189 * @typedef {Object} StringifiedStyle
192 190 * @property {string} style CSS style represented by a string.
193 function stringifyStyle(style) 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)
194 { 200 {
195 let styles = []; 201 let styles = [];
196 for (let i = 0; i < style.length; i++) 202 for (let i = 0; i < rule.style.length; i++)
197 { 203 {
198 let property = style.item(i); 204 let property = rule.style.item(i);
199 let value = style.getPropertyValue(property); 205 let value = rule.style.getPropertyValue(property);
200 let priority = style.getPropertyPriority(property); 206 let priority = rule.style.getPropertyPriority(property);
201 styles.push(property + ": " + value + (priority ? " !" + priority : "") + 207 styles.push(`${property}: ${value}${priority ? " !" + priority : ""};`);
202 ";");
203 } 208 }
204 styles.sort(); 209 styles.sort();
205 return styles.join(" "); 210 return {
211 style: styles.join(" "),
212 subSelectors: splitSelector(rule.selectorText)
213 };
206 } 214 }
207 215
208 function* evaluate(chain, index, prefix, subtree, styles) 216 function* evaluate(chain, index, prefix, subtree, styles)
209 { 217 {
210 if (index >= chain.length) 218 if (index >= chain.length)
211 { 219 {
212 yield prefix; 220 yield prefix;
213 return; 221 return;
214 } 222 }
215 for (let [selector, element] of 223 for (let [selector, element] of
216 chain[index].getSelectors(prefix, subtree, styles)) 224 chain[index].getSelectors(prefix, subtree, styles))
217 yield* evaluate(chain, index + 1, selector, element, styles); 225 yield* evaluate(chain, index + 1, selector, element, styles);
218 } 226 }
219 227
220 function PlainSelector(selector) 228 function PlainSelector(selector)
221 { 229 {
222 this._selector = selector; 230 this._selector = selector;
223 } 231 }
224 232
225 PlainSelector.prototype = { 233 PlainSelector.prototype = {
226 /** 234 /**
227 * Generator function returning a pair of selector 235 * Generator function returning a pair of selector
228 * string and subtree. 236 * string and subtree.
229 * @param {string} prefix the prefix for the selector. 237 * @param {string} prefix the prefix for the selector.
230 * @param {Node} subtree the subtree we work on. 238 * @param {Node} subtree the subtree we work on.
231 * @param {Array} styles the stringified stylesheet objects. 239 * @param {StringifiedStyle[]} styles the stringified style objects.
232 */ 240 */
233 *getSelectors(prefix, subtree, styles) 241 *getSelectors(prefix, subtree, styles)
234 { 242 {
235 yield [prefix + this._selector, subtree]; 243 yield [prefix + this._selector, subtree];
236 } 244 }
237 }; 245 };
238 246
239 const incompletePrefixRegexp = /[\s>+~]$/; 247 const incompletePrefixRegexp = /[\s>+~]$/;
240 248
241 function HasSelector(selector, level = 0) 249 function HasSelector(selector)
242 { 250 {
243 let inner = parseSelector(selector, level + 1); 251 this._innerSelectors = parseSelector(selector);
244 this._innerSelectors = inner ? inner.selectors : null;
245 } 252 }
246 253
247 HasSelector.prototype = { 254 HasSelector.prototype = {
255 requiresHiding: true,
256
248 valid() 257 valid()
249 { 258 {
250 return this._innerSelectors != null; 259 return this._innerSelectors != null;
251 }, 260 },
252 261
253 *getSelectors(prefix, subtree, styles) 262 *getSelectors(prefix, subtree, styles)
254 { 263 {
255 for (let element of this.getElements(prefix, subtree, styles)) 264 for (let element of this.getElements(prefix, subtree, styles))
256 yield [makeSelector(element, ""), element]; 265 yield [makeSelector(element, ""), element];
257 }, 266 },
258 267
259 /** 268 /**
260 * Generator function returning selected elements. 269 * Generator function returning selected elements.
261 * @param {string} prefix the prefix for the selector. 270 * @param {string} prefix the prefix for the selector.
262 * @param {Node} subtree the subtree we work on. 271 * @param {Node} subtree the subtree we work on.
263 * @param {Array} styles the stringified stylesheet objects. 272 * @param {StringifiedStyle[]} styles the stringified style objects.
264 */ 273 */
265 *getElements(prefix, subtree, styles) 274 *getElements(prefix, subtree, styles)
266 { 275 {
267 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? 276 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
268 prefix + "*" : prefix; 277 prefix + "*" : prefix;
269 let elements = subtree.querySelectorAll(actualPrefix); 278 let elements = subtree.querySelectorAll(actualPrefix);
270 for (let element of elements) 279 for (let element of elements)
271 { 280 {
272 let newPrefix = makeSelector(element, ""); 281 let newPrefix = makeSelector(element, "");
273 let iter = evaluate(this._innerSelectors, 0, newPrefix + " ", 282 let iter = evaluate(this._innerSelectors, 0, newPrefix + " ",
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
331 catch (e) 340 catch (e)
332 { 341 {
333 // Invalid URL, assume that it is first-party. 342 // Invalid URL, assume that it is first-party.
334 return true; 343 return true;
335 } 344 }
336 }, 345 },
337 346
338 addSelectors(stylesheets) 347 addSelectors(stylesheets)
339 { 348 {
340 let selectors = []; 349 let selectors = [];
341 let filters = []; 350 let selectorFilters = [];
342 351
343 let hideElements = []; 352 let elements = [];
344 let filterElements = []; 353 let elementFilters = [];
Wladimir Palant 2017/06/07 08:32:59 This array contains filters, not elements. How abo
hub 2017/06/07 14:15:08 make sense. done.
345 354
346 let cssStyles = []; 355 let cssStyles = [];
347 356
348 for (let stylesheet of stylesheets) 357 for (let stylesheet of stylesheets)
349 { 358 {
350 // Explicitly ignore third-party stylesheets to ensure consistent behavior 359 // Explicitly ignore third-party stylesheets to ensure consistent behavior
351 // between Firefox and Chrome. 360 // between Firefox and Chrome.
352 if (!this.isSameOrigin(stylesheet)) 361 if (!this.isSameOrigin(stylesheet))
353 continue; 362 continue;
354 363
355 let rules = stylesheet.cssRules; 364 let rules = stylesheet.cssRules;
356 if (!rules) 365 if (!rules)
357 continue; 366 continue;
358 367
359 for (let rule of rules) 368 for (let rule of rules)
360 { 369 {
361 if (rule.type != rule.STYLE_RULE) 370 if (rule.type != rule.STYLE_RULE)
362 continue; 371 continue;
363 372
364 let style = stringifyStyle(rule.style); 373 cssStyles.push(stringifyStyle(rule));
365 let subSelectors = splitSelector(rule.selectorText);
366 cssStyles.push({style, subSelectors});
367 } 374 }
368 } 375 }
369 376
377 let {document} = this.window;
370 for (let pattern of this.patterns) 378 for (let pattern of this.patterns)
379 {
371 for (let selector of evaluate(pattern.selectors, 380 for (let selector of evaluate(pattern.selectors,
372 0, "", document, cssStyles)) 381 0, "", document, cssStyles))
373 if (!pattern.hide) 382 {
383 if (!pattern.selectors.some(s => s.requiresHiding))
374 { 384 {
375 selectors.push(selector); 385 selectors.push(selector);
376 filters.push(pattern.text); 386 selectorFilters.push(pattern.text);
377 } 387 }
378 else 388 else
389 {
379 for (let element of document.querySelectorAll(selector)) 390 for (let element of document.querySelectorAll(selector))
Wladimir Palant 2017/06/07 08:32:59 Nit: we require brackets around multi-line blocks,
hub 2017/06/07 14:15:09 Done.
380 { 391 {
381 hideElements.push(element); 392 elements.push(element);
382 filterElements.push(pattern.text); 393 elementFilters.push(pattern.text);
383 } 394 }
384 395 }
385 this.addSelectorsFunc(selectors, filters); 396 }
386 this.hideElemsFunc(hideElements, filterElements); 397 }
398
399 this.addSelectorsFunc(selectors, selectorFilters);
400 this.hideElemsFunc(elements, elementFilters);
387 }, 401 },
388 402
389 onLoad(event) 403 onLoad(event)
390 { 404 {
391 let stylesheet = event.target.sheet; 405 let stylesheet = event.target.sheet;
392 if (stylesheet) 406 if (stylesheet)
393 this.addSelectors([stylesheet]); 407 this.addSelectors([stylesheet]);
394 }, 408 },
395 409
396 apply() 410 apply()
397 { 411 {
398 this.getFiltersFunc(patterns => 412 this.getFiltersFunc(patterns =>
399 { 413 {
414 let oldReportError = reportError;
415 reportError = error => this.window.console.error(error);
416
400 this.patterns = []; 417 this.patterns = [];
401 for (let pattern of patterns) 418 for (let pattern of patterns)
402 { 419 {
403 let {selectors, hide} = parseSelector(pattern.selector); 420 let selectors = parseSelector(pattern.selector);
404 if (selectors != null && selectors.length > 0) 421 if (selectors != null && selectors.length > 0)
405 this.patterns.push({selectors, hide, text: pattern.text}); 422 this.patterns.push({selectors, text: pattern.text});
406 } 423 }
407 424
408 if (this.patterns.length > 0) 425 if (this.patterns.length > 0)
409 { 426 {
410 let {document} = this.window; 427 let {document} = this.window;
411 this.addSelectors(document.styleSheets); 428 this.addSelectors(document.styleSheets);
412 document.addEventListener("load", this.onLoad.bind(this), true); 429 document.addEventListener("load", this.onLoad.bind(this), true);
413 } 430 }
431 reportError = oldReportError;
414 }); 432 });
415 } 433 }
416 }; 434 };
LEFTRIGHT

Powered by Google App Engine
This is Rietveld