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, |
Manish Jethani
2018/03/01 17:18:11
-abp-has and -abp-contains definitely, one hundred
|
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, stylesheet) |
{ |
for (let element of this.getElements(prefix, subtree, stylesheet)) |
yield [makeSelector(element, ""), subtree]; |
}, |
@@ -378,16 +380,46 @@ |
}; |
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 patternDependsOnBothStylesAndDOM(pattern) |
+{ |
+ return pattern.selectors.some(s => s.dependsOnStyles && s.dependsOnDOM); |
+} |
+ |
+function shouldProcessPattern(pattern, stylesheets, mutations) |
+{ |
+ if (!stylesheets && !mutations) |
+ // Process everything. |
+ return true; |
+ |
+ if (!stylesheets && !patternDependsOnDOM(pattern)) |
+ return false; |
+ |
+ if (!mutations && !patternDependsOnStyles(pattern)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
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 +529,60 @@ |
/** |
* 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 selectors = []; |
let selectorFilters = []; |
let elements = []; |
let elementFilters = []; |
let cssStyles = []; |
- let stylesheetOnlyChange = !!stylesheets; |
- if (!stylesheets) |
+ let patterns = null; |
+ |
+ // If neither any style sheets nor any DOM mutations have been specified, |
+ // do full processing. |
+ if (!stylesheets && !mutations) |
+ { |
+ patterns = this.patterns.slice(); |
+ stylesheets = this.document.styleSheets; |
+ } |
+ else |
+ { |
+ patterns = this.patterns.filter( |
+ pattern => shouldProcessPattern(pattern, stylesheets, mutations) |
+ ); |
+ } |
+ |
+ // 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(patternDependsOnBothStylesAndDOM)) |
stylesheets = this.document.styleSheets; |
- for (let stylesheet of 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 +592,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 +612,16 @@ |
this.hideElemsFunc(elements, elementFilters); |
if (typeof done == "function") |
done(); |
return; |
} |
pattern = patterns.shift(); |
- if (stylesheetOnlyChange && |
Manish Jethani
2018/03/01 17:18:11
This is taken care of in shouldProcessPattern now.
|
- !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 +666,107 @@ |
_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) |
Manish Jethani
2018/03/01 17:18:11
The addition of the mutations parameter here is ma
|
{ |
let completion = () => |
{ |
this._lastInvocation = performance.now(); |
this._filteringInProgress = false; |
if (this._scheduledProcessing) |
{ |
let newStylesheets = this._scheduledProcessing.stylesheets; |
+ let newMutations = this._scheduledProcessing.mutations; |
this._scheduledProcessing = null; |
- this.queueFiltering(newStylesheets); |
+ this.queueFiltering(newStylesheets, newMutations); |
} |
}; |
if (this._scheduledProcessing) |
{ |
- if (!stylesheets) |
+ if (!stylesheets && !mutations) |
+ { |
this._scheduledProcessing.stylesheets = null; |
- else if (this._scheduledProcessing.stylesheets) |
- this._scheduledProcessing.stylesheets.push(...stylesheets); |
+ this._scheduledProcessing.mutations = null; |
+ } |
+ 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 newMutations = this._scheduledProcessing.mutations; |
this._filteringInProgress = true; |
this._scheduledProcessing = null; |
- this._addSelectors(newStylesheets, completion); |
+ this._addSelectors(newStylesheets, newMutations, 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 newMutations = this._scheduledProcessing.mutations; |
this._filteringInProgress = true; |
this._scheduledProcessing = null; |
- this._addSelectors(newStylesheets, completion); |
+ this._addSelectors(newStylesheets, newMutations, 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); |