Index: lib/content/elemHideEmulation.js |
=================================================================== |
--- a/lib/content/elemHideEmulation.js |
+++ b/lib/content/elemHideEmulation.js |
@@ -239,16 +239,17 @@ |
function HasSelector(selectors) |
{ |
this._innerSelectors = selectors; |
} |
HasSelector.prototype = { |
requiresHiding: true, |
+ dependsOnDOM: true, |
get dependsOnStyles() |
{ |
return this._innerSelectors.some(selector => selector.dependsOnStyles); |
}, |
get dependsOnCharacterData() |
{ |
@@ -301,16 +302,17 @@ |
function ContainsSelector(textContent) |
{ |
this._regexp = makeRegExpParameter(textContent); |
} |
ContainsSelector.prototype = { |
requiresHiding: true, |
+ dependsOnDOM: true, |
dependsOnCharacterData: true, |
*getSelectors(prefix, subtree, styles) |
{ |
for (let element of this.getElements(prefix, subtree, styles)) |
yield [makeSelector(element, ""), subtree]; |
}, |
@@ -378,16 +380,42 @@ |
}; |
function isSelectorHidingOnlyPattern(pattern) |
{ |
return pattern.selectors.some(s => s.preferHideWithSelector) && |
!pattern.selectors.some(s => s.requiresHiding); |
} |
+function patternDependsOnStyles(pattern) |
+{ |
+ return pattern.selectors.some(s => s.dependsOnStyles); |
+} |
+ |
+function patternDependsOnDOM(pattern) |
+{ |
+ return pattern.selectors.some(s => s.dependsOnDOM); |
+} |
+ |
+function patternDependsOnStylesAndDOM(pattern) |
+{ |
+ return pattern.selectors.some(s => s.dependsOnStyles && s.dependsOnDOM); |
+} |
+ |
+function filterPatterns(patterns, {stylesheets, mutations}) |
+{ |
+ if (stylesheets && !mutations) |
+ return patterns.filter(patternDependsOnStyles); |
+ |
+ if (!stylesheets && mutations) |
+ return patterns.filter(patternDependsOnDOM); |
+ |
+ return patterns.slice(); |
+} |
+ |
function shouldObserveAttributes(patterns) |
{ |
// Observe changes to attributes if either there's a plain selector that |
// looks like an ID selector, class selector, or attribute selector in one of |
// the patterns (e.g. "a[href='https://example.com/']") |
// or there's a properties selector nested inside a has selector |
// (e.g. "div:-abp-has(:-abp-properties(color: blue))") |
return patterns.some( |
@@ -497,34 +525,51 @@ |
/** |
* 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 {MutationRecord[]} [mutations] |
+ * The list of DOM mutations that have been applied to the document and |
+ * made reprocessing necessary. This parameter shouldn't be passed in for |
+ * the initial processing, the entire document will be considered |
+ * then and all rules, including the ones not dependent on the DOM. |
* @param {function} [done] |
* Callback to call when done. |
*/ |
- _addSelectors(stylesheets, done) |
+ _addSelectors(stylesheets, mutations, done) |
{ |
+ let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); |
+ |
let selectors = []; |
let selectorFilters = []; |
let elements = []; |
let elementFilters = []; |
let cssStyles = []; |
- let stylesheetOnlyChange = !!stylesheets; |
- if (!stylesheets) |
+ // If neither any style sheets nor any DOM mutations have been specified, |
+ // do full processing. |
+ if (!stylesheets && !mutations) |
stylesheets = this.document.styleSheets; |
- for (let stylesheet of stylesheets) |
+ // If there are any DOM mutations and any of the patterns depends on both |
+ // style sheets and the DOM (e.g. -abp-has(-abp-properties)), find all the |
+ // rules in every style sheet in the document, because we need to run |
+ // querySelectorAll afterwards. On the other hand, if we only have patterns |
+ // that depend on either styles or DOM both not both |
+ // (e.g. -abp-properties or -abp-contains), we can skip this part. |
+ if (mutations && patterns.some(patternDependsOnStylesAndDOM)) |
+ stylesheets = this.document.styleSheets; |
+ |
+ for (let stylesheet of stylesheets || []) |
{ |
// Explicitly ignore third-party stylesheets to ensure consistent behavior |
// between Firefox and Chrome. |
if (!this.isSameOrigin(stylesheet)) |
continue; |
let rules = stylesheet.cssRules; |
if (!rules) |
@@ -534,17 +579,16 @@ |
{ |
if (rule.type != rule.STYLE_RULE) |
continue; |
cssStyles.push(stringifyStyle(rule)); |
} |
} |
- let patterns = this.patterns.slice(); |
let pattern = null; |
let generator = null; |
let processPatterns = () => |
{ |
let cycleStart = performance.now(); |
if (!pattern) |
@@ -555,22 +599,16 @@ |
this.hideElemsFunc(elements, elementFilters); |
if (typeof done == "function") |
done(); |
return; |
} |
pattern = patterns.shift(); |
- if (stylesheetOnlyChange && |
- !pattern.selectors.some(selector => selector.dependsOnStyles)) |
- { |
- pattern = null; |
- return processPatterns(); |
- } |
generator = evaluate(pattern.selectors, 0, "", |
this.document, cssStyles); |
} |
for (let selector of generator) |
{ |
if (selector != null) |
{ |
if (isSelectorHidingOnlyPattern(pattern)) |
@@ -615,85 +653,103 @@ |
_filteringInProgress: false, |
_lastInvocation: -MIN_INVOCATION_INTERVAL, |
_scheduledProcessing: null, |
/** |
* Re-run filtering either immediately or queued. |
* @param {CSSStyleSheet[]} [stylesheets] |
* new stylesheets to be processed. This parameter should be omitted |
- * for DOM modification (full reprocessing required). |
+ * for full reprocessing. |
+ * @param {MutationRecord[]} [mutations] |
+ * new DOM mutations to be processed. This parameter should be omitted |
+ * for full reprocessing. |
*/ |
- queueFiltering(stylesheets) |
+ queueFiltering(stylesheets, mutations) |
{ |
let completion = () => |
{ |
this._lastInvocation = performance.now(); |
this._filteringInProgress = false; |
if (this._scheduledProcessing) |
{ |
- let newStylesheets = this._scheduledProcessing.stylesheets; |
+ let {stylesheets, mutations} = this._scheduledProcessing; |
this._scheduledProcessing = null; |
- this.queueFiltering(newStylesheets); |
+ this.queueFiltering(stylesheets, mutations); |
} |
}; |
if (this._scheduledProcessing) |
{ |
- if (!stylesheets) |
- this._scheduledProcessing.stylesheets = null; |
- else if (this._scheduledProcessing.stylesheets) |
- this._scheduledProcessing.stylesheets.push(...stylesheets); |
+ if (!stylesheets && !mutations) |
+ { |
+ this._scheduledProcessing = {}; |
+ } |
+ else |
+ { |
+ if (stylesheets) |
+ { |
+ if (!this._scheduledProcessing.stylesheets) |
+ this._scheduledProcessing.stylesheets = []; |
+ this._scheduledProcessing.stylesheets.push(...stylesheets); |
+ } |
+ if (mutations) |
+ { |
+ if (!this._scheduledProcessing.mutations) |
+ this._scheduledProcessing.mutations = []; |
+ this._scheduledProcessing.mutations.push(...mutations); |
+ } |
+ } |
} |
else if (this._filteringInProgress) |
{ |
- this._scheduledProcessing = {stylesheets}; |
+ this._scheduledProcessing = {stylesheets, mutations}; |
} |
else if (performance.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) |
{ |
- this._scheduledProcessing = {stylesheets}; |
+ this._scheduledProcessing = {stylesheets, mutations}; |
setTimeout(() => |
{ |
- let newStylesheets = this._scheduledProcessing.stylesheets; |
+ let {stylesheets, mutations} = this._scheduledProcessing; |
this._filteringInProgress = true; |
this._scheduledProcessing = null; |
- this._addSelectors(newStylesheets, completion); |
+ this._addSelectors(stylesheets, mutations, completion); |
}, |
MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); |
} |
else if (this.document.readyState == "loading") |
{ |
- this._scheduledProcessing = {stylesheets}; |
+ this._scheduledProcessing = {stylesheets, mutations}; |
let handler = () => |
{ |
document.removeEventListener("DOMContentLoaded", handler); |
- let newStylesheets = this._scheduledProcessing.stylesheets; |
+ let {stylesheets, mutations} = this._scheduledProcessing; |
this._filteringInProgress = true; |
this._scheduledProcessing = null; |
- this._addSelectors(newStylesheets, completion); |
+ this._addSelectors(stylesheets, mutations, completion); |
}; |
document.addEventListener("DOMContentLoaded", handler); |
} |
else |
{ |
this._filteringInProgress = true; |
- this._addSelectors(stylesheets, completion); |
+ this._addSelectors(stylesheets, mutations, completion); |
} |
}, |
onLoad(event) |
{ |
let stylesheet = event.target.sheet; |
if (stylesheet) |
this.queueFiltering([stylesheet]); |
}, |
observe(mutations) |
{ |
- this.queueFiltering(); |
+ this.queueFiltering(null, mutations); |
}, |
apply(patterns) |
{ |
this.patterns = []; |
for (let pattern of patterns) |
{ |
let selectors = this.parseSelector(pattern.selector); |