Index: lib/content/elemHideEmulation.js |
=================================================================== |
--- a/lib/content/elemHideEmulation.js |
+++ b/lib/content/elemHideEmulation.js |
@@ -189,30 +189,30 @@ |
return new RegExp(pattern, flags); |
} |
catch (e) |
{ |
} |
return null; |
} |
-function* evaluate(chain, index, prefix, subtree, styles) |
+function* evaluate(chain, index, prefix, subtree, styles, targets) |
{ |
if (index >= chain.length) |
{ |
yield prefix; |
return; |
} |
for (let [selector, element] of |
- chain[index].getSelectors(prefix, subtree, styles)) |
+ chain[index].getSelectors(prefix, subtree, styles, targets)) |
{ |
if (selector == null) |
yield null; |
else |
- yield* evaluate(chain, index + 1, selector, element, styles); |
+ yield* evaluate(chain, index + 1, selector, element, styles, targets); |
} |
// Just in case the getSelectors() generator above had to run some heavy |
// 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) |
@@ -223,18 +223,19 @@ |
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. |
* @param {StringifiedStyle[]} styles the stringified style objects. |
+ * @param {Node[]} [targets] the nodes we are interested in. |
*/ |
- *getSelectors(prefix, subtree, styles) |
+ *getSelectors(prefix, subtree, styles, targets) |
{ |
yield [prefix + this._selector, subtree]; |
} |
}; |
const incompletePrefixRegexp = /[\s>+~]$/; |
function HasSelector(selectors) |
@@ -260,38 +261,49 @@ |
get maybeDependsOnAttributes() |
{ |
return this._innerSelectors.some( |
selector => selector.maybeDependsOnAttributes |
); |
}, |
- *getSelectors(prefix, subtree, styles) |
+ *getSelectors(prefix, subtree, styles, targets) |
{ |
- for (let element of this.getElements(prefix, subtree, styles)) |
+ for (let element of this.getElements(prefix, subtree, styles, targets)) |
yield [makeSelector(element, ""), element]; |
}, |
/** |
* Generator function returning selected elements. |
* @param {string} prefix the prefix for the selector. |
* @param {Node} subtree the subtree we work on. |
* @param {StringifiedStyle[]} styles the stringified style objects. |
+ * @param {Node[]} [targets] the nodes we are interested in. |
*/ |
- *getElements(prefix, subtree, styles) |
+ *getElements(prefix, subtree, styles, targets) |
{ |
let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? |
prefix + "*" : prefix; |
let elements = scopedQuerySelectorAll(subtree, actualPrefix); |
if (elements) |
{ |
for (let element of elements) |
{ |
- let iter = evaluate(this._innerSelectors, 0, "", element, styles); |
+ // If the element is neither an ancestor nor a descendant of one of the |
+ // targets, we can skip it. |
+ if (targets && !targets.some(target => element.contains(target) || |
+ target.contains(element))) |
+ { |
+ yield null; |
+ continue; |
+ } |
+ |
+ let iter = evaluate(this._innerSelectors, 0, "", element, styles, |
+ targets); |
for (let selector of iter) |
{ |
if (selector == null) |
yield null; |
else if (scopedQuerySelector(element, selector)) |
yield element; |
} |
yield null; |
@@ -305,32 +317,39 @@ |
this._regexp = makeRegExpParameter(textContent); |
} |
ContainsSelector.prototype = { |
requiresHiding: true, |
dependsOnDOM: true, |
dependsOnCharacterData: true, |
- *getSelectors(prefix, subtree, styles) |
+ *getSelectors(prefix, subtree, styles, targets) |
{ |
- for (let element of this.getElements(prefix, subtree, styles)) |
+ for (let element of this.getElements(prefix, subtree, styles, targets)) |
yield [makeSelector(element, ""), subtree]; |
}, |
- *getElements(prefix, subtree, styles) |
+ *getElements(prefix, subtree, styles, targets) |
{ |
let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? |
prefix + "*" : prefix; |
let elements = scopedQuerySelectorAll(subtree, actualPrefix); |
if (elements) |
{ |
for (let element of elements) |
{ |
+ if (targets && !targets.some(target => element.contains(target) || |
+ target.contains(element))) |
+ { |
+ yield null; |
+ continue; |
+ } |
+ |
if (this._regexp && this._regexp.test(element.textContent)) |
yield element; |
else |
yield null; |
} |
} |
} |
}; |
@@ -367,17 +386,17 @@ |
} |
let idx = subSelector.lastIndexOf("::"); |
if (idx != -1) |
subSelector = subSelector.substr(0, idx); |
yield prefix + subSelector; |
} |
}, |
- *getSelectors(prefix, subtree, styles) |
+ *getSelectors(prefix, subtree, styles, targets) |
{ |
for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) |
yield [selector, subtree]; |
} |
}; |
function isSelectorHidingOnlyPattern(pattern) |
{ |
@@ -442,16 +461,47 @@ |
// "childList". |
if (types.size == 3) |
break; |
} |
return types; |
} |
+function extractMutationTargets(mutations) |
+{ |
+ if (!mutations) |
+ return null; |
+ |
+ let targets = new Set(); |
+ |
+ for (let mutation of mutations) |
+ { |
+ if (mutation.type == "childList") |
+ { |
+ // When new nodes are added, we're interested in the added nodes rather |
+ // than the parent. |
+ for (let node of mutation.addedNodes) |
+ targets.add(node); |
+ |
+ // Ideally we would also be interested in removed nodes, but since we |
+ // never unhide an element once hidden we can simply ignore any removed |
+ // nodes. Note that this will change once we start using CSS selectors |
+ // for -abp-has and -abp-contains, i.e. we'll have to remove the |
+ // selectors for any removed nodes. |
+ } |
+ else |
+ { |
+ targets.add(mutation.target); |
+ } |
+ } |
+ |
+ return [...targets]; |
+} |
+ |
function filterPatterns(patterns, {stylesheets, mutations}) |
{ |
if (!stylesheets && !mutations) |
return patterns.slice(); |
let mutationTypes = mutations ? extractMutationTypes(mutations) : null; |
return patterns.filter( |
@@ -612,16 +662,18 @@ |
{ |
if (rule.type != rule.STYLE_RULE) |
continue; |
cssStyles.push(stringifyStyle(rule)); |
} |
} |
+ let targets = extractMutationTargets(mutations); |
+ |
let pattern = null; |
let generator = null; |
let processPatterns = () => |
{ |
let cycleStart = performance.now(); |
if (!pattern) |
@@ -633,17 +685,17 @@ |
if (typeof done == "function") |
done(); |
return; |
} |
pattern = patterns.shift(); |
generator = evaluate(pattern.selectors, 0, "", |
- this.document, cssStyles); |
+ this.document, cssStyles, targets); |
} |
for (let selector of generator) |
{ |
if (selector != null) |
{ |
if (isSelectorHidingOnlyPattern(pattern)) |
{ |
selectors.push(selector); |