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

Unified Diff: lib/content/elemHideEmulation.js

Issue 29728690: Issue 6504, 6458, 6446 - Update selectors based on DOM mutations (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Add special case check for documents with no extended selectors Created March 21, 2018, 3:28 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
@@ -469,23 +469,177 @@
return patterns.some(pattern => pattern.maybeDependsOnAttributes);
}
function shouldObserveCharacterData(patterns)
{
return patterns.some(pattern => pattern.dependsOnCharacterData);
}
+function CSSSelectorManager(root)
+{
+ this._root = root;
+
+ this.selectors = [];
+ this.filters = [];
+
+ this._lookup = new Set();
+
+ this._extended = false;
+}
+
+CSSSelectorManager.prototype = {
+ add(selectors, filters)
+ {
+ for (let i = 0; i < selectors.length; i++)
+ {
+ this.selectors.push(selectors[i]);
+ this.filters.push(filters[i]);
+
+ this._lookup.add(selectors[i]);
+
+ if (!this._extended && selectors[i].startsWith(":root > "))
+ this._extended = true;
+ }
+ },
+
+ has(selector)
+ {
+ return this._lookup.has(selector);
+ },
+
+ setAt(index, selector)
+ {
+ this._lookup.delete(this.selectors[index]);
+ this._lookup.add(selector);
+
+ this.selectors[index] = selector;
+ },
+
+ deleteAt(index)
+ {
+ this._lookup.delete(this.selectors[index]);
+
+ this.selectors.splice(index, 1);
+ this.filters.splice(index, 1);
+ },
+
+ update(mutations)
+ {
+ // If there are no extended CSS selectors (i.e. :-abp-*), there's nothing
+ // to update.
+ if (!this._extended || this.selectors.length == 0)
Manish Jethani 2018/03/21 15:35:26 This is useful. In EasyList the number of sites wi
+ return false;
+
+ let changed = false;
+
+ for (let mutation of mutations)
+ {
+ if (this._updateForMutation(mutation))
+ changed = true;
+ }
+
+ return changed;
+ },
+
+ _updateForMutation(mutation)
+ {
+ let {target} = mutation;
+
+ // Existing selectors aren't affected by non-childList mutations.
+ if (mutation.type != "childList" || !this._root.contains(target))
+ return false;
+
+ // Find the mutation index, i.e. the index in the parent element where the
+ // mutation has occurred.
+ let index = 0;
+ let ref = mutation.previousSibling;
+ if (ref)
+ {
+ if (!(ref instanceof Element))
+ ref = ref.previousElementSibling;
+
+ if (ref)
+ index = positionInParent(ref);
+ }
+
+ // Count the number of elements added and removed. Both can be non-zero in
+ // some cases, e.g. when Element.innerHTML is set on the parent element to
+ // overwrite all the existing child nodes.
+ let numAddedElements = 0;
+ let numRemovedElements = 0;
+
+ for (let node of mutation.addedNodes)
+ {
+ if (node instanceof Element)
+ numAddedElements++;
+ }
+
+ for (let node of mutation.removedNodes)
+ {
+ if (node instanceof Element)
+ numRemovedElements++;
+ }
+
+ let changed = false;
+
+ // Go through all the selectors and find any that match the mutation target.
+ let targetSelector = makeSelector(target) + " > ";
+ for (let i = 0; i < this.selectors.length; i++)
+ {
+ let selector = this.selectors[i];
+ if (selector.startsWith(targetSelector))
+ {
+ // If there's a match, extract the one-based child index.
+ let subSelector = selector.substring(targetSelector.length);
+ let match = /^[^:]*:nth-child\((\d+)\)/.exec(subSelector);
+ let nthIndex = match ? +match[1] : 0;
+
+ // If the child being targeted by the selector is before the mutation
+ // index (i.e. the one-based child index is not affected), do nothing.
+ if (nthIndex <= index)
+ continue;
+
+ nthIndex -= numRemovedElements;
+
+ // After adjusting for the number of removed elements, if the one-based
+ // child index appears to be lower than the mutation index, it means the
+ // child got removed. In that case we must delete the selector.
+ if (nthIndex <= index)
+ {
+ this.deleteAt(i--);
+ changed = true;
+ continue;
+ }
+
+ nthIndex += numAddedElements;
+
+ // If the one-based child index has changed, we must update the
+ // selector.
+ if (nthIndex != +match[1])
+ {
+ this.setAt(i, targetSelector +
+ subSelector.replace(match[1], nthIndex));
+ changed = true;
+ }
+ }
+ }
+
+ return changed;
+ }
+};
+
function ElemHideEmulation(addSelectorsFunc, hideElemsFunc)
{
this.document = document;
this.addSelectorsFunc = addSelectorsFunc;
this.hideElemsFunc = hideElemsFunc;
this.observer = new MutationObserver(this.observe.bind(this));
this.useInlineStyles = true;
+ this.css = new CSSSelectorManager(this.document);
}
ElemHideEmulation.prototype = {
isSameOrigin(stylesheet)
{
try
{
return new URL(stylesheet.href).origin == this.document.location.origin;
@@ -574,16 +728,19 @@
* then and all rules, including the ones not dependent on the DOM.
* @param {function} [done]
* Callback to call when done.
*/
_addSelectors(stylesheets, mutations, done)
{
let patterns = filterPatterns(this.patterns, {stylesheets, mutations});
+ if (patterns.length == 0)
+ return;
+
let selectors = [];
let selectorFilters = [];
let elements = [];
let elementFilters = [];
let cssStyles = [];
@@ -628,19 +785,24 @@
{
let cycleStart = performance.now();
if (!pattern)
{
if (!patterns.length)
{
if (selectors.length > 0)
- this.addSelectorsFunc(selectors, selectorFilters);
+ {
+ this.css.add(selectors, selectorFilters);
+ this.addSelectorsFunc(this.css.selectors, this.css.filters);
+ }
+
if (elements.length > 0)
this.hideElemsFunc(elements, elementFilters);
+
if (typeof done == "function")
done();
return;
}
pattern = patterns.shift();
generator = evaluate(pattern.selectors, 0, "",
@@ -648,18 +810,21 @@
}
for (let selector of generator)
{
if (selector != null)
{
if (!this.useInlineStyles ||
pattern.isSelectorHidingOnlyPattern())
{
- selectors.push(selector);
- selectorFilters.push(pattern.text);
+ if (!this.css.has(selector))
+ {
+ selectors.push(selector);
+ selectorFilters.push(pattern.text);
+ }
}
else
{
for (let element of this.document.querySelectorAll(selector))
{
elements.push(element);
elementFilters.push(pattern.text);
}
@@ -779,16 +944,19 @@
{
let stylesheet = event.target.sheet;
if (stylesheet)
this.queueFiltering([stylesheet]);
},
observe(mutations)
{
+ if (this.css.update(mutations))
+ this.addSelectorsFunc(this.css.selectors, this.css.filters);
hub 2018/03/23 15:54:25 My concern here is that the intent of queueFilteri
Manish Jethani 2018/03/23 16:55:33 The thing is that I'm trying to avoid running full
+
this.queueFiltering(null, mutations);
},
apply(patterns)
{
this.patterns = [];
for (let pattern of patterns)
{
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld