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: Filter reporting was broken in the previous revision. Created May 12, 2017, 3:40 p.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
@@ -14,18 +14,16 @@
* You should have received a copy of the GNU General Public License
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
*/
/* globals filterToRegExp */
"use strict";
-let propertySelectorRegExp = /\[-abp-properties=(["'])([^"']+)\1\]/;
-
function splitSelector(selector)
{
if (selector.indexOf(",") == -1)
return [selector];
let selectors = [];
let start = 0;
let level = 0;
@@ -54,131 +52,329 @@
}
}
}
selectors.push(selector.substring(start));
return selectors;
}
+// Return position of node from parent.
+// 1 base index like for :nth-child()
+function positionInParent(node)
+{
+ let parentNode = node ? node.parentNode : null;
+ if (parentNode == null)
+ return 0;
+
+ let {children} = parentNode;
+ if (!children)
+ return 0;
+ let i = 0;
+ for (i = 0; i < children.length; i++)
+ if (children[i] == node)
+ break;
+ return i + 1;
+}
+
+function makeSelector(node, selector)
+{
+ if (node && node.id && node.id != "")
hub 2017/05/12 17:31:18 I need to check that node.id is a valid CSS identi
+ {
+ let newSelector = "#" + node.id;
+ if (selector != "")
+ newSelector += " > ";
+ return newSelector + selector;
+ }
+ let idx = positionInParent(node);
+ if (idx > 0)
+ {
+ let newSelector = `${node.tagName}:nth-child(${idx}) `;
+ if (selector != "")
+ newSelector += "> ";
+ return makeSelector(node.parentNode, newSelector + selector);
+ }
+
+ return selector;
+}
+
+// return the regexString for the properties
+function parsePropSelPattern(propertyExpression)
+{
+ let regexpString;
+ if (propertyExpression.length >= 2 && propertyExpression[0] == "/" &&
+ propertyExpression[propertyExpression.length - 1] == "/")
+ regexpString = propertyExpression.slice(1, -1)
+ .replace("\\x7B ", "{").replace("\\x7D ", "}");
+ else
+ regexpString = filterToRegExp(propertyExpression);
+ return regexpString;
+}
+
+function parseSelector(selector)
+{
+ if (selector.length == 0)
+ return [];
+
+ let abpSelectorIndex = selector.indexOf(":-abp-");
+ if (abpSelectorIndex == -1)
+ return [new PlainSelector(selector)];
+
+ let selectors = [];
+ if (abpSelectorIndex > 0)
+ selectors.push(new PlainSelector(selector.substr(0, abpSelectorIndex)));
+
+ let suffixStart = abpSelectorIndex;
+
+ if (selector.indexOf(":-abp-properties(", abpSelectorIndex) ==
+ abpSelectorIndex)
+ {
+ let startIndex = abpSelectorIndex + 17;
+ let endquoteIndex = selector.indexOf(selector[startIndex], startIndex + 1);
+ if ((endquoteIndex == -1) || (selector[endquoteIndex + 1] != ")"))
+ return null;
+
+ selectors.push(new PropsSelector(
+ selector.substr(startIndex + 1, endquoteIndex - startIndex - 1)));
+ suffixStart = endquoteIndex + 2;
+ }
+ else if (selector.indexOf(":-abp-has(", abpSelectorIndex) ==
+ abpSelectorIndex)
+ {
+ let startIndex = abpSelectorIndex + 10;
+ let parens = 1;
+ let i;
+ for (i = startIndex; i < selector.length; i++)
+ {
+ if (selector[i] == "(")
+ parens++;
+ else if (selector[i] == ")")
+ parens--;
+
+ if (parens == 0)
+ break;
+ }
+ if (parens != 0)
+ return null;
+ selectors.push(new HasSelector(
+ selector.substr(startIndex, i - startIndex)));
+ suffixStart = i + 1;
+ }
+
+ let suffix = parseSelector(selector.substr(suffixStart));
hub 2017/05/12 19:48:24 here, if we had been parsing something matching "-
+ if (suffix)
+ selectors.push(...suffix);
+
+ return selectors;
+}
+
+function matchStyleProps(style, rule, pattern, selectors, filters)
+{
+ if (pattern.regexp.test(style))
+ {
+ let subSelectors = splitSelector(rule.selectorText);
+ for (let i = 0; i < subSelectors.length; i++)
+ {
+ let subSelector = subSelectors[i];
+ selectors.push(pattern.prefix + subSelector + pattern.suffix);
+ filters.push(pattern.text);
+ }
+ }
+}
+
+function findPropsSelectors(stylesheet, pattern, selectors, filters)
+{
+ let rules = stylesheet.cssRules;
+ if (!rules)
+ return;
+
+ for (let rule of rules)
+ {
+ if (rule.type != rule.STYLE_RULE)
+ continue;
+
+ let style = stringifyStyle(rule.style);
+ matchStyleProps(style, rule, pattern, selectors, filters);
+ }
+}
+
+function stringifyStyle(style)
+{
+ let styles = [];
+ for (let i = 0; i < style.length; i++)
+ {
+ let property = style.item(i);
+ let value = style.getPropertyValue(property);
+ let priority = style.getPropertyPriority(property);
+ styles.push(property + ": " + value + (priority ? " !" + priority : "") +
+ ";");
+ }
+ styles.sort();
+ return styles.join(" ");
+}
+
+function* evaluate(chain, index, prefix, subtree, stylesheet)
+{
+ if (index >= chain.length)
+ {
+ yield prefix;
+ return;
+ }
+ for (let [selector, element] of
+ chain[index].getSelectors(prefix, subtree, stylesheet))
+ yield* evaluate(chain, index + 1, selector, element, stylesheet);
+}
+
+/*
+ * getSelector() is a generator function returning a pair of selector
+ * string and subtree.
+ * getElements() is a generator function returning elements selected.
+ */
+function PlainSelector(selector)
+{
+ this._selector = selector;
+}
+
+PlainSelector.prototype = {
+ *getSelectors(prefix, subtree, stylesheet)
+ {
+ yield [prefix + this._selector, subtree];
+ },
+
+ *getElements(prefix, subtree, stylesheet)
+ {
+ for (let selector of this.getSelectors(prefix, subtree, stylesheet))
+ for (let element of subtree.querySelectorAll(selector[0]))
+ yield element;
+ }
+};
+
+function HasSelector(selector)
+{
+ this._innerSelectors = parseSelector(selector);
+}
+
+HasSelector.prototype = {
+ *getSelectors(prefix, subtree, stylesheet)
+ {
+ for (let element of this.getElements(prefix, subtree, stylesheet))
+ yield [prefix + makeSelector(element, ""), subtree];
+ },
+
+ *getElements(prefix, subtree, stylesheet)
+ {
+ let elements = subtree.querySelectorAll(prefix ? prefix : "*");
+ for (let element of elements)
+ {
+ let newPrefix = makeSelector(element, "");
+ let iter = evaluate(this._innerSelectors, 0, "", element, stylesheet);
+ for (let selector of iter)
+ // we insert a space between the two. It becomes a no-op if selector
+ // doesn't have a combinator
+ if (subtree.querySelector(newPrefix + " " + selector))
+ yield element;
+ }
+ }
+};
+
+function PropsSelector(selector)
+{
+ this._regexp = new RegExp(parsePropSelPattern(selector), "i");
+}
+
+PropsSelector.prototype = {
+ *getSelectors(prefix, subtree, stylesheet)
+ {
+ let selectors = [];
+ let filters = [];
+ let selPattern = {
+ prefix,
+ suffix: "",
+ regexp: this._regexp
+ };
+
+ findPropsSelectors(stylesheet, selPattern, selectors, filters);
+ for (let selector of selectors)
+ yield [selector, subtree];
+ },
+
+ *getElements(prefix, subtree, stylesheet)
+ {
+ for (let [selector, element] of
+ this.getSelectors(prefix, subtree, stylesheet))
+ for (let subElement of element.querySelectorAll(selector))
+ yield subElement;
+ }
+};
+
function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc)
{
this.window = window;
this.getFiltersFunc = getFiltersFunc;
this.addSelectorsFunc = addSelectorsFunc;
}
ElemHideEmulation.prototype = {
- stringifyStyle(style)
- {
- let styles = [];
- for (let i = 0; i < style.length; i++)
- {
- let property = style.item(i);
- let value = style.getPropertyValue(property);
- let priority = style.getPropertyPriority(property);
- styles.push(property + ": " + value + (priority ? " !" + priority : "") +
- ";");
- }
- styles.sort();
- return styles.join(" ");
- },
isSameOrigin(stylesheet)
{
try
{
return new URL(stylesheet.href).origin == this.window.location.origin;
}
catch (e)
{
// Invalid URL, assume that it is first-party.
return true;
}
},
- findSelectors(stylesheet, selectors, filters)
+ addSelectors(stylesheet)
{
+ let selectors = [];
+ let filters = [];
+
// Explicitly ignore third-party stylesheets to ensure consistent behavior
// between Firefox and Chrome.
if (!this.isSameOrigin(stylesheet))
return;
- let rules = stylesheet.cssRules;
- if (!rules)
- return;
-
- for (let rule of rules)
- {
- if (rule.type != rule.STYLE_RULE)
- continue;
-
- let style = this.stringifyStyle(rule.style);
- for (let pattern of this.patterns)
+ for (let patterns of this.selPatterns)
+ for (let selector of
+ evaluate(patterns.selectors, 0, "", document, stylesheet))
{
- if (pattern.regexp.test(style))
- {
- let subSelectors = splitSelector(rule.selectorText);
- for (let subSelector of subSelectors)
- {
- selectors.push(pattern.prefix + subSelector + pattern.suffix);
- filters.push(pattern.text);
- }
- }
+ selectors.push(selector);
+ filters.push(patterns.text);
}
- }
- },
- addSelectors(stylesheets)
- {
- let selectors = [];
- let filters = [];
- for (let stylesheet of stylesheets)
- this.findSelectors(stylesheet, selectors, filters);
this.addSelectorsFunc(selectors, filters);
},
onLoad(event)
{
let stylesheet = event.target.sheet;
if (stylesheet)
- this.addSelectors([stylesheet]);
+ this.addSelectors(stylesheet);
},
apply()
{
this.getFiltersFunc(patterns =>
{
- this.patterns = [];
+ this.selPatterns = [];
+
for (let pattern of patterns)
{
- let match = propertySelectorRegExp.exec(pattern.selector);
- if (!match)
- continue;
-
- let propertyExpression = match[2];
- let regexpString;
- if (propertyExpression.length >= 2 && propertyExpression[0] == "/" &&
- propertyExpression[propertyExpression.length - 1] == "/")
- {
- regexpString = propertyExpression.slice(1, -1)
- .replace("\\x7B ", "{").replace("\\x7D ", "}");
- }
- else
- regexpString = filterToRegExp(propertyExpression);
-
- 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)
- });
+ let selectors = parseSelector(pattern.selector);
+ if (selectors != null && selectors.length > 0)
+ this.selPatterns.push({selectors, text: pattern.text});
}
- if (this.patterns.length > 0)
+ if (this.selPatterns.length > 0)
{
let {document} = this.window;
- this.addSelectors(document.styleSheets);
+ for (let stylesheet of document.styleSheets)
+ this.addSelectors(stylesheet);
document.addEventListener("load", this.onLoad.bind(this), true);
}
});
}
};
« 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