Loading src/css.cpp +46 −7 Original line number Diff line number Diff line Loading @@ -153,17 +153,54 @@ namespace { return matchSel; } // Selector syntax this matcher can't safely evaluate -- attribute // selectors and pseudo-classes/elements describe context (an ancestor's // attribute) or state (":hover") this matcher has no way to check, and // "*" would blanket-match every element. Guessing wrong here is worse // than not matching at all. // Selector syntax this matcher can't safely evaluate -- pseudo- // classes/elements describe state (":hover") this matcher has no way to // check, and "*" would blanket-match every element. Guessing wrong here // is worse than not matching at all. Attribute selectors ("[href]") are // handled separately (see stripAttributeSelectors) rather than rejected // here, since by the time this runs they've already been removed from // whatever compound they were attached to. bool hasUnsupportedSelectorSyntax(const std::string &matchSel) { return matchSel.find('[') != std::string::npos || matchSel.find(':') != std::string::npos || return matchSel.find(':') != std::string::npos || matchSel == "*"; } // Removes every "[...]" attribute-selector segment from a compound // selector (already combinator-stripped -- see stripCombinator), so a // compound like ".card[data-variant=featured]" is still checkable on // its verifiable ".card" part instead of being thrown away entirely. // This matcher has no per-element attribute map to verify the // condition itself (collectApproximateMatches only ever gets a single // element's tag/class/id, see its own doc comment), so the condition is // simply ignored rather than guessed -- on a component-library site // (e.g. a page builder) nearly every layout/visibility toggle is // expressed as "known class + [data-variant=x]" rather than a distinct // class per variant, so rejecting the whole compound meant NONE of // those rules were ever seen. This can still occasionally pick a // sibling variant's declarations when several "[data-variant=...]" // rules share the same base class (last rule of equal priority wins, // the same cascade approximation used everywhere else in this file), // but that is far closer to the source page than matching nothing. // Malformed/unterminated "[" is left in place rather than risk // corrupting the rest of the compound. std::string stripAttributeSelectors(const std::string &matchSel) { if (matchSel.find('[') == std::string::npos) return matchSel; std::string out; out.reserve(matchSel.size()); size_t pos = 0; while (pos < matchSel.size()) { if (matchSel[pos] == '[') { size_t close = matchSel.find(']', pos + 1); if (close == std::string::npos) { out += matchSel.substr(pos); break; } pos = close + 1; } else { out += matchSel[pos]; ++pos; } } return out; } // Finds the index of the ')' matching the '(' at `openPos` (which must // point at '(' itself), respecting nested parens. Returns npos if // unterminated. Loading Loading @@ -664,6 +701,7 @@ bool libhtmlpp::CSSStyleSheet::approximateSelectorMatch(const std::string &selec bool hadCombinator = false; std::string matchSel = stripCombinator(singleSel, hadCombinator); matchSel = stripAttributeSelectors(matchSel); if (hasUnsupportedSelectorSyntax(matchSel)) continue; CompoundParts compound = parseCompoundSelector(matchSel); Loading Loading @@ -756,6 +794,7 @@ void libhtmlpp::CSSStyleSheet::_rebuildCompoundCache() const { bool hadCombinator = false; std::string matchSel = stripCombinator(singleSel, hadCombinator); matchSel = stripAttributeSelectors(matchSel); if (hasUnsupportedSelectorSyntax(matchSel)) { branch.skip = true; } else { Loading Loading
src/css.cpp +46 −7 Original line number Diff line number Diff line Loading @@ -153,17 +153,54 @@ namespace { return matchSel; } // Selector syntax this matcher can't safely evaluate -- attribute // selectors and pseudo-classes/elements describe context (an ancestor's // attribute) or state (":hover") this matcher has no way to check, and // "*" would blanket-match every element. Guessing wrong here is worse // than not matching at all. // Selector syntax this matcher can't safely evaluate -- pseudo- // classes/elements describe state (":hover") this matcher has no way to // check, and "*" would blanket-match every element. Guessing wrong here // is worse than not matching at all. Attribute selectors ("[href]") are // handled separately (see stripAttributeSelectors) rather than rejected // here, since by the time this runs they've already been removed from // whatever compound they were attached to. bool hasUnsupportedSelectorSyntax(const std::string &matchSel) { return matchSel.find('[') != std::string::npos || matchSel.find(':') != std::string::npos || return matchSel.find(':') != std::string::npos || matchSel == "*"; } // Removes every "[...]" attribute-selector segment from a compound // selector (already combinator-stripped -- see stripCombinator), so a // compound like ".card[data-variant=featured]" is still checkable on // its verifiable ".card" part instead of being thrown away entirely. // This matcher has no per-element attribute map to verify the // condition itself (collectApproximateMatches only ever gets a single // element's tag/class/id, see its own doc comment), so the condition is // simply ignored rather than guessed -- on a component-library site // (e.g. a page builder) nearly every layout/visibility toggle is // expressed as "known class + [data-variant=x]" rather than a distinct // class per variant, so rejecting the whole compound meant NONE of // those rules were ever seen. This can still occasionally pick a // sibling variant's declarations when several "[data-variant=...]" // rules share the same base class (last rule of equal priority wins, // the same cascade approximation used everywhere else in this file), // but that is far closer to the source page than matching nothing. // Malformed/unterminated "[" is left in place rather than risk // corrupting the rest of the compound. std::string stripAttributeSelectors(const std::string &matchSel) { if (matchSel.find('[') == std::string::npos) return matchSel; std::string out; out.reserve(matchSel.size()); size_t pos = 0; while (pos < matchSel.size()) { if (matchSel[pos] == '[') { size_t close = matchSel.find(']', pos + 1); if (close == std::string::npos) { out += matchSel.substr(pos); break; } pos = close + 1; } else { out += matchSel[pos]; ++pos; } } return out; } // Finds the index of the ')' matching the '(' at `openPos` (which must // point at '(' itself), respecting nested parens. Returns npos if // unterminated. Loading Loading @@ -664,6 +701,7 @@ bool libhtmlpp::CSSStyleSheet::approximateSelectorMatch(const std::string &selec bool hadCombinator = false; std::string matchSel = stripCombinator(singleSel, hadCombinator); matchSel = stripAttributeSelectors(matchSel); if (hasUnsupportedSelectorSyntax(matchSel)) continue; CompoundParts compound = parseCompoundSelector(matchSel); Loading Loading @@ -756,6 +794,7 @@ void libhtmlpp::CSSStyleSheet::_rebuildCompoundCache() const { bool hadCombinator = false; std::string matchSel = stripCombinator(singleSel, hadCombinator); matchSel = stripAttributeSelectors(matchSel); if (hasUnsupportedSelectorSyntax(matchSel)) { branch.skip = true; } else { Loading