Index: lib/content/elemHideEmulation.js |
=================================================================== |
--- a/lib/content/elemHideEmulation.js |
+++ b/lib/content/elemHideEmulation.js |
@@ -194,53 +194,55 @@ |
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) |
{ |
this._selector = selector; |
this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); |
this.dependsOnDOM = this.maybeDependsOnAttributes; |
+ this.maybeContainsSiblingCombinators = /[~+]/.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. |
* @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 +267,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 +322,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 +351,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 +403,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) |
{ |
@@ -449,16 +469,25 @@ |
// Observe changes to character data only if there's a contains selector in |
// one of the patterns. |
return getCachedPropertyValue( |
this, "_dependsOnCharacterData", |
() => this.selectors.some(selector => selector.dependsOnCharacterData) |
); |
}, |
+ get maybeContainsSiblingCombinators() |
+ { |
+ return getCachedPropertyValue( |
+ this, "_maybeContainsSiblingCombinators", |
+ () => this.selectors.some(selector => |
+ selector.maybeContainsSiblingCombinators) |
+ ); |
+ }, |
+ |
matchesMutationTypes(mutationTypes) |
{ |
let mutationTypeMatchMap = getCachedPropertyValue( |
this, "_mutationTypeMatchMap", |
() => new Map([ |
// All types of DOM-dependent patterns are affected by mutations of |
// type "childList". |
["childList", true], |
@@ -489,16 +518,41 @@ |
// "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); |
+ } |
+ 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 +727,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) |
@@ -695,18 +751,32 @@ |
this.hideElemsFunc(elements, elementFilters); |
if (typeof done == "function") |
done(); |
return; |
} |
pattern = patterns.shift(); |
+ let evaluationTargets = targets; |
+ |
+ // If the pattern appears to contain any sibling combinators, we can't |
+ // easily optimize based on the mutation targets. Since this is a |
+ // special case, skip the optimization. By setting it to null here we |
+ // make sure we process the entire DOM. |
+ if (pattern.maybeContainsSiblingCombinators) |
+ evaluationTargets = null; |
+ |
+ // Ignore mutation targets when using style sheets, because we may have |
+ // to update all the CSS selectors. |
+ if (!this.useInlineStyles) |
+ evaluationTargets = null; |
+ |
generator = evaluate(pattern.selectors, 0, "", |
- this.document, cssStyles); |
+ this.document, cssStyles, evaluationTargets); |
} |
for (let selector of generator) |
{ |
if (selector != null) |
{ |
if (!this.useInlineStyles) |
{ |
selectors.push(selector); |