| 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,72 @@ 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 +199,49 @@ 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; |
| + 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; |