| 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) |
| { |