fix: Strip unused CSS rules (#1014)

## Summary

* In a sample book I loaded, it had 900+ CSS rules, and took up 180kB of
memory loading the cache in
* Looking at the rules, a lot of them were completely useless as we only
ever apply look for 3 kinds of CSS rules:
    * `tag`
    * `tag.class1`
    * `.class1`
* Stripping out CSS rules with descendant, nested, attribute matching,
sibling matching, pseudo element selection (as we never actually read
these from the cache) reduced the rule count down to 200

## Additional Context

* I've left in `.class1.class2` rules for now, even though we
technically can never match on them as they're likely to be addressed
soonest out of the all the CSS expansion
* Because we don't ever delete the CSS cache, users will need to delete
the book cache through the menu in order to get this new logic
* A new PR should be done up to address this - tracked here
https://github.com/crosspoint-reader/crosspoint-reader/issues/1015

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? No
This commit is contained in:
Dave Allie
2026-02-20 16:35:49 +11:00
committed by GitHub
parent 5da23eed82
commit 356fe9a31e

View File

@@ -74,7 +74,7 @@ std::string CssParser::normalized(const std::string& s) {
} }
// Remove trailing space // Remove trailing space
if (!result.empty() && result.back() == ' ') { while (!result.empty() && (result.back() == ' ' || result.back() == '\n')) {
result.pop_back(); result.pop_back();
} }
return result; return result;
@@ -365,6 +365,56 @@ void CssParser::processRuleBlockWithStyle(const std::string& selectorGroup, cons
std::string key = normalized(sel); std::string key = normalized(sel);
if (key.empty()) continue; if (key.empty()) continue;
// TODO: Consider adding support for sibling css selectors in the future
// Ensure no + in selector as we don't support adjacent CSS selectors for now
if (key.find('+') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for direct nested css selectors in the future
// Ensure no > in selector as we don't support nested CSS selectors for now
if (key.find('>') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for attribute css selectors in the future
// Ensure no [ in selector as we don't support attribute CSS selectors for now
if (key.find('[') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for pseudo selectors in the future
// Ensure no : in selector as we don't support pseudo CSS selectors for now
if (key.find(':') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for ID css selectors in the future
// Ensure no # in selector as we don't support ID CSS selectors for now
if (key.find('#') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for general sibling combinator selectors in the future
// Ensure no ~ in selector as we don't support general sibling combinator CSS selectors for now
if (key.find('~') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for wildcard css selectors in the future
// Ensure no * in selector as we don't support wildcard CSS selectors for now
if (key.find('*') != std::string_view::npos) {
continue;
}
// TODO: Add support for more complex selectors in the future
// At the moment, we only ever check for `tag`, `tag.class1` or `.class1`
// If the selector has whitespace in it, then it's either a CSS selector for a descendant element (e.g. `tag1 tag2`)
// or some other slightly more advanced CSS selector which we don't support yet
if (key.find(' ') != std::string_view::npos) {
continue;
}
// Skip if this would exceed the rule limit // Skip if this would exceed the rule limit
if (rulesBySelector_.size() >= MAX_RULES) { if (rulesBySelector_.size() >= MAX_RULES) {
LOG_DBG("CSS", "Reached max rules limit, stopping selector processing"); LOG_DBG("CSS", "Reached max rules limit, stopping selector processing");
@@ -550,6 +600,7 @@ CssStyle CssParser::resolveStyle(const std::string& tagName, const std::string&
result.applyOver(tagIt->second); result.applyOver(tagIt->second);
} }
// TODO: Support combinations of classes (e.g. style on .class1.class2)
// 2. Apply class styles (medium priority) // 2. Apply class styles (medium priority)
if (!classAttr.empty()) { if (!classAttr.empty()) {
const auto classes = splitWhitespace(classAttr); const auto classes = splitWhitespace(classAttr);
@@ -563,6 +614,7 @@ CssStyle CssParser::resolveStyle(const std::string& tagName, const std::string&
} }
} }
// TODO: Support combinations of classes (e.g. style on p.class1.class2)
// 3. Apply element.class styles (higher priority) // 3. Apply element.class styles (higher priority)
for (const auto& cls : classes) { for (const auto& cls : classes) {
std::string combinedKey = tag + "." + normalized(cls); std::string combinedKey = tag + "." + normalized(cls);