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

Unified Diff: chrome/content/elemHideEmulation.js

Issue 29494577: Issue 5438 - Observer DOM changes to reapply filters. (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Improvements Created Aug. 2, 2017, 4 a.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
« chrome/content/.eslintrc.json ('K') | « chrome/content/.eslintrc.json ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/content/elemHideEmulation.js
===================================================================
--- a/chrome/content/elemHideEmulation.js
+++ b/chrome/content/elemHideEmulation.js
@@ -152,26 +152,27 @@
}
styles.sort();
return {
style: styles.join(" "),
subSelectors: splitSelector(rule.selectorText)
};
}
-function* evaluate(chain, index, prefix, subtree, styles)
+function* evaluate(chain, index, prefix, subtree, styles, map)
{
if (index >= chain.length)
{
yield prefix;
return;
}
for (let [selector, element] of
- chain[index].getSelectors(prefix, subtree, styles))
- yield* evaluate(chain, index + 1, selector, element, styles);
+ chain[index].getSelectors(prefix, subtree, styles,
+ chain.slice(index), map))
+ yield* evaluate(chain, index + 1, selector, element, styles, map);
}
function PlainSelector(selector)
{
this._selector = selector;
}
PlainSelector.prototype = {
@@ -199,36 +200,43 @@
HasSelector.prototype = {
requiresHiding: true,
get dependsOnStyles()
{
return this._innerSelectors.some(selector => selector.dependsOnStyles);
},
- *getSelectors(prefix, subtree, styles)
+ *getSelectors(prefix, subtree, styles, chain, map)
{
- for (let element of this.getElements(prefix, subtree, styles))
+ for (let element of this.getElements(prefix, subtree, styles, chain, map))
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 {Array} chain the chain of selectors including this.
+ * @param {WeakMap} map of the elements and chain for re-evaluation.
*/
- *getElements(prefix, subtree, styles)
+ *getElements(prefix, subtree, styles, chain, map)
{
let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
prefix + "*" : prefix;
let elements = subtree.querySelectorAll(actualPrefix);
for (let element of elements)
{
- let iter = evaluate(this._innerSelectors, 0, "", element, styles);
+ let e = map.get(element);
+ if (e == undefined)
+ map.set(element, [chain]);
+ else
+ e.push(chain);
+ let iter = evaluate(this._innerSelectors, 0, "", element, styles, map);
for (let selector of iter)
{
if (relativeSelector.test(selector))
selector = ":scope" + selector;
if (element.querySelector(selector))
yield element;
}
}
@@ -238,23 +246,24 @@
function ContainsSelector(textContent)
{
this._text = textContent;
}
ContainsSelector.prototype = {
requiresHiding: true,
- *getSelectors(prefix, subtree, stylesheet)
+ *getSelectors(prefix, subtree, stylesheet, chain, map)
{
- for (let element of this.getElements(prefix, subtree, stylesheet))
+ for (let element of this.getElements(prefix, subtree, stylesheet,
+ chain, map))
yield [makeSelector(element, ""), subtree];
},
- *getElements(prefix, subtree, stylesheet)
+ *getElements(prefix, subtree, stylesheet, chain, map)
{
let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
prefix + "*" : prefix;
let elements = subtree.querySelectorAll(actualPrefix);
for (let element of elements)
if (element.textContent.includes(this._text))
yield element;
}
@@ -305,23 +314,30 @@
*getSelectors(prefix, subtree, styles)
{
for (let selector of this.findPropsSelectors(styles, prefix, this._regexp))
yield [selector, subtree];
}
};
+function isSelectorHidingOnlyPattern(pattern)
+{
+ return pattern.selectors.some(s => s.preferHideWithSelector) &&
+ !pattern.selectors.some(s => s.requiresHiding);
+}
+
function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc,
hideElemsFunc)
{
this.window = window;
this.getFiltersFunc = getFiltersFunc;
this.addSelectorsFunc = addSelectorsFunc;
this.hideElemsFunc = hideElemsFunc;
+ this.observer = new window.MutationObserver(this.observe.bind(this));
}
ElemHideEmulation.prototype = {
isSameOrigin(stylesheet)
{
try
{
return new URL(stylesheet.href).origin == this.window.location.origin;
@@ -398,39 +414,43 @@
new SyntaxError("Failed to parse Adblock Plus " +
`selector ${selector}, can't ` +
"have a lonely :-abp-contains()."));
return null;
}
return selectors;
},
+ _observerMap: new WeakMap(),
+
_lastInvocation: 0,
/**
* Processes the current document and applies all rules to it.
* @param {CSSStyleSheet[]} [stylesheets]
* The list of new stylesheets that have been added to the document and
* made reprocessing necessary. This parameter shouldn't be passed in for
* the initial processing, all of document's stylesheets will be considered
* then and all rules, including the ones not dependent on styles.
+ * @param {boolean} [domUpdate]
+ * Indicate this is a DOM update.
*/
- addSelectors(stylesheets)
+ addSelectors(stylesheets, domUpdate)
{
this._lastInvocation = Date.now();
let selectors = [];
let selectorFilters = [];
let elements = [];
let elementFilters = [];
let cssStyles = [];
- let stylesheetOnlyChange = !!stylesheets;
+ let stylesheetOnlyChange = !!stylesheets && !domUpdate;
if (!stylesheets)
stylesheets = this.window.document.styleSheets;
// Chrome < 51 doesn't have an iterable StyleSheetList
// https://issues.adblockplus.org/ticket/5381
for (let i = 0; i < stylesheets.length; i++)
{
let stylesheet = stylesheets[i];
@@ -456,21 +476,20 @@
for (let pattern of this.patterns)
{
if (stylesheetOnlyChange &&
!pattern.selectors.some(selector => selector.dependsOnStyles))
{
continue;
}
- for (let selector of evaluate(pattern.selectors,
- 0, "", document, cssStyles))
+ for (let selector of evaluate(pattern.selectors, 0, "", document,
+ cssStyles, this._observerMap))
{
- if (pattern.selectors.some(s => s.preferHideWithSelector) &&
- !pattern.selectors.some(s => s.requiresHiding))
+ if (isSelectorHidingOnlyPattern(pattern))
{
selectors.push(selector);
selectorFilters.push(pattern.text);
}
else
{
for (let element of document.querySelectorAll(selector))
{
@@ -482,38 +501,96 @@
}
this.addSelectorsFunc(selectors, selectorFilters);
this.hideElemsFunc(elements, elementFilters);
},
_stylesheetQueue: null,
+ /** Filtering reason
+ * @typedef {Object} FilteringReason
+ * @property {boolean} dom Indicate the DOM changed (tree or attributes)
+ * @property {CSSStyleSheet[]} [stylesheets]
+ * Indicate the stylesheets that needs refresh
+ * @property {WeakSet} subtrees The subtrees affected we were watching.
+ */
+
+ /** Re-run filtering either immediately or queued.
+ * @param {FilteringReason} reason why the filtering must be queued.
+ */
+ queueFiltering(reason)
+ {
+ if (!this._stylesheetQueue &&
+ (Date.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL ||
+ reason.dom))
+ {
+ this._stylesheetQueue = [];
+ this.window.setTimeout(() =>
+ {
+ let stylesheets = this._stylesheetQueue;
+ this._stylesheetQueue = null;
+ let domUpdate = reason.dom;
hub 2017/08/03 16:28:18 I realise this should have been moved up, out of t
+ this.addSelectors(stylesheets, domUpdate);
hub 2017/08/02 04:10:55 Here we don't use reason.subtree. Actually I'm no
+ }, MIN_INVOCATION_INTERVAL - (Date.now() - this._lastInvocation));
+ }
+ if (reason.stylesheets)
+ {
+ if (this._stylesheetQueue)
+ this._stylesheetQueue.push(...reason.stylesheets);
+ else
+ this.addSelectors(reason.stylesheets);
+ }
+ },
+
onLoad(event)
{
let stylesheet = event.target.sheet;
if (stylesheet)
+ this.queueFiltering({stylesheets: [stylesheet]});
+ },
+
+ observe(mutations)
+ {
+ let reason = {};
+ reason.dom = true;
+ let stylesheets = [];
+ for (let mutation of mutations)
{
- if (!this._stylesheetQueue &&
- Date.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL)
+ if (mutation.type == "childList")
{
- this._stylesheetQueue = [];
- this.window.setTimeout(() =>
+ for (let added of mutation.addedNodes)
+ {
+ if (added.nodeType == Node.ELEMENT_NODE &&
+ (added.tagName == "STYLE" || added.tagName == "style") &&
+ added.styesheet)
+ stylesheets.push(added.stylesheet);
hub 2017/08/02 04:10:54 If we have a new style element, then we likely hav
+ }
+ for (let removed of mutation.removedNodes)
{
- let stylesheets = this._stylesheetQueue;
- this._stylesheetQueue = null;
- this.addSelectors(stylesheets);
- }, MIN_INVOCATION_INTERVAL - (Date.now() - this._lastInvocation));
+ this._observerMap.delete(removed);
+ }
}
-
- if (this._stylesheetQueue)
- this._stylesheetQueue.push(stylesheet);
- else
- this.addSelectors([stylesheet]);
+ let currentNode = mutation.target;
+ while (currentNode)
+ {
+ let e = this._observerMap.has(currentNode);
+ if (e)
+ {
+ if (!(reason.subtrees instanceof Set))
+ reason.subtrees = new Set();
+ reason.subtrees.add(currentNode);
+ break;
+ }
+ currentNode = currentNode.parentNode;
+ }
}
+ if (stylesheets.length > 0)
+ reason.stylesheets = stylesheets;
+ this.queueFiltering(reason);
},
apply()
{
this.getFiltersFunc(patterns =>
{
this.patterns = [];
for (let pattern of patterns)
@@ -522,13 +599,22 @@
if (selectors != null && selectors.length > 0)
this.patterns.push({selectors, text: pattern.text});
}
if (this.patterns.length > 0)
{
let {document} = this.window;
this.addSelectors();
+ this.observer.observe(
+ document,
+ {
+ childList: true,
+ attributes: true,
+ subtree: true,
+ attributeFilter: ["class", "id"]
hub 2017/08/02 04:10:54 check for the obvious class and id attributes that
+ }
+ );
document.addEventListener("load", this.onLoad.bind(this), true);
}
});
}
};
« chrome/content/.eslintrc.json ('K') | « chrome/content/.eslintrc.json ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld