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

Unified Diff: lib/common.js

Issue 29760699: Issue 6619 - Qualify CSS selectors in document style sheet correctly (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Reinclude test/common.js Created May 15, 2018, 5:18 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/content/elemHideEmulation.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/common.js
===================================================================
--- a/lib/common.js
+++ b/lib/common.js
@@ -96,8 +96,110 @@
}
}
selectors.push(selector.substring(start));
return selectors;
}
exports.splitSelector = splitSelector;
+
+function findTargetSelectorIndex(selector)
+{
+ let index = 0;
+ let whitespace = 0;
+ let scope = [];
+
+ // Start from the end of the string and go character by character, where each
+ // character is a Unicode code point.
+ for (let character of [...selector].reverse())
+ {
+ let currentScope = scope[scope.length - 1];
+
+ if (character == "'" || character == "\"")
+ {
+ // If we're already within the same type of quote, close the scope;
+ // otherwise open a new scope.
+ if (currentScope == character)
+ scope.pop();
+ else
+ scope.push(character);
+ }
+ else if (character == "]" || character == ")")
+ {
+ // For closing brackets and parentheses, open a new scope only if we're
+ // not within a quote. Within quotes these characters should have no
+ // meaning.
+ if (currentScope != "'" && currentScope != "\"")
+ scope.push(character);
+ }
+ else if (character == "[")
+ {
+ // If we're already within a bracket, close the scope.
+ if (currentScope == "]")
+ scope.pop();
+ }
+ else if (character == "(")
+ {
+ // If we're already within a parenthesis, close the scope.
+ if (currentScope == ")")
+ scope.pop();
+ }
+ else if (!currentScope)
+ {
+ // At the top level (not within any scope), count the whitespace if we've
+ // encountered it. Otherwise if we've hit one of the combinators,
+ // terminate here; otherwise if we've hit a non-colon character,
+ // terminate here.
+ if (/\s/u.test(character))
+ {
+ whitespace++;
+ }
+ else if ((character == ">" || character == "+" || character == "~") ||
+ (whitespace > 0 && character != ":"))
+ {
+ break;
+ }
+ }
+
+ // Zero out the whitespace count if we've entered a scope.
+ if (scope.length > 0)
+ whitespace = 0;
+
+ // Increment the index by the size of the character. Note that for Unicode
+ // composite characters (like emoji) this will be more than one.
+ index += character.length;
+ }
+
+ return selector.length - index + whitespace;
+}
+
+/**
+ * Qualifies a CSS selector with a qualifier, which may be another CSS selector
+ * or an empty string. For example, given the selector "div.bar" and the
+ * qualifier "#foo", this function returns "div#foo.bar".
+ * @param {string} selector The selector to qualify.
+ * @param {string} qualifier The qualifier with which to qualify the selector.
+ * @returns {string} The qualified selector.
+ */
+function qualifySelector(selector, qualifier)
+{
+ let qualifiedSelector = "";
+
+ for (let sub of splitSelector(selector))
+ {
+ sub = sub.trim();
+
+ qualifiedSelector += ", ";
+
+ let index = findTargetSelectorIndex(sub);
+ let [, type = "", rest] = /^([a-z][a-z-]*)?(.*)/i.exec(sub.substr(index));
+
+ // Note that the first group in the regular expression is optional. If it
+ // doesn't match (e.g. "#foo::nth-child(1)"), type will be an empty string.
+ qualifiedSelector += sub.substr(0, index) + type + qualifier + rest;
+ }
+
+ // Remove the initial comma and space.
+ return qualifiedSelector.substr(2);
+}
+
+exports.qualifySelector = qualifySelector;
« no previous file with comments | « no previous file | lib/content/elemHideEmulation.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld