| Index: lib/content/elemHideEmulation.js | 
| =================================================================== | 
| --- a/lib/content/elemHideEmulation.js | 
| +++ b/lib/content/elemHideEmulation.js | 
| @@ -190,16 +190,17 @@ | 
| // document.querySelectorAll() call which didn't produce any results, make | 
| // sure there is at least one point where execution can pause. | 
| yield null; | 
| } | 
| function PlainSelector(selector) | 
| { | 
| this._selector = selector; | 
| + this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); | 
| } | 
| PlainSelector.prototype = { | 
| /** | 
| * Generator function returning a pair of selector | 
| * string and subtree. | 
| * @param {string} prefix the prefix for the selector. | 
| * @param {Node} subtree the subtree we work on. | 
| @@ -221,16 +222,30 @@ | 
| HasSelector.prototype = { | 
| requiresHiding: true, | 
| get dependsOnStyles() | 
| { | 
| return this._innerSelectors.some(selector => selector.dependsOnStyles); | 
| }, | 
| + get dependsOnCharacterData() | 
| + { | 
| + return this._innerSelectors.some( | 
| + selector => selector.dependsOnCharacterData | 
| + ); | 
| + }, | 
| + | 
| + get maybeDependsOnAttributes() | 
| + { | 
| + return this._innerSelectors.some( | 
| + selector => selector.maybeDependsOnAttributes | 
| + ); | 
| + }, | 
| + | 
| *getSelectors(prefix, subtree, styles) | 
| { | 
| for (let element of this.getElements(prefix, subtree, styles)) | 
| yield [makeSelector(element, ""), element]; | 
| }, | 
| /** | 
| * Generator function returning selected elements. | 
| @@ -263,16 +278,17 @@ | 
| function ContainsSelector(textContent) | 
| { | 
| this._text = textContent; | 
| } | 
| ContainsSelector.prototype = { | 
| requiresHiding: true, | 
| + dependsOnCharacterData: true, | 
| *getSelectors(prefix, subtree, stylesheet) | 
| { | 
| for (let element of this.getElements(prefix, subtree, stylesheet)) | 
| yield [makeSelector(element, ""), subtree]; | 
| }, | 
| *getElements(prefix, subtree, stylesheet) | 
| @@ -339,16 +355,43 @@ | 
| }; | 
| function isSelectorHidingOnlyPattern(pattern) | 
| { | 
| return pattern.selectors.some(s => s.preferHideWithSelector) && | 
| !pattern.selectors.some(s => s.requiresHiding); | 
| } | 
| +function shouldObserveAttributes(patterns) | 
| +{ | 
| + // Observe changes to attributes if either there's a plain selector that | 
| + // looks like an ID selector, class selector, or attribute selector in one of | 
| + // the patterns (e.g. "a[href='https://example.com/']") | 
| + // or there's a properties selector nested inside a has selector | 
| + // (e.g. "div:-abp-has(:-abp-properties(color: blue))") | 
| + return patterns.some( | 
| + pattern => pattern.selectors.some( | 
| + selector => selector.maybeDependsOnAttributes || | 
| + selector instanceof HasSelector && | 
| + selector.dependsOnStyles | 
| + ) | 
| + ); | 
| +} | 
| + | 
| +function shouldObserveCharacterData(patterns) | 
| +{ | 
| + // Observe changes to character data only if there's a contains selector in | 
| + // one of the patterns. | 
| + return patterns.some( | 
| + pattern => pattern.selectors.some( | 
| + selector => selector.dependsOnCharacterData | 
| + ) | 
| + ); | 
| +} | 
| + | 
| function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) | 
| { | 
| this.document = document; | 
| this.addSelectorsFunc = addSelectorsFunc; | 
| this.hideElemsFunc = hideElemsFunc; | 
| this.observer = new MutationObserver(this.observe.bind(this)); | 
| } | 
| @@ -637,18 +680,18 @@ | 
| if (this.patterns.length > 0) | 
| { | 
| this.queueFiltering(); | 
| this.observer.observe( | 
| this.document, | 
| { | 
| childList: true, | 
| - attributes: true, | 
| - characterData: true, | 
| + attributes: shouldObserveAttributes(this.patterns), | 
| + characterData: shouldObserveCharacterData(this.patterns), | 
| subtree: true | 
| } | 
| ); | 
| this.document.addEventListener("load", this.onLoad.bind(this), true); | 
| } | 
| } | 
| }; |