| LEFT | RIGHT |
| 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 | 21 |
| 22 let MIN_INVOCATION_INTERVAL = 3000; | 22 let MIN_INVOCATION_INTERVAL = 3000; |
| 23 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50; | 23 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50; |
| 24 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; | 24 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; |
| 25 | 25 |
| 26 function getCachedPropertyValue(object, name, defaultValueFunc = () => {}) |
| 27 { |
| 28 let value = object[name]; |
| 29 if (typeof value == "undefined") |
| 30 Object.defineProperty(object, name, {value: value = defaultValueFunc()}); |
| 31 return value; |
| 32 } |
| 33 |
| 26 /** Return position of node from parent. | 34 /** Return position of node from parent. |
| 27 * @param {Node} node the node to find the position of. | 35 * @param {Node} node the node to find the position of. |
| 28 * @return {number} One-based index like for :nth-child(), or 0 on error. | 36 * @return {number} One-based index like for :nth-child(), or 0 on error. |
| 29 */ | 37 */ |
| 30 function positionInParent(node) | 38 function positionInParent(node) |
| 31 { | 39 { |
| 32 let {children} = node.parentNode; | 40 let {children} = node.parentNode; |
| 33 for (let i = 0; i < children.length; i++) | 41 for (let i = 0; i < children.length; i++) |
| 34 if (children[i] == node) | 42 if (children[i] == node) |
| 35 return i + 1; | 43 return i + 1; |
| (...skipping 336 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 372 } | 380 } |
| 373 }, | 381 }, |
| 374 | 382 |
| 375 *getSelectors(prefix, subtree, styles) | 383 *getSelectors(prefix, subtree, styles) |
| 376 { | 384 { |
| 377 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) | 385 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) |
| 378 yield [selector, subtree]; | 386 yield [selector, subtree]; |
| 379 } | 387 } |
| 380 }; | 388 }; |
| 381 | 389 |
| 382 function isSelectorHidingOnlyPattern(pattern) | 390 function Pattern(selectors, text) |
| 383 { | 391 { |
| 384 return pattern.selectors.some(s => s.preferHideWithSelector) && | 392 this.selectors = selectors; |
| 385 !pattern.selectors.some(s => s.requiresHiding); | 393 this.text = text; |
| 386 } | 394 } |
| 387 | 395 |
| 388 function patternDependsOnStyles(pattern) | 396 Pattern.prototype = { |
| 389 { | 397 isSelectorHidingOnlyPattern() |
| 390 return pattern.selectors.some(s => s.dependsOnStyles); | 398 { |
| 391 } | 399 return getCachedPropertyValue( |
| 392 | 400 this, "_selectorHidingOnlyPattern", |
| 393 function patternDependsOnDOM(pattern) | 401 () => this.selectors.some(selector => selector.preferHideWithSelector) && |
| 394 { | 402 !this.selectors.some(selector => selector.requiresHiding) |
| 395 return pattern.selectors.some(s => s.dependsOnDOM); | 403 ); |
| 396 } | 404 }, |
| 397 | 405 |
| 398 function patternDependsOnStylesAndDOM(pattern) | 406 get dependsOnStyles() |
| 399 { | 407 { |
| 400 return pattern.selectors.some(s => s.dependsOnStyles && s.dependsOnDOM); | 408 return getCachedPropertyValue( |
| 401 } | 409 this, "_dependsOnStyles", |
| 410 () => this.selectors.some(selector => selector.dependsOnStyles) |
| 411 ); |
| 412 }, |
| 413 |
| 414 get dependsOnDOM() |
| 415 { |
| 416 return getCachedPropertyValue( |
| 417 this, "_dependsOnDOM", |
| 418 () => this.selectors.some(selector => selector.dependsOnDOM) |
| 419 ); |
| 420 }, |
| 421 |
| 422 get dependsOnStylesAndDOM() |
| 423 { |
| 424 return getCachedPropertyValue( |
| 425 this, "_dependsOnStylesAndDOM", |
| 426 () => this.selectors.some(selector => selector.dependsOnStyles && |
| 427 selector.dependsOnDOM) |
| 428 ); |
| 429 }, |
| 430 |
| 431 get maybeDependsOnAttributes() |
| 432 { |
| 433 // Observe changes to attributes if either there's a plain selector that |
| 434 // looks like an ID selector, class selector, or attribute selector in one |
| 435 // of the patterns (e.g. "a[href='https://example.com/']") |
| 436 // or there's a properties selector nested inside a has selector |
| 437 // (e.g. "div:-abp-has(:-abp-properties(color: blue))") |
| 438 return getCachedPropertyValue( |
| 439 this, "_maybeDependsOnAttributes", |
| 440 () => this.selectors.some( |
| 441 selector => selector.maybeDependsOnAttributes || |
| 442 (selector instanceof HasSelector && |
| 443 selector.dependsOnStyles) |
| 444 ) |
| 445 ); |
| 446 }, |
| 447 |
| 448 get dependsOnCharacterData() |
| 449 { |
| 450 // Observe changes to character data only if there's a contains selector in |
| 451 // one of the patterns. |
| 452 return getCachedPropertyValue( |
| 453 this, "_dependsOnCharacterData", |
| 454 () => this.selectors.some(selector => selector.dependsOnCharacterData) |
| 455 ); |
| 456 } |
| 457 }; |
| 402 | 458 |
| 403 function filterPatterns(patterns, {stylesheets, mutations}) | 459 function filterPatterns(patterns, {stylesheets, mutations}) |
| 404 { | 460 { |
| 405 if (stylesheets && !mutations) | 461 if (!stylesheets && !mutations) |
| 406 return patterns.filter(patternDependsOnStyles); | 462 return patterns.slice(); |
| 407 | 463 |
| 408 if (!stylesheets && mutations) | 464 return patterns.filter( |
| 409 return patterns.filter(patternDependsOnDOM); | 465 pattern => (stylesheets && pattern.dependsOnStyles) || |
| 410 | 466 (mutations && pattern.dependsOnDOM) |
| 411 return patterns.slice(); | 467 ); |
| 412 } | 468 } |
| 413 | 469 |
| 414 function shouldObserveAttributes(patterns) | 470 function shouldObserveAttributes(patterns) |
| 415 { | 471 { |
| 416 // Observe changes to attributes if either there's a plain selector that | 472 return patterns.some(pattern => pattern.maybeDependsOnAttributes); |
| 417 // looks like an ID selector, class selector, or attribute selector in one of | |
| 418 // the patterns (e.g. "a[href='https://example.com/']") | |
| 419 // or there's a properties selector nested inside a has selector | |
| 420 // (e.g. "div:-abp-has(:-abp-properties(color: blue))") | |
| 421 return patterns.some( | |
| 422 pattern => pattern.selectors.some( | |
| 423 selector => selector.maybeDependsOnAttributes || | |
| 424 (selector instanceof HasSelector && | |
| 425 selector.dependsOnStyles) | |
| 426 ) | |
| 427 ); | |
| 428 } | 473 } |
| 429 | 474 |
| 430 function shouldObserveCharacterData(patterns) | 475 function shouldObserveCharacterData(patterns) |
| 431 { | 476 { |
| 432 // Observe changes to character data only if there's a contains selector in | 477 return patterns.some(pattern => pattern.dependsOnCharacterData); |
| 433 // one of the patterns. | |
| 434 return patterns.some( | |
| 435 pattern => pattern.selectors.some( | |
| 436 selector => selector.dependsOnCharacterData | |
| 437 ) | |
| 438 ); | |
| 439 } | 478 } |
| 440 | 479 |
| 441 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) | 480 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) |
| 442 { | 481 { |
| 443 this.document = document; | 482 this.document = document; |
| 444 this.addSelectorsFunc = addSelectorsFunc; | 483 this.addSelectorsFunc = addSelectorsFunc; |
| 445 this.hideElemsFunc = hideElemsFunc; | 484 this.hideElemsFunc = hideElemsFunc; |
| 446 this.observer = new MutationObserver(this.observe.bind(this)); | 485 this.observer = new MutationObserver(this.observe.bind(this)); |
| 447 } | 486 } |
| 448 | 487 |
| (...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 554 // do full processing. | 593 // do full processing. |
| 555 if (!stylesheets && !mutations) | 594 if (!stylesheets && !mutations) |
| 556 stylesheets = this.document.styleSheets; | 595 stylesheets = this.document.styleSheets; |
| 557 | 596 |
| 558 // If there are any DOM mutations and any of the patterns depends on both | 597 // If there are any DOM mutations and any of the patterns depends on both |
| 559 // style sheets and the DOM (e.g. -abp-has(-abp-properties)), find all the | 598 // style sheets and the DOM (e.g. -abp-has(-abp-properties)), find all the |
| 560 // rules in every style sheet in the document, because we need to run | 599 // rules in every style sheet in the document, because we need to run |
| 561 // querySelectorAll afterwards. On the other hand, if we only have patterns | 600 // querySelectorAll afterwards. On the other hand, if we only have patterns |
| 562 // that depend on either styles or DOM both not both | 601 // that depend on either styles or DOM both not both |
| 563 // (e.g. -abp-properties or -abp-contains), we can skip this part. | 602 // (e.g. -abp-properties or -abp-contains), we can skip this part. |
| 564 if (mutations && patterns.some(patternDependsOnStylesAndDOM)) | 603 if (mutations && patterns.some(pattern => pattern.dependsOnStylesAndDOM)) |
| 565 stylesheets = this.document.styleSheets; | 604 stylesheets = this.document.styleSheets; |
| 566 | 605 |
| 567 for (let stylesheet of stylesheets || []) | 606 for (let stylesheet of stylesheets || []) |
| 568 { | 607 { |
| 569 // Explicitly ignore third-party stylesheets to ensure consistent behavior | 608 // Explicitly ignore third-party stylesheets to ensure consistent behavior |
| 570 // between Firefox and Chrome. | 609 // between Firefox and Chrome. |
| 571 if (!this.isSameOrigin(stylesheet)) | 610 if (!this.isSameOrigin(stylesheet)) |
| 572 continue; | 611 continue; |
| 573 | 612 |
| 574 let rules = stylesheet.cssRules; | 613 let rules = stylesheet.cssRules; |
| (...skipping 13 matching lines...) Expand all Loading... |
| 588 let generator = null; | 627 let generator = null; |
| 589 | 628 |
| 590 let processPatterns = () => | 629 let processPatterns = () => |
| 591 { | 630 { |
| 592 let cycleStart = performance.now(); | 631 let cycleStart = performance.now(); |
| 593 | 632 |
| 594 if (!pattern) | 633 if (!pattern) |
| 595 { | 634 { |
| 596 if (!patterns.length) | 635 if (!patterns.length) |
| 597 { | 636 { |
| 598 this.addSelectorsFunc(selectors, selectorFilters); | 637 if (selectors.length > 0) |
| 599 this.hideElemsFunc(elements, elementFilters); | 638 this.addSelectorsFunc(selectors, selectorFilters); |
| 639 if (elements.length > 0) |
| 640 this.hideElemsFunc(elements, elementFilters); |
| 600 if (typeof done == "function") | 641 if (typeof done == "function") |
| 601 done(); | 642 done(); |
| 602 return; | 643 return; |
| 603 } | 644 } |
| 604 | 645 |
| 605 pattern = patterns.shift(); | 646 pattern = patterns.shift(); |
| 606 | 647 |
| 607 generator = evaluate(pattern.selectors, 0, "", | 648 generator = evaluate(pattern.selectors, 0, "", |
| 608 this.document, cssStyles); | 649 this.document, cssStyles); |
| 609 } | 650 } |
| 610 for (let selector of generator) | 651 for (let selector of generator) |
| 611 { | 652 { |
| 612 if (selector != null) | 653 if (selector != null) |
| 613 { | 654 { |
| 614 if (isSelectorHidingOnlyPattern(pattern)) | 655 if (pattern.isSelectorHidingOnlyPattern()) |
| 615 { | 656 { |
| 616 selectors.push(selector); | 657 selectors.push(selector); |
| 617 selectorFilters.push(pattern.text); | 658 selectorFilters.push(pattern.text); |
| 618 } | 659 } |
| 619 else | 660 else |
| 620 { | 661 { |
| 621 for (let element of this.document.querySelectorAll(selector)) | 662 for (let element of this.document.querySelectorAll(selector)) |
| 622 { | 663 { |
| 623 elements.push(element); | 664 elements.push(element); |
| 624 elementFilters.push(pattern.text); | 665 elementFilters.push(pattern.text); |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 747 this.queueFiltering(null, mutations); | 788 this.queueFiltering(null, mutations); |
| 748 }, | 789 }, |
| 749 | 790 |
| 750 apply(patterns) | 791 apply(patterns) |
| 751 { | 792 { |
| 752 this.patterns = []; | 793 this.patterns = []; |
| 753 for (let pattern of patterns) | 794 for (let pattern of patterns) |
| 754 { | 795 { |
| 755 let selectors = this.parseSelector(pattern.selector); | 796 let selectors = this.parseSelector(pattern.selector); |
| 756 if (selectors != null && selectors.length > 0) | 797 if (selectors != null && selectors.length > 0) |
| 757 this.patterns.push({selectors, text: pattern.text}); | 798 this.patterns.push(new Pattern(selectors, pattern.text)); |
| 758 } | 799 } |
| 759 | 800 |
| 760 if (this.patterns.length > 0) | 801 if (this.patterns.length > 0) |
| 761 { | 802 { |
| 762 this.queueFiltering(); | 803 this.queueFiltering(); |
| 763 this.observer.observe( | 804 this.observer.observe( |
| 764 this.document, | 805 this.document, |
| 765 { | 806 { |
| 766 childList: true, | 807 childList: true, |
| 767 attributes: shouldObserveAttributes(this.patterns), | 808 attributes: shouldObserveAttributes(this.patterns), |
| 768 characterData: shouldObserveCharacterData(this.patterns), | 809 characterData: shouldObserveCharacterData(this.patterns), |
| 769 subtree: true | 810 subtree: true |
| 770 } | 811 } |
| 771 ); | 812 ); |
| 772 this.document.addEventListener("load", this.onLoad.bind(this), true); | 813 this.document.addEventListener("load", this.onLoad.bind(this), true); |
| 773 } | 814 } |
| 774 } | 815 } |
| 775 }; | 816 }; |
| 776 | 817 |
| 777 exports.ElemHideEmulation = ElemHideEmulation; | 818 exports.ElemHideEmulation = ElemHideEmulation; |
| LEFT | RIGHT |