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,16 +37,41 @@ function splitSelector(selector) |
} |
} |
} |
selectors.push(selector.substring(start)); |
return selectors; |
} |
+// 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) |
{ |
this.window = window; |
this.getFiltersFunc = getFiltersFunc; |
this.addSelectorsFunc = addSelectorsFunc; |
} |
ElemHideEmulation.prototype = { |
@@ -92,28 +118,73 @@ ElemHideEmulation.prototype = { |
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++) |
{ |
var pattern = this.patterns[j]; |
- if (pattern.regexp.test(style)) |
+ if (pattern.type == "prop" && 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); |
} |
} |
} |
} |
+ this.findPseudoClassHasSelectors(selectors, filters); |
+ }, |
+ |
+ findPseudoClassHasSelectors: function(selectors, filters) |
+ { |
+ // XXX shall we split the patterns? |
+ for (var i = 0; i < this.patterns.length; i++) |
+ { |
+ var pattern = this.patterns[i]; |
+ if (pattern.type != ":has") |
+ continue; |
+ |
+ 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); |
+ }); |
+ } |
+ } |
}, |
addSelectors: function(stylesheets) |
{ |
var selectors = []; |
var filters = []; |
for (var i = 0; i < stylesheets.length; i++) |
this.findSelectors(stylesheets[i], selectors, filters); |
@@ -129,33 +200,50 @@ ElemHideEmulation.prototype = { |
apply: function() |
{ |
this.getFiltersFunc(function(patterns) |
{ |
this.patterns = []; |
for (var i = 0; i < patterns.length; i++) |
{ |
+ var regexpMatcher; |
+ var elementMatcher; |
var pattern = patterns[i]; |
var match = propertySelectorRegExp.exec(pattern.selector); |
- if (!match) |
- continue; |
+ var type; |
+ if (match) |
+ { |
+ 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"); |
+ type = "prop"; |
+ } |
+ else |
+ { |
+ 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); |
+ type = ":has"; |
+ } |
this.patterns.push({ |
+ type: type, |
text: pattern.text, |
- regexp: new RegExp(regexpString, "i"), |
+ regexp: regexpMatcher, |
+ elementMatcher: elementMatcher, |
prefix: pattern.selector.substr(0, match.index), |
suffix: pattern.selector.substr(match.index + match[0].length) |
}); |
} |
if (this.patterns.length > 0) |
{ |
var document = this.window.document; |