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

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

Issue 29490698: Issue 5422 - Properly build props selector (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Left Patch Set: Rebased on master Created July 19, 2017, 2:35 p.m.
Right Patch Set: Another nit Created Aug. 19, 2017, 1:13 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 | « no previous file | no next file » | 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 MIN_INVOCATION_INTERVAL = 3000;
22 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; 23 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i;
23 24
24 function splitSelector(selector) 25 function splitSelector(selector)
25 { 26 {
26 if (selector.indexOf(",") == -1) 27 if (selector.indexOf(",") == -1)
27 return [selector]; 28 return [selector];
28 29
29 let selectors = []; 30 let selectors = [];
30 let start = 0; 31 let start = 0;
31 let level = 0; 32 let level = 0;
(...skipping 10 matching lines...) Expand all
42 else if (sep == "") 43 else if (sep == "")
43 { 44 {
44 if (chr == '"' || chr == "'") 45 if (chr == '"' || chr == "'")
45 sep = chr; 46 sep = chr;
46 else if (chr == "(") // don't split between parentheses 47 else if (chr == "(") // don't split between parentheses
47 level++; // e.g. :matches(div,span) 48 level++; // e.g. :matches(div,span)
48 else if (chr == ")") 49 else if (chr == ")")
49 level = Math.max(0, level - 1); 50 level = Math.max(0, level - 1);
50 else if (chr == "," && level == 0) 51 else if (chr == "," && level == 0)
51 { 52 {
52 selectors.push(selector.substring(start, i).trim()); 53 selectors.push(selector.substring(start, i));
53 start = i + 1; 54 start = i + 1;
54 } 55 }
55 } 56 }
56 } 57 }
57 58
58 selectors.push(selector.substring(start).trim()); 59 selectors.push(selector.substring(start));
59 return selectors; 60 return selectors;
60 } 61 }
61 62
62 /** Return position of node from parent. 63 /** Return position of node from parent.
63 * @param {Node} node the node to find the position of. 64 * @param {Node} node the node to find the position of.
64 * @return {number} One-based index like for :nth-child(), or 0 on error. 65 * @return {number} One-based index like for :nth-child(), or 0 on error.
65 */ 66 */
66 function positionInParent(node) 67 function positionInParent(node)
67 { 68 {
68 let {children} = node.parentNode; 69 let {children} = node.parentNode;
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after
190 const incompletePrefixRegexp = /[\s>+~]$/; 191 const incompletePrefixRegexp = /[\s>+~]$/;
191 192
192 function HasSelector(selectors) 193 function HasSelector(selectors)
193 { 194 {
194 this._innerSelectors = selectors; 195 this._innerSelectors = selectors;
195 } 196 }
196 197
197 HasSelector.prototype = { 198 HasSelector.prototype = {
198 requiresHiding: true, 199 requiresHiding: true,
199 200
201 get dependsOnStyles()
202 {
203 return this._innerSelectors.some(selector => selector.dependsOnStyles);
204 },
205
200 *getSelectors(prefix, subtree, styles) 206 *getSelectors(prefix, subtree, styles)
201 { 207 {
202 for (let element of this.getElements(prefix, subtree, styles)) 208 for (let element of this.getElements(prefix, subtree, styles))
203 yield [makeSelector(element, ""), element]; 209 yield [makeSelector(element, ""), element];
204 }, 210 },
205 211
206 /** 212 /**
207 * Generator function returning selected elements. 213 * Generator function returning selected elements.
208 * @param {string} prefix the prefix for the selector. 214 * @param {string} prefix the prefix for the selector.
209 * @param {Node} subtree the subtree we work on. 215 * @param {Node} subtree the subtree we work on.
210 * @param {StringifiedStyle[]} styles the stringified style objects. 216 * @param {StringifiedStyle[]} styles the stringified style objects.
211 */ 217 */
212 *getElements(prefix, subtree, styles) 218 *getElements(prefix, subtree, styles)
213 { 219 {
214 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? 220 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
215 prefix + "*" : prefix; 221 prefix + "*" : prefix;
216 let elements = subtree.querySelectorAll(actualPrefix); 222 let elements = subtree.querySelectorAll(actualPrefix);
217 for (let element of elements) 223 for (let element of elements)
218 { 224 {
219 let newPrefix = makeSelector(element, ""); 225 let iter = evaluate(this._innerSelectors, 0, "", element, styles);
220 let iter = evaluate(this._innerSelectors, 0, newPrefix + " ",
221 element, styles);
222 for (let selector of iter) 226 for (let selector of iter)
223 // we insert a space between the two. It becomes a no-op if selector 227 if (element.querySelector(selector))
224 // doesn't have a combinator
225 if (subtree.querySelector(selector))
226 yield element; 228 yield element;
227 } 229 }
228 } 230 }
229 }; 231 };
230 232
231 function ContainsSelector(textContent) 233 function ContainsSelector(textContent)
232 { 234 {
233 this._text = textContent; 235 this._text = textContent;
234 } 236 }
235 237
(...skipping 27 matching lines...) Expand all
263 .replace("\\x7B ", "{").replace("\\x7D ", "}"); 265 .replace("\\x7B ", "{").replace("\\x7D ", "}");
264 } 266 }
265 else 267 else
266 regexpString = filterToRegExp(propertyExpression); 268 regexpString = filterToRegExp(propertyExpression);
267 269
268 this._regexp = new RegExp(regexpString, "i"); 270 this._regexp = new RegExp(regexpString, "i");
269 } 271 }
270 272
271 PropsSelector.prototype = { 273 PropsSelector.prototype = {
272 preferHideWithSelector: true, 274 preferHideWithSelector: true,
275 dependsOnStyles: true,
273 276
274 *findPropsSelectors(styles, prefix, regexp) 277 *findPropsSelectors(styles, prefix, regexp)
275 { 278 {
276 let actualPrefix = (prefix && !incompletePrefixRegexp.test(prefix)) ?
277 prefix + " " : prefix;
Wladimir Palant 2017/08/16 08:37:02 No, this is not what we want. Consider .abp-testsu
hub 2017/08/16 19:50:32 Acknowledged.
278 for (let style of styles) 279 for (let style of styles)
279 if (regexp.test(style.style)) 280 if (regexp.test(style.style))
280 for (let subSelector of style.subSelectors) 281 for (let subSelector of style.subSelectors)
281 { 282 {
282 if (subSelector == "*") 283 if (subSelector.startsWith("*") &&
283 subSelector = ""; 284 !incompletePrefixRegexp.test(prefix))
Wladimir Palant 2017/08/16 08:37:02 What if we have a filter like `:-abp-properties(fo
hub 2017/08/16 19:50:32 Done.
284 yield actualPrefix + subSelector; 285 {
286 subSelector = subSelector.substr(1);
287 }
288 let idx = subSelector.lastIndexOf("::");
289 if (idx != -1)
290 subSelector = subSelector.substr(0, idx);
291 yield prefix + subSelector;
285 } 292 }
286 }, 293 },
287 294
288 *getSelectors(prefix, subtree, styles) 295 *getSelectors(prefix, subtree, styles)
289 { 296 {
290 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) 297 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp))
291 yield [selector, subtree]; 298 yield [selector, subtree];
292 } 299 }
293 }; 300 };
294 301
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
374 { 381 {
375 this.window.console.error( 382 this.window.console.error(
376 new SyntaxError("Failed to parse Adblock Plus " + 383 new SyntaxError("Failed to parse Adblock Plus " +
377 `selector ${selector}, can't ` + 384 `selector ${selector}, can't ` +
378 "have a lonely :-abp-contains().")); 385 "have a lonely :-abp-contains()."));
379 return null; 386 return null;
380 } 387 }
381 return selectors; 388 return selectors;
382 }, 389 },
383 390
391 _lastInvocation: 0,
392
393 /**
394 * Processes the current document and applies all rules to it.
395 * @param {CSSStyleSheet[]} [stylesheets]
396 * The list of new stylesheets that have been added to the document and
397 * made reprocessing necessary. This parameter shouldn't be passed in for
398 * the initial processing, all of document's stylesheets will be considered
399 * then and all rules, including the ones not dependent on styles.
400 */
384 addSelectors(stylesheets) 401 addSelectors(stylesheets)
385 { 402 {
403 this._lastInvocation = Date.now();
404
386 let selectors = []; 405 let selectors = [];
387 let selectorFilters = []; 406 let selectorFilters = [];
388 407
389 let elements = []; 408 let elements = [];
390 let elementFilters = []; 409 let elementFilters = [];
391 410
392 let cssStyles = []; 411 let cssStyles = [];
412
413 let stylesheetOnlyChange = !!stylesheets;
414 if (!stylesheets)
415 stylesheets = this.window.document.styleSheets;
393 416
394 // Chrome < 51 doesn't have an iterable StyleSheetList 417 // Chrome < 51 doesn't have an iterable StyleSheetList
395 // https://issues.adblockplus.org/ticket/5381 418 // https://issues.adblockplus.org/ticket/5381
396 for (let i = 0; i < stylesheets.length; i++) 419 for (let i = 0; i < stylesheets.length; i++)
397 { 420 {
398 let stylesheet = stylesheets[i]; 421 let stylesheet = stylesheets[i];
399 // Explicitly ignore third-party stylesheets to ensure consistent behavior 422 // Explicitly ignore third-party stylesheets to ensure consistent behavior
400 // between Firefox and Chrome. 423 // between Firefox and Chrome.
401 if (!this.isSameOrigin(stylesheet)) 424 if (!this.isSameOrigin(stylesheet))
402 continue; 425 continue;
403 426
404 let rules = stylesheet.cssRules; 427 let rules = stylesheet.cssRules;
405 if (!rules) 428 if (!rules)
406 continue; 429 continue;
407 430
408 for (let rule of rules) 431 for (let rule of rules)
409 { 432 {
410 if (rule.type != rule.STYLE_RULE) 433 if (rule.type != rule.STYLE_RULE)
411 continue; 434 continue;
412 435
413 cssStyles.push(stringifyStyle(rule)); 436 cssStyles.push(stringifyStyle(rule));
414 } 437 }
415 } 438 }
416 439
417 let {document} = this.window; 440 let {document} = this.window;
418 for (let pattern of this.patterns) 441 for (let pattern of this.patterns)
419 { 442 {
443 if (stylesheetOnlyChange &&
444 !pattern.selectors.some(selector => selector.dependsOnStyles))
445 {
446 continue;
447 }
448
420 for (let selector of evaluate(pattern.selectors, 449 for (let selector of evaluate(pattern.selectors,
421 0, "", document, cssStyles)) 450 0, "", document, cssStyles))
422 { 451 {
423 if (pattern.selectors.some(s => s.preferHideWithSelector) && 452 if (pattern.selectors.some(s => s.preferHideWithSelector) &&
424 !pattern.selectors.some(s => s.requiresHiding)) 453 !pattern.selectors.some(s => s.requiresHiding))
425 { 454 {
426 selectors.push(selector); 455 selectors.push(selector);
427 selectorFilters.push(pattern.text); 456 selectorFilters.push(pattern.text);
428 } 457 }
429 else 458 else
430 { 459 {
431 for (let element of document.querySelectorAll(selector)) 460 for (let element of document.querySelectorAll(selector))
432 { 461 {
433 elements.push(element); 462 elements.push(element);
434 elementFilters.push(pattern.text); 463 elementFilters.push(pattern.text);
435 } 464 }
436 } 465 }
437 } 466 }
438 } 467 }
439 468
440 this.addSelectorsFunc(selectors, selectorFilters); 469 this.addSelectorsFunc(selectors, selectorFilters);
441 this.hideElemsFunc(elements, elementFilters); 470 this.hideElemsFunc(elements, elementFilters);
442 }, 471 },
443 472
473 _stylesheetQueue: null,
474
444 onLoad(event) 475 onLoad(event)
445 { 476 {
446 let stylesheet = event.target.sheet; 477 let stylesheet = event.target.sheet;
447 if (stylesheet) 478 if (stylesheet)
448 this.addSelectors([stylesheet]); 479 {
480 if (!this._stylesheetQueue &&
481 Date.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL)
482 {
483 this._stylesheetQueue = [];
484 this.window.setTimeout(() =>
485 {
486 let stylesheets = this._stylesheetQueue;
487 this._stylesheetQueue = null;
488 this.addSelectors(stylesheets);
489 }, MIN_INVOCATION_INTERVAL - (Date.now() - this._lastInvocation));
490 }
491
492 if (this._stylesheetQueue)
493 this._stylesheetQueue.push(stylesheet);
494 else
495 this.addSelectors([stylesheet]);
496 }
449 }, 497 },
450 498
451 apply() 499 apply()
452 { 500 {
453 this.getFiltersFunc(patterns => 501 this.getFiltersFunc(patterns =>
454 { 502 {
455 this.patterns = []; 503 this.patterns = [];
456 for (let pattern of patterns) 504 for (let pattern of patterns)
457 { 505 {
458 let selectors = this.parseSelector(pattern.selector); 506 let selectors = this.parseSelector(pattern.selector);
459 if (selectors != null && selectors.length > 0) 507 if (selectors != null && selectors.length > 0)
460 this.patterns.push({selectors, text: pattern.text}); 508 this.patterns.push({selectors, text: pattern.text});
461 } 509 }
462 510
463 if (this.patterns.length > 0) 511 if (this.patterns.length > 0)
464 { 512 {
465 let {document} = this.window; 513 let {document} = this.window;
466 this.addSelectors(document.styleSheets); 514 this.addSelectors();
467 document.addEventListener("load", this.onLoad.bind(this), true); 515 document.addEventListener("load", this.onLoad.bind(this), true);
468 } 516 }
469 }); 517 });
470 } 518 }
471 }; 519 };
LEFTRIGHT
« no previous file | no next file » | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld