OLD | NEW |
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 |
(...skipping 18 matching lines...) Expand all Loading... |
29 */ | 29 */ |
30 function positionInParent(node) | 30 function positionInParent(node) |
31 { | 31 { |
32 let {children} = node.parentNode; | 32 let {children} = node.parentNode; |
33 for (let i = 0; i < children.length; i++) | 33 for (let i = 0; i < children.length; i++) |
34 if (children[i] == node) | 34 if (children[i] == node) |
35 return i + 1; | 35 return i + 1; |
36 return 0; | 36 return 0; |
37 } | 37 } |
38 | 38 |
| 39 function getCachedPropertyValue(object, name, defaultValueFunc = () => {}) |
| 40 { |
| 41 let value = object[name]; |
| 42 if (value === undefined) |
| 43 { |
| 44 value = defaultValueFunc(); |
| 45 Object.defineProperty(object, name, {value}); |
| 46 } |
| 47 return value; |
| 48 } |
| 49 |
39 function makeSelector(node, selector) | 50 function makeSelector(node, selector) |
40 { | 51 { |
41 if (node == null) | 52 if (node == null) |
42 return null; | 53 return null; |
43 if (!node.parentElement) | 54 if (!node.parentElement) |
44 { | 55 { |
45 let newSelector = ":root"; | 56 let newSelector = ":root"; |
46 if (selector) | 57 if (selector) |
47 newSelector += " > " + selector; | 58 newSelector += " > " + selector; |
48 return newSelector; | 59 return newSelector; |
(...skipping 323 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
372 } | 383 } |
373 }, | 384 }, |
374 | 385 |
375 *getSelectors(prefix, subtree, styles) | 386 *getSelectors(prefix, subtree, styles) |
376 { | 387 { |
377 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) | 388 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) |
378 yield [selector, subtree]; | 389 yield [selector, subtree]; |
379 } | 390 } |
380 }; | 391 }; |
381 | 392 |
382 function isSelectorHidingOnlyPattern(pattern) | 393 function Pattern(selectors, text) |
383 { | 394 { |
384 return pattern.selectors.some(s => s.preferHideWithSelector) && | 395 this.selectors = selectors; |
385 !pattern.selectors.some(s => s.requiresHiding); | 396 this.text = text; |
386 } | 397 } |
387 | 398 |
388 function patternDependsOnStyles(pattern) | 399 Pattern.prototype = { |
389 { | 400 isSelectorHidingOnlyPattern() |
390 return pattern.selectors.some(s => s.dependsOnStyles); | 401 { |
391 } | 402 return getCachedPropertyValue( |
| 403 this, "_selectorHidingOnlyPattern", |
| 404 () => this.selectors.some(s => s.preferHideWithSelector) && |
| 405 !this.selectors.some(s => s.requiresHiding) |
| 406 ); |
| 407 }, |
392 | 408 |
393 function patternDependsOnDOM(pattern) | 409 get dependsOnStyles() |
394 { | 410 { |
395 return pattern.selectors.some(s => s.dependsOnDOM); | 411 return getCachedPropertyValue( |
396 } | 412 this, "_dependsOnStyles", () => this.selectors.some(s => s.dependsOnStyles
) |
| 413 ); |
| 414 }, |
397 | 415 |
398 function patternDependsOnStylesAndDOM(pattern) | 416 get dependsOnDOM() |
399 { | 417 { |
400 return pattern.selectors.some(s => s.dependsOnStyles && s.dependsOnDOM); | 418 return getCachedPropertyValue( |
401 } | 419 this, "_dependsOnDOM", () => this.selectors.some(s => s.dependsOnDOM) |
| 420 ); |
| 421 }, |
402 | 422 |
403 function patternMaybeDependsOnAttributes(pattern) | 423 get dependsOnStylesAndDOM() |
404 { | 424 { |
405 // Observe changes to attributes if either there's a plain selector that | 425 return getCachedPropertyValue( |
406 // looks like an ID selector, class selector, or attribute selector in one of | 426 this, "_dependsOnStylesAndDOM", |
407 // the patterns (e.g. "a[href='https://example.com/']") | 427 () => this.selectors.some(s => s.dependsOnStyles && s.dependsOnDOM) |
408 // or there's a properties selector nested inside a has selector | 428 ); |
409 // (e.g. "div:-abp-has(:-abp-properties(color: blue))") | 429 }, |
410 return pattern.selectors.some( | |
411 selector => selector.maybeDependsOnAttributes || | |
412 (selector instanceof HasSelector && | |
413 selector.dependsOnStyles) | |
414 ); | |
415 } | |
416 | 430 |
417 function patternDependsOnCharacterData(pattern) | 431 get maybeDependsOnAttributes() |
418 { | 432 { |
419 // Observe changes to character data only if there's a contains selector in | 433 // Observe changes to attributes if either there's a plain selector that |
420 // one of the patterns. | 434 // looks like an ID selector, class selector, or attribute selector in one |
421 return pattern.selectors.some(selector => selector.dependsOnCharacterData); | 435 // of the patterns (e.g. "a[href='https://example.com/']") or there's a |
422 } | 436 // 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 }, |
423 | 447 |
424 function patternMatchesMutationTypes(pattern, mutationTypes) | 448 get dependsOnCharacterData() |
425 { | 449 { |
426 return mutationTypes.has("childList") || | 450 // Observe changes to character data only if there's a contains selector in |
427 (mutationTypes.has("attributes") && | 451 // one of the patterns. |
428 patternMaybeDependsOnAttributes(pattern)) || | 452 return getCachedPropertyValue( |
429 (mutationTypes.has("characterData") && | 453 this, "_dependsOnCharacterData", |
430 patternDependsOnCharacterData(pattern)); | 454 () => this.selectors.some(selector => selector.dependsOnCharacterData) |
431 } | 455 ); |
| 456 }, |
| 457 |
| 458 matchesMutationTypes(mutationTypes) |
| 459 { |
| 460 return mutationTypes.has("childList") || |
| 461 (mutationTypes.has("attributes") && |
| 462 this.maybeDependsOnAttributes) || |
| 463 (mutationTypes.has("characterData") && |
| 464 this.dependsOnCharacterData); |
| 465 } |
| 466 }; |
432 | 467 |
433 function extractMutationTypes(mutations) | 468 function extractMutationTypes(mutations) |
434 { | 469 { |
435 let types = new Set(); | 470 let types = new Set(); |
436 | 471 |
437 for (let mutation of mutations) | 472 for (let mutation of mutations) |
438 { | 473 { |
439 types.add(mutation.type); | 474 types.add(mutation.type); |
440 | 475 |
441 // There are only 3 types of mutations: "attributes", "characterData", and | 476 // There are only 3 types of mutations: "attributes", "characterData", and |
442 // "childList". | 477 // "childList". |
443 if (types.size == 3) | 478 if (types.size == 3) |
444 break; | 479 break; |
445 } | 480 } |
446 | 481 |
447 return types; | 482 return types; |
448 } | 483 } |
449 | 484 |
450 function filterPatterns(patterns, {stylesheets, mutations}) | 485 function filterPatterns(patterns, {stylesheets, mutations}) |
451 { | 486 { |
452 if (!stylesheets && !mutations) | 487 if (!stylesheets && !mutations) |
453 return patterns.slice(); | 488 return patterns.slice(); |
454 | 489 |
455 let mutationTypes = mutations ? extractMutationTypes(mutations) : null; | 490 let mutationTypes = mutations ? extractMutationTypes(mutations) : null; |
456 | 491 |
457 return patterns.filter( | 492 return patterns.filter( |
458 pattern => (stylesheets && patternDependsOnStyles(pattern)) || | 493 pattern => (stylesheets && pattern.dependsOnStyles) || |
459 (mutations && patternDependsOnDOM(pattern) && | 494 (mutations && pattern.dependsOnDOM && |
460 patternMatchesMutationTypes(pattern, mutationTypes)) | 495 pattern.matchesMutationTypes(mutationTypes)) |
461 ); | 496 ); |
462 } | 497 } |
463 | 498 |
464 function shouldObserveAttributes(patterns) | 499 function shouldObserveAttributes(patterns) |
465 { | 500 { |
466 return patterns.some(patternMaybeDependsOnAttributes); | 501 return patterns.some(pattern => pattern.maybeDependsOnAttributes); |
467 } | 502 } |
468 | 503 |
469 function shouldObserveCharacterData(patterns) | 504 function shouldObserveCharacterData(patterns) |
470 { | 505 { |
471 return patterns.some(patternDependsOnCharacterData); | 506 return patterns.some(pattern => pattern.dependsOnCharacterData); |
472 } | 507 } |
473 | 508 |
474 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) | 509 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) |
475 { | 510 { |
476 this.document = document; | 511 this.document = document; |
477 this.addSelectorsFunc = addSelectorsFunc; | 512 this.addSelectorsFunc = addSelectorsFunc; |
478 this.hideElemsFunc = hideElemsFunc; | 513 this.hideElemsFunc = hideElemsFunc; |
479 this.observer = new MutationObserver(this.observe.bind(this)); | 514 this.observer = new MutationObserver(this.observe.bind(this)); |
480 } | 515 } |
481 | 516 |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
587 // do full processing. | 622 // do full processing. |
588 if (!stylesheets && !mutations) | 623 if (!stylesheets && !mutations) |
589 stylesheets = this.document.styleSheets; | 624 stylesheets = this.document.styleSheets; |
590 | 625 |
591 // If there are any DOM mutations and any of the patterns depends on both | 626 // If there are any DOM mutations and any of the patterns depends on both |
592 // style sheets and the DOM (e.g. -abp-has(-abp-properties)), find all the | 627 // style sheets and the DOM (e.g. -abp-has(-abp-properties)), find all the |
593 // rules in every style sheet in the document, because we need to run | 628 // rules in every style sheet in the document, because we need to run |
594 // querySelectorAll afterwards. On the other hand, if we only have patterns | 629 // querySelectorAll afterwards. On the other hand, if we only have patterns |
595 // that depend on either styles or DOM both not both | 630 // that depend on either styles or DOM both not both |
596 // (e.g. -abp-properties or -abp-contains), we can skip this part. | 631 // (e.g. -abp-properties or -abp-contains), we can skip this part. |
597 if (mutations && patterns.some(patternDependsOnStylesAndDOM)) | 632 if (mutations && patterns.some(pattern => pattern.dependsOnStylesAndDOM)) |
| 633 { |
598 stylesheets = this.document.styleSheets; | 634 stylesheets = this.document.styleSheets; |
| 635 } |
599 | 636 |
600 for (let stylesheet of stylesheets || []) | 637 for (let stylesheet of stylesheets || []) |
601 { | 638 { |
602 // Explicitly ignore third-party stylesheets to ensure consistent behavior | 639 // Explicitly ignore third-party stylesheets to ensure consistent behavior |
603 // between Firefox and Chrome. | 640 // between Firefox and Chrome. |
604 if (!this.isSameOrigin(stylesheet)) | 641 if (!this.isSameOrigin(stylesheet)) |
605 continue; | 642 continue; |
606 | 643 |
607 let rules = stylesheet.cssRules; | 644 let rules = stylesheet.cssRules; |
608 if (!rules) | 645 if (!rules) |
(...skipping 28 matching lines...) Expand all Loading... |
637 | 674 |
638 pattern = patterns.shift(); | 675 pattern = patterns.shift(); |
639 | 676 |
640 generator = evaluate(pattern.selectors, 0, "", | 677 generator = evaluate(pattern.selectors, 0, "", |
641 this.document, cssStyles); | 678 this.document, cssStyles); |
642 } | 679 } |
643 for (let selector of generator) | 680 for (let selector of generator) |
644 { | 681 { |
645 if (selector != null) | 682 if (selector != null) |
646 { | 683 { |
647 if (isSelectorHidingOnlyPattern(pattern)) | 684 if (pattern.isSelectorHidingOnlyPattern()) |
648 { | 685 { |
649 selectors.push(selector); | 686 selectors.push(selector); |
650 selectorFilters.push(pattern.text); | 687 selectorFilters.push(pattern.text); |
651 } | 688 } |
652 else | 689 else |
653 { | 690 { |
654 for (let element of this.document.querySelectorAll(selector)) | 691 for (let element of this.document.querySelectorAll(selector)) |
655 { | 692 { |
656 elements.push(element); | 693 elements.push(element); |
657 elementFilters.push(pattern.text); | 694 elementFilters.push(pattern.text); |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
780 this.queueFiltering(null, mutations); | 817 this.queueFiltering(null, mutations); |
781 }, | 818 }, |
782 | 819 |
783 apply(patterns) | 820 apply(patterns) |
784 { | 821 { |
785 this.patterns = []; | 822 this.patterns = []; |
786 for (let pattern of patterns) | 823 for (let pattern of patterns) |
787 { | 824 { |
788 let selectors = this.parseSelector(pattern.selector); | 825 let selectors = this.parseSelector(pattern.selector); |
789 if (selectors != null && selectors.length > 0) | 826 if (selectors != null && selectors.length > 0) |
790 this.patterns.push({selectors, text: pattern.text}); | 827 this.patterns.push(new Pattern(selectors, pattern.text)); |
791 } | 828 } |
792 | 829 |
793 if (this.patterns.length > 0) | 830 if (this.patterns.length > 0) |
794 { | 831 { |
795 this.queueFiltering(); | 832 this.queueFiltering(); |
796 this.observer.observe( | 833 this.observer.observe( |
797 this.document, | 834 this.document, |
798 { | 835 { |
799 childList: true, | 836 childList: true, |
800 attributes: shouldObserveAttributes(this.patterns), | 837 attributes: shouldObserveAttributes(this.patterns), |
801 characterData: shouldObserveCharacterData(this.patterns), | 838 characterData: shouldObserveCharacterData(this.patterns), |
802 subtree: true | 839 subtree: true |
803 } | 840 } |
804 ); | 841 ); |
805 this.document.addEventListener("load", this.onLoad.bind(this), true); | 842 this.document.addEventListener("load", this.onLoad.bind(this), true); |
806 } | 843 } |
807 } | 844 } |
808 }; | 845 }; |
809 | 846 |
810 exports.ElemHideEmulation = ElemHideEmulation; | 847 exports.ElemHideEmulation = ElemHideEmulation; |
OLD | NEW |