| 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); |
| } |
| } |
| }; |