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

Unified Diff: chrome/content/elemHideEmulation.js

Issue 29383960: Issue 3143 - Filter elements with :-abp-has() (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore
Patch Set: Hide elements and not using styles. Created March 16, 2017, 5:16 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
« no previous file with comments | « no previous file | lib/filterClasses.js » ('j') | 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
@@ -1,12 +1,13 @@
// We are currently limited to ECMAScript 5 in this file, because it is being
// used in the browser tests. See https://issues.adblockplus.org/ticket/4796
var propertySelectorRegExp = /\[\-abp\-properties=(["'])([^"']+)\1\]/;
+var pseudoClassHasSelectorRegExp = /:has\((.*)\)/;
function splitSelector(selector)
{
if (selector.indexOf(",") == -1)
return [selector];
var selectors = [];
var start = 0;
@@ -36,21 +37,47 @@ function splitSelector(selector)
}
}
}
selectors.push(selector.substring(start));
return selectors;
}
-function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc)
+// matcher for the pseudo CSS4 class :has
+// For those browser that don't have it yet.
+function PseudoHasMatcher(selector)
+{
+ this.hasSelector = selector;
+}
+
+PseudoHasMatcher.prototype = {
+ match: function(elem)
+ {
+ var matches = [];
+ // look up for all elements that match the :has().
+ var children = elem.children;
+ for (var i = 0; i < children.length; i++)
+ {
+ var hasElem = elem.querySelector(this.hasSelector);
+ if (hasElem != null)
+ {
+ matches.push(hasElem);
+ }
+ }
+ return matches;
+ }
+};
+
+function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc, hideElementsFunc)
{
this.window = window;
this.getFiltersFunc = getFiltersFunc;
this.addSelectorsFunc = addSelectorsFunc;
+ this.hideElementsFunc = hideElementsFunc;
}
ElemHideEmulation.prototype = {
stringifyStyle: function(style)
{
var styles = [];
for (var i = 0; i < style.length; i++)
{
@@ -89,79 +116,197 @@ ElemHideEmulation.prototype = {
for (var i = 0; i < rules.length; i++)
{
var rule = rules[i];
if (rule.type != rule.STYLE_RULE)
continue;
var style = this.stringifyStyle(rule.style);
- for (var j = 0; j < this.patterns.length; j++)
+ for (var j = 0; j < this.propSelPatterns.length; j++)
{
- var pattern = this.patterns[j];
+ var pattern = this.propSelPatterns[j];
if (pattern.regexp.test(style))
{
var subSelectors = splitSelector(rule.selectorText);
for (var k = 0; k < subSelectors.length; k++)
{
var subSelector = subSelectors[k];
selectors.push(pattern.prefix + subSelector + pattern.suffix);
filters.push(pattern.text);
}
}
}
}
},
+ findPseudoClassHasSelectors: function(selectors, filters)
+ {
+ for (var i = 0; i < this.pseudoHasPatterns.length; i++)
+ {
+ var pattern = this.pseudoHasPatterns[i];
+
+ var prefixes = document.querySelectorAll(pattern.prefix);
+ for (var j = 0; j < prefixes.length; j++)
+ {
+ var matched = pattern.elementMatcher.match(prefixes[j]);
+ if (matched.length == 0)
+ {
+ continue;
+ }
+ // XXX make sure we don't have performance problems here
+ matched.forEach(function(e)
+ {
+ var subSelector = e.id;
+ if (!subSelector)
+ {
+ var findUniqueId = function()
+ {
+ var id = "elemHideEmulationHide-" +
+ Math.floor(Math.random() * 10000);
+ if (!document.getElementById(id))
+ return id;
+ return findUniqueId();
+ };
+ subSelector = findUniqueId();
+ e.id = subSelector;
+ }
+ var newSelector = pattern.prefix ? pattern.prefix + " > " : "";
+ newSelector += "#" + subSelector;
+ newSelector += pattern.suffix ? " > " + pattern.suffix : "";
+ selectors.push(newSelector);
+ filters.push(pattern.text);
+ });
+ }
+ }
+ },
+
+ findPseudoClassHasElements: function(elements, filters)
+ {
+ for (var i = 0; i < this.pseudoHasPatterns.length; i++)
+ {
+ var pattern = this.pseudoHasPatterns[i];
+
+ var prefixes = document.querySelectorAll(pattern.prefix);
+ for (var j = 0; j < prefixes.length; j++)
+ {
+ var matched = pattern.elementMatcher.match(prefixes[j]);
+ if (matched.length == 0)
+ {
+ continue;
+ }
+
+ matched.forEach(function(e)
+ {
+ var toHide = [];
+ var hidingPatterns = []
+ if (pattern.suffix)
+ {
+ hidingPatterns.push(pattern.text);
+ console.log('pattern.suffix', pattern.suffix);
+ var sel = pattern.suffix;
+ // XXX we should have a more elegant way
+ // also startsWith isn't available in PhantomJS.
+ if (sel.substr(0, 1) == ">")
+ {
+ sel = sel.substr(1);
+ }
+ var subElements = e.querySelectorAll(sel);
+ for (var k = 0; k < subElements.length; k++)
+ {
+ elements.push(subElements[i]);
+ filters.push(pattern.text);
+ }
+ }
+ else
+ {
+ elements.push(e);
+ filters.push(pattern.text);
+ }
+ });
+ }
+ }
+ },
+
addSelectors: function(stylesheets)
{
var selectors = [];
var filters = [];
for (var i = 0; i < stylesheets.length; i++)
this.findSelectors(stylesheets[i], selectors, filters);
this.addSelectorsFunc(selectors, filters);
},
+ hideElements: function()
+ {
+ var elements = [];
+ var filters = [];
+ this.findPseudoClassHasElements(elements, filters);
+ console.log("hideElements", elements.length);
+ this.hideElementsFunc(elements, filters);
+ },
+
onLoad: function(event)
{
var stylesheet = event.target.sheet;
if (stylesheet)
this.addSelectors([stylesheet]);
+ this.hideElements();
},
apply: function()
{
this.getFiltersFunc(function(patterns)
{
- this.patterns = [];
+ this.propSelPatterns = [];
+ this.pseudoHasPatterns = [];
for (var i = 0; i < patterns.length; i++)
{
var pattern = patterns[i];
var match = propertySelectorRegExp.exec(pattern.selector);
- if (!match)
- continue;
+ if (match)
+ {
+ var regexpMatcher;
+ var regexpString;
+ var propertyExpression = match[2];
+ if (propertyExpression.length >= 2 && propertyExpression[0] == "/" &&
+ propertyExpression[propertyExpression.length - 1] == "/")
+ regexpString = propertyExpression.slice(1, -1)
+ .replace("\\x7B ", "{").replace("\\x7D ", "}");
+ else
+ regexpString = filterToRegExp(propertyExpression);
+ regexpMatcher = new RegExp(regexpString, "i");
+ this.propSelPatterns.push({
+ text: pattern.text,
+ regexp: regexpMatcher,
+ prefix: pattern.selector.substr(0, match.index),
+ suffix: pattern.selector.substr(match.index + match[0].length)
+ });
+ }
+ else
+ {
+ var elementMatcher;
+ match = pseudoClassHasSelectorRegExp.exec(pattern.selector);
+ if (!match)
+ continue;
- var propertyExpression = match[2];
- var regexpString;
- if (propertyExpression.length >= 2 && propertyExpression[0] == "/" &&
- propertyExpression[propertyExpression.length - 1] == "/")
- regexpString = propertyExpression.slice(1, -1)
- .replace("\\x7B ", "{").replace("\\x7D ", "}");
- else
- regexpString = filterToRegExp(propertyExpression);
+ var pseudoHasSelector = match[1];
+ elementMatcher = new PseudoHasMatcher(pseudoHasSelector);
+ this.pseudoHasPatterns.push({
+ text: pattern.text,
+ elementMatcher: elementMatcher,
+ prefix: pattern.selector.substr(0, match.index).trim(),
+ suffix: pattern.selector.substr(match.index + match[0].length).trim()
+ });
+ }
- this.patterns.push({
- text: pattern.text,
- regexp: new RegExp(regexpString, "i"),
- prefix: pattern.selector.substr(0, match.index),
- suffix: pattern.selector.substr(match.index + match[0].length)
- });
}
- if (this.patterns.length > 0)
+ if (this.pseudoHasPatterns.length > 0 || this.propSelPatterns.length > 0)
{
var document = this.window.document;
this.addSelectors(document.styleSheets);
+ this.hideElements();
document.addEventListener("load", this.onLoad.bind(this), true);
}
}.bind(this));
}
};
« no previous file with comments | « no previous file | lib/filterClasses.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld