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

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

Issue 29714613: Issue 6434 - Reimplement positionInParent with indexOf (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Left Patch Set: Created March 5, 2018, 8:25 p.m.
Right Patch Set: Move findIndex up Created March 20, 2018, 11:34 a.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 | lib/coreUtils.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-present eyeo GmbH 3 * Copyright (C) 2006-present 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 "use strict"; 18 "use strict";
19 19
20 const {textToRegExp, filterToRegExp, splitSelector} = require("../common"); 20 const {textToRegExp, filterToRegExp, splitSelector} = require("../common");
21 const {findIndex} = require("../coreUtils"); 21 const {indexOf} = require("../coreUtils");
22 22
23 let MIN_INVOCATION_INTERVAL = 3000; 23 let MIN_INVOCATION_INTERVAL = 3000;
24 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50; 24 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50;
25 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; 25 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i;
26
27 function getCachedPropertyValue(object, name, defaultValueFunc = () => {})
28 {
29 let value = object[name];
30 if (typeof value == "undefined")
31 Object.defineProperty(object, name, {value: value = defaultValueFunc()});
32 return value;
33 }
26 34
27 /** Return position of node from parent. 35 /** Return position of node from parent.
28 * @param {Node} node the node to find the position of. 36 * @param {Node} node the node to find the position of.
29 * @return {number} One-based index like for :nth-child(), or 0 on error. 37 * @return {number} One-based index like for :nth-child(), or 0 on error.
30 */ 38 */
31 function positionInParent(node) 39 function positionInParent(node)
32 { 40 {
33 return findIndex(node.parentNode.children, child => child == node) + 1; 41 return indexOf(node.parentNode.children, node) + 1;
34 } 42 }
35 43
36 function makeSelector(node, selector) 44 function makeSelector(node, selector)
37 { 45 {
38 if (node == null) 46 if (node == null)
39 return null; 47 return null;
40 if (!node.parentElement) 48 if (!node.parentElement)
41 { 49 {
42 let newSelector = ":root"; 50 let newSelector = ":root";
43 if (selector) 51 if (selector)
(...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after
234 242
235 const incompletePrefixRegexp = /[\s>+~]$/; 243 const incompletePrefixRegexp = /[\s>+~]$/;
236 244
237 function HasSelector(selectors) 245 function HasSelector(selectors)
238 { 246 {
239 this._innerSelectors = selectors; 247 this._innerSelectors = selectors;
240 } 248 }
241 249
242 HasSelector.prototype = { 250 HasSelector.prototype = {
243 requiresHiding: true, 251 requiresHiding: true,
252 dependsOnDOM: true,
244 253
245 get dependsOnStyles() 254 get dependsOnStyles()
246 { 255 {
247 return this._innerSelectors.some(selector => selector.dependsOnStyles); 256 return this._innerSelectors.some(selector => selector.dependsOnStyles);
248 }, 257 },
249 258
250 get dependsOnCharacterData() 259 get dependsOnCharacterData()
251 { 260 {
252 return this._innerSelectors.some( 261 return this._innerSelectors.some(
253 selector => selector.dependsOnCharacterData 262 selector => selector.dependsOnCharacterData
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
296 } 305 }
297 }; 306 };
298 307
299 function ContainsSelector(textContent) 308 function ContainsSelector(textContent)
300 { 309 {
301 this._regexp = makeRegExpParameter(textContent); 310 this._regexp = makeRegExpParameter(textContent);
302 } 311 }
303 312
304 ContainsSelector.prototype = { 313 ContainsSelector.prototype = {
305 requiresHiding: true, 314 requiresHiding: true,
315 dependsOnDOM: true,
306 dependsOnCharacterData: true, 316 dependsOnCharacterData: true,
307 317
308 *getSelectors(prefix, subtree, styles) 318 *getSelectors(prefix, subtree, styles)
309 { 319 {
310 for (let element of this.getElements(prefix, subtree, styles)) 320 for (let element of this.getElements(prefix, subtree, styles))
311 yield [makeSelector(element, ""), subtree]; 321 yield [makeSelector(element, ""), subtree];
312 }, 322 },
313 323
314 *getElements(prefix, subtree, styles) 324 *getElements(prefix, subtree, styles)
315 { 325 {
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
367 } 377 }
368 }, 378 },
369 379
370 *getSelectors(prefix, subtree, styles) 380 *getSelectors(prefix, subtree, styles)
371 { 381 {
372 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) 382 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp))
373 yield [selector, subtree]; 383 yield [selector, subtree];
374 } 384 }
375 }; 385 };
376 386
377 function isSelectorHidingOnlyPattern(pattern) 387 function Pattern(selectors, text)
378 { 388 {
379 return pattern.selectors.some(s => s.preferHideWithSelector) && 389 this.selectors = selectors;
380 !pattern.selectors.some(s => s.requiresHiding); 390 this.text = text;
391 }
392
393 Pattern.prototype = {
394 isSelectorHidingOnlyPattern()
395 {
396 return getCachedPropertyValue(
397 this, "_selectorHidingOnlyPattern",
398 () => this.selectors.some(selector => selector.preferHideWithSelector) &&
399 !this.selectors.some(selector => selector.requiresHiding)
400 );
401 },
402
403 get dependsOnStyles()
404 {
405 return getCachedPropertyValue(
406 this, "_dependsOnStyles",
407 () => this.selectors.some(selector => selector.dependsOnStyles)
408 );
409 },
410
411 get dependsOnDOM()
412 {
413 return getCachedPropertyValue(
414 this, "_dependsOnDOM",
415 () => this.selectors.some(selector => selector.dependsOnDOM)
416 );
417 },
418
419 get dependsOnStylesAndDOM()
420 {
421 return getCachedPropertyValue(
422 this, "_dependsOnStylesAndDOM",
423 () => this.selectors.some(selector => selector.dependsOnStyles &&
424 selector.dependsOnDOM)
425 );
426 },
427
428 get maybeDependsOnAttributes()
429 {
430 // Observe changes to attributes if either there's a plain selector that
431 // looks like an ID selector, class selector, or attribute selector in one
432 // of the patterns (e.g. "a[href='https://example.com/']")
433 // or there's a properties selector nested inside a has selector
434 // (e.g. "div:-abp-has(:-abp-properties(color: blue))")
435 return getCachedPropertyValue(
436 this, "_maybeDependsOnAttributes",
437 () => this.selectors.some(
438 selector => selector.maybeDependsOnAttributes ||
439 (selector instanceof HasSelector &&
440 selector.dependsOnStyles)
441 )
442 );
443 },
444
445 get dependsOnCharacterData()
446 {
447 // Observe changes to character data only if there's a contains selector in
448 // one of the patterns.
449 return getCachedPropertyValue(
450 this, "_dependsOnCharacterData",
451 () => this.selectors.some(selector => selector.dependsOnCharacterData)
452 );
453 }
454 };
455
456 function filterPatterns(patterns, {stylesheets, mutations})
457 {
458 if (!stylesheets && !mutations)
459 return patterns.slice();
460
461 return patterns.filter(
462 pattern => (stylesheets && pattern.dependsOnStyles) ||
463 (mutations && pattern.dependsOnDOM)
464 );
381 } 465 }
382 466
383 function shouldObserveAttributes(patterns) 467 function shouldObserveAttributes(patterns)
384 { 468 {
385 // Observe changes to attributes if either there's a plain selector that 469 return patterns.some(pattern => pattern.maybeDependsOnAttributes);
386 // looks like an ID selector, class selector, or attribute selector in one of
387 // the patterns (e.g. "a[href='https://example.com/']")
388 // or there's a properties selector nested inside a has selector
389 // (e.g. "div:-abp-has(:-abp-properties(color: blue))")
390 return patterns.some(
391 pattern => pattern.selectors.some(
392 selector => selector.maybeDependsOnAttributes ||
393 (selector instanceof HasSelector &&
394 selector.dependsOnStyles)
395 )
396 );
397 } 470 }
398 471
399 function shouldObserveCharacterData(patterns) 472 function shouldObserveCharacterData(patterns)
400 { 473 {
401 // Observe changes to character data only if there's a contains selector in 474 return patterns.some(pattern => pattern.dependsOnCharacterData);
402 // one of the patterns.
403 return patterns.some(
404 pattern => pattern.selectors.some(
405 selector => selector.dependsOnCharacterData
406 )
407 );
408 } 475 }
409 476
410 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) 477 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc)
411 { 478 {
412 this.document = document; 479 this.document = document;
413 this.addSelectorsFunc = addSelectorsFunc; 480 this.addSelectorsFunc = addSelectorsFunc;
414 this.hideElemsFunc = hideElemsFunc; 481 this.hideElemsFunc = hideElemsFunc;
415 this.observer = new MutationObserver(this.observe.bind(this)); 482 this.observer = new MutationObserver(this.observe.bind(this));
416 } 483 }
417 484
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
492 return selectors; 559 return selectors;
493 }, 560 },
494 561
495 /** 562 /**
496 * Processes the current document and applies all rules to it. 563 * Processes the current document and applies all rules to it.
497 * @param {CSSStyleSheet[]} [stylesheets] 564 * @param {CSSStyleSheet[]} [stylesheets]
498 * The list of new stylesheets that have been added to the document and 565 * The list of new stylesheets that have been added to the document and
499 * made reprocessing necessary. This parameter shouldn't be passed in for 566 * made reprocessing necessary. This parameter shouldn't be passed in for
500 * the initial processing, all of document's stylesheets will be considered 567 * the initial processing, all of document's stylesheets will be considered
501 * then and all rules, including the ones not dependent on styles. 568 * then and all rules, including the ones not dependent on styles.
569 * @param {MutationRecord[]} [mutations]
570 * The list of DOM mutations that have been applied to the document and
571 * made reprocessing necessary. This parameter shouldn't be passed in for
572 * the initial processing, the entire document will be considered
573 * then and all rules, including the ones not dependent on the DOM.
502 * @param {function} [done] 574 * @param {function} [done]
503 * Callback to call when done. 575 * Callback to call when done.
504 */ 576 */
505 _addSelectors(stylesheets, done) 577 _addSelectors(stylesheets, mutations, done)
506 { 578 {
579 let patterns = filterPatterns(this.patterns, {stylesheets, mutations});
580
507 let selectors = []; 581 let selectors = [];
508 let selectorFilters = []; 582 let selectorFilters = [];
509 583
510 let elements = []; 584 let elements = [];
511 let elementFilters = []; 585 let elementFilters = [];
512 586
513 let cssStyles = []; 587 let cssStyles = [];
514 588
515 let stylesheetOnlyChange = !!stylesheets; 589 // If neither any style sheets nor any DOM mutations have been specified,
516 if (!stylesheets) 590 // do full processing.
591 if (!stylesheets && !mutations)
517 stylesheets = this.document.styleSheets; 592 stylesheets = this.document.styleSheets;
518 593
519 for (let stylesheet of stylesheets) 594 // If there are any DOM mutations and any of the patterns depends on both
595 // style sheets and the DOM (e.g. -abp-has(-abp-properties)), find all the
596 // rules in every style sheet in the document, because we need to run
597 // querySelectorAll afterwards. On the other hand, if we only have patterns
598 // that depend on either styles or DOM both not both
599 // (e.g. -abp-properties or -abp-contains), we can skip this part.
600 if (mutations && patterns.some(pattern => pattern.dependsOnStylesAndDOM))
601 stylesheets = this.document.styleSheets;
602
603 for (let stylesheet of stylesheets || [])
520 { 604 {
521 // Explicitly ignore third-party stylesheets to ensure consistent behavior 605 // Explicitly ignore third-party stylesheets to ensure consistent behavior
522 // between Firefox and Chrome. 606 // between Firefox and Chrome.
523 if (!this.isSameOrigin(stylesheet)) 607 if (!this.isSameOrigin(stylesheet))
524 continue; 608 continue;
525 609
526 let rules = stylesheet.cssRules; 610 let rules = stylesheet.cssRules;
527 if (!rules) 611 if (!rules)
528 continue; 612 continue;
529 613
530 for (let rule of rules) 614 for (let rule of rules)
531 { 615 {
532 if (rule.type != rule.STYLE_RULE) 616 if (rule.type != rule.STYLE_RULE)
533 continue; 617 continue;
534 618
535 cssStyles.push(stringifyStyle(rule)); 619 cssStyles.push(stringifyStyle(rule));
536 } 620 }
537 } 621 }
538 622
539 let patterns = this.patterns.slice();
540 let pattern = null; 623 let pattern = null;
541 let generator = null; 624 let generator = null;
542 625
543 let processPatterns = () => 626 let processPatterns = () =>
544 { 627 {
545 let cycleStart = performance.now(); 628 let cycleStart = performance.now();
546 629
547 if (!pattern) 630 if (!pattern)
548 { 631 {
549 if (!patterns.length) 632 if (!patterns.length)
550 { 633 {
551 this.addSelectorsFunc(selectors, selectorFilters); 634 if (selectors.length > 0)
552 this.hideElemsFunc(elements, elementFilters); 635 this.addSelectorsFunc(selectors, selectorFilters);
636 if (elements.length > 0)
637 this.hideElemsFunc(elements, elementFilters);
553 if (typeof done == "function") 638 if (typeof done == "function")
554 done(); 639 done();
555 return; 640 return;
556 } 641 }
557 642
558 pattern = patterns.shift(); 643 pattern = patterns.shift();
559 644
560 if (stylesheetOnlyChange &&
561 !pattern.selectors.some(selector => selector.dependsOnStyles))
562 {
563 pattern = null;
564 return processPatterns();
565 }
566 generator = evaluate(pattern.selectors, 0, "", 645 generator = evaluate(pattern.selectors, 0, "",
567 this.document, cssStyles); 646 this.document, cssStyles);
568 } 647 }
569 for (let selector of generator) 648 for (let selector of generator)
570 { 649 {
571 if (selector != null) 650 if (selector != null)
572 { 651 {
573 if (isSelectorHidingOnlyPattern(pattern)) 652 if (pattern.isSelectorHidingOnlyPattern())
574 { 653 {
575 selectors.push(selector); 654 selectors.push(selector);
576 selectorFilters.push(pattern.text); 655 selectorFilters.push(pattern.text);
577 } 656 }
578 else 657 else
579 { 658 {
580 for (let element of this.document.querySelectorAll(selector)) 659 for (let element of this.document.querySelectorAll(selector))
581 { 660 {
582 elements.push(element); 661 elements.push(element);
583 elementFilters.push(pattern.text); 662 elementFilters.push(pattern.text);
(...skipping 26 matching lines...) Expand all
610 }, 689 },
611 690
612 _filteringInProgress: false, 691 _filteringInProgress: false,
613 _lastInvocation: -MIN_INVOCATION_INTERVAL, 692 _lastInvocation: -MIN_INVOCATION_INTERVAL,
614 _scheduledProcessing: null, 693 _scheduledProcessing: null,
615 694
616 /** 695 /**
617 * Re-run filtering either immediately or queued. 696 * Re-run filtering either immediately or queued.
618 * @param {CSSStyleSheet[]} [stylesheets] 697 * @param {CSSStyleSheet[]} [stylesheets]
619 * new stylesheets to be processed. This parameter should be omitted 698 * new stylesheets to be processed. This parameter should be omitted
620 * for DOM modification (full reprocessing required). 699 * for full reprocessing.
700 * @param {MutationRecord[]} [mutations]
701 * new DOM mutations to be processed. This parameter should be omitted
702 * for full reprocessing.
621 */ 703 */
622 queueFiltering(stylesheets) 704 queueFiltering(stylesheets, mutations)
623 { 705 {
624 let completion = () => 706 let completion = () =>
625 { 707 {
626 this._lastInvocation = performance.now(); 708 this._lastInvocation = performance.now();
627 this._filteringInProgress = false; 709 this._filteringInProgress = false;
628 if (this._scheduledProcessing) 710 if (this._scheduledProcessing)
629 { 711 {
630 let newStylesheets = this._scheduledProcessing.stylesheets; 712 let params = Object.assign({}, this._scheduledProcessing);
631 this._scheduledProcessing = null; 713 this._scheduledProcessing = null;
632 this.queueFiltering(newStylesheets); 714 this.queueFiltering(params.stylesheets, params.mutations);
633 } 715 }
634 }; 716 };
635 717
636 if (this._scheduledProcessing) 718 if (this._scheduledProcessing)
637 { 719 {
638 if (!stylesheets) 720 if (!stylesheets && !mutations)
639 this._scheduledProcessing.stylesheets = null; 721 {
640 else if (this._scheduledProcessing.stylesheets) 722 this._scheduledProcessing = {};
641 this._scheduledProcessing.stylesheets.push(...stylesheets); 723 }
724 else
725 {
726 if (stylesheets)
727 {
728 if (!this._scheduledProcessing.stylesheets)
729 this._scheduledProcessing.stylesheets = [];
730 this._scheduledProcessing.stylesheets.push(...stylesheets);
731 }
732 if (mutations)
733 {
734 if (!this._scheduledProcessing.mutations)
735 this._scheduledProcessing.mutations = [];
736 this._scheduledProcessing.mutations.push(...mutations);
737 }
738 }
642 } 739 }
643 else if (this._filteringInProgress) 740 else if (this._filteringInProgress)
644 { 741 {
645 this._scheduledProcessing = {stylesheets}; 742 this._scheduledProcessing = {stylesheets, mutations};
646 } 743 }
647 else if (performance.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) 744 else if (performance.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL)
648 { 745 {
649 this._scheduledProcessing = {stylesheets}; 746 this._scheduledProcessing = {stylesheets, mutations};
650 setTimeout(() => 747 setTimeout(() =>
651 { 748 {
652 let newStylesheets = this._scheduledProcessing.stylesheets; 749 let params = Object.assign({}, this._scheduledProcessing);
653 this._filteringInProgress = true; 750 this._filteringInProgress = true;
654 this._scheduledProcessing = null; 751 this._scheduledProcessing = null;
655 this._addSelectors(newStylesheets, completion); 752 this._addSelectors(params.stylesheets, params.mutations, completion);
656 }, 753 },
657 MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); 754 MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation));
658 } 755 }
659 else if (this.document.readyState == "loading") 756 else if (this.document.readyState == "loading")
660 { 757 {
661 this._scheduledProcessing = {stylesheets}; 758 this._scheduledProcessing = {stylesheets, mutations};
662 let handler = () => 759 let handler = () =>
663 { 760 {
664 document.removeEventListener("DOMContentLoaded", handler); 761 document.removeEventListener("DOMContentLoaded", handler);
665 let newStylesheets = this._scheduledProcessing.stylesheets; 762 let params = Object.assign({}, this._scheduledProcessing);
666 this._filteringInProgress = true; 763 this._filteringInProgress = true;
667 this._scheduledProcessing = null; 764 this._scheduledProcessing = null;
668 this._addSelectors(newStylesheets, completion); 765 this._addSelectors(params.stylesheets, params.mutations, completion);
669 }; 766 };
670 document.addEventListener("DOMContentLoaded", handler); 767 document.addEventListener("DOMContentLoaded", handler);
671 } 768 }
672 else 769 else
673 { 770 {
674 this._filteringInProgress = true; 771 this._filteringInProgress = true;
675 this._addSelectors(stylesheets, completion); 772 this._addSelectors(stylesheets, mutations, completion);
676 } 773 }
677 }, 774 },
678 775
679 onLoad(event) 776 onLoad(event)
680 { 777 {
681 let stylesheet = event.target.sheet; 778 let stylesheet = event.target.sheet;
682 if (stylesheet) 779 if (stylesheet)
683 this.queueFiltering([stylesheet]); 780 this.queueFiltering([stylesheet]);
684 }, 781 },
685 782
686 observe(mutations) 783 observe(mutations)
687 { 784 {
688 this.queueFiltering(); 785 this.queueFiltering(null, mutations);
689 }, 786 },
690 787
691 apply(patterns) 788 apply(patterns)
692 { 789 {
693 this.patterns = []; 790 this.patterns = [];
694 for (let pattern of patterns) 791 for (let pattern of patterns)
695 { 792 {
696 let selectors = this.parseSelector(pattern.selector); 793 let selectors = this.parseSelector(pattern.selector);
697 if (selectors != null && selectors.length > 0) 794 if (selectors != null && selectors.length > 0)
698 this.patterns.push({selectors, text: pattern.text}); 795 this.patterns.push(new Pattern(selectors, pattern.text));
699 } 796 }
700 797
701 if (this.patterns.length > 0) 798 if (this.patterns.length > 0)
702 { 799 {
703 this.queueFiltering(); 800 this.queueFiltering();
704 this.observer.observe( 801 this.observer.observe(
705 this.document, 802 this.document,
706 { 803 {
707 childList: true, 804 childList: true,
708 attributes: shouldObserveAttributes(this.patterns), 805 attributes: shouldObserveAttributes(this.patterns),
709 characterData: shouldObserveCharacterData(this.patterns), 806 characterData: shouldObserveCharacterData(this.patterns),
710 subtree: true 807 subtree: true
711 } 808 }
712 ); 809 );
713 this.document.addEventListener("load", this.onLoad.bind(this), true); 810 this.document.addEventListener("load", this.onLoad.bind(this), true);
714 } 811 }
715 } 812 }
716 }; 813 };
717 814
718 exports.ElemHideEmulation = ElemHideEmulation; 815 exports.ElemHideEmulation = ElemHideEmulation;
LEFTRIGHT
« no previous file | lib/coreUtils.js » ('j') | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld