Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: lib/content/elemHideEmulation.js

Issue 29714601: Issue 6437 - Skip elements not affected by DOM mutations (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Disable optimization for patterns containing sibling combinators Created May 11, 2018, 4:18 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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,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
Manish Jethani 2018/05/11 16:32:00 Well we're already using CSS selectors for element
+ // 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 +733,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 +757,27 @@
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;
+
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);
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld