| Index: lib/content/elemHideEmulation.js |
| =================================================================== |
| --- a/lib/content/elemHideEmulation.js |
| +++ b/lib/content/elemHideEmulation.js |
| @@ -194,30 +194,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) |
| @@ -229,18 +229,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) |
| @@ -265,38 +266,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; |
| @@ -309,23 +321,23 @@ |
| { |
| this._regexp = makeRegExpParameter(textContent); |
| } |
| ContainsSelector.prototype = { |
| 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) |
| { |
| @@ -338,16 +350,23 @@ |
| if (lastRoot && lastRoot.contains(element)) |
| { |
| yield null; |
| continue; |
| } |
| lastRoot = element; |
| + 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; |
| } |
| } |
| } |
| }; |
| @@ -383,17 +402,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 Pattern(selectors, text) |
| { |
| @@ -489,16 +508,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( |
| @@ -673,16 +723,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) |
| @@ -696,17 +748,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 (!this.useInlineStyles) |
| { |
| selectors.push(selector); |