Commit 1e9c0b2e authored by jan.koester's avatar jan.koester
Browse files

test

parent 572d2181
Loading
Loading
Loading
Loading
+24 −39
Original line number Diff line number Diff line
@@ -225,37 +225,34 @@ namespace {
        return tagOk && idOk && classesOk;
    }

    // Whether `compound` is a plain bare-tag selector (no class/id/attribute
    // qualifier of its own) that only shows up as the TARGET compound
    // because a leading ancestor combinator was stripped off it -- e.g.
    // "[data-atom=header]>div" reduces to a bare "div" here. See this
    // function's callers for why that matters: on its own such a target
    // can't be trusted (confirmed against a real scraped page where exactly
    // this pattern matched ~24,000 times and blew up an HTML-import's
    // output to 36MB, back when this matcher had no way to verify the
    // ancestor side of the selector either) -- it's only safe to accept once
    // every one of this rule's ancestor compounds has ALSO been genuinely
    // verified against a real candidate ancestor, not vacuously satisfied
    // (see ancestorChainSatisfies/usedUnverifiableAncestor). A class/id/
    // attribute qualifier alongside the tag (e.g. "div.foo") is already
    // specific enough to check on its own and never counts as "bare" here.
    bool isBareTagAfterCombinator(const CompoundParts &compound, bool hadCombinator) {
        return hadCombinator && compound.classes.empty() && compound.id.empty() &&
               compound.attrConditions.empty() && !compound.tag.empty();
    }

    // A selector matches only if EVERY part it specifies is present on this
    // element -- in particular every class listed, not just one. Whether a
    // bare-tag target (see isBareTagAfterCombinator) should be trusted here
    // is the CALLER's decision (it depends on how the ancestor side of the
    // same rule verifies, which isn't known yet at this point) -- this
    // function only checks the target compound itself.
    // element -- in particular every class listed, not just one. A bare tag
    // left over after stripping a combinator's ancestor part (e.g.
    // "[data-atom=header]>div" reduces to bare "div") is rejected outright
    // for the TARGET compound, even when @p targetAttrs/ancestor
    // verification is available: on a real component-library/page-builder
    // site, a generic "layout plumbing" ancestor condition like
    // "[data-atom][data-atom=header]" is common enough (used on nearly
    // every content block) that a bare "div" child of it is still
    // effectively a near-blanket match once combined with this file's
    // specificity ordering (see computeSpecificity) -- confirmed on a real
    // scraped page: relaxing this guard for a "genuinely verified" ancestor
    // let "[data-atom][data-atom=header]>div{width:var(--a-width)}" (pure
    // layout plumbing, specificity (0,2,1)) win over a much more specific,
    // content-driven ".status-dot{width:12px}" (specificity (0,1,0)) rule
    // purely because the plumbing rule now qualified as a real match, even
    // though genuinely verifying its own ancestor chain didn't make it safe
    // to trust the bare-tag side too. A class/id/attribute qualifier
    // alongside the tag (e.g. "div.foo") is already specific enough to
    // check on its own and never triggers this rejection.
    bool compoundMatches(const CompoundParts &compound, bool hadCombinator,
                          const std::string &tag, const std::vector<std::string> &classes,
                          const std::string &id,
                          const std::map<std::string,std::string> *targetAttrs = nullptr)
    {
        (void)hadCombinator; // kept for signature/call-site symmetry with isBareTagAfterCombinator
        bool bareTagAfterCombinator = hadCombinator &&
            compound.classes.empty() && compound.id.empty() &&
            compound.attrConditions.empty() && !compound.tag.empty();
        // An attribute condition only counts as "this compound specifies a
        // real requirement" when @p targetAttrs is actually available to
        // check it against -- a caller with no attribute map (e.g.
@@ -266,7 +263,7 @@ namespace {
        bool specifiedSomething = !compound.tag.empty() || !compound.classes.empty() ||
                                   !compound.id.empty() ||
                                   (!compound.attrConditions.empty() && targetAttrs != nullptr);
        if (!specifiedSomething) return false;
        if (bareTagAfterCombinator || !specifiedSomething) return false;

        return compoundPartsMatch(compound, tag, classes, id) &&
               attributeConditionsSatisfied(compound.attrConditions, targetAttrs);
@@ -978,7 +975,6 @@ bool libhtmlpp::CSSStyleSheet::approximateSelectorMatch(const std::string &selec
        compound.attrConditions = parseAttributeConditions(chain.back());
        if (!compoundMatches(compound, hadCombinator, tagLower, classes, id)) continue;

        bool usedUnverifiableAncestor = false;
        if (ancestors && chain.size() > 1) {
            std::vector<CompoundParts> ancestorCompounds;
            for (size_t i = 0; i + 1 < chain.size(); ++i) {
@@ -991,11 +987,8 @@ bool libhtmlpp::CSSStyleSheet::approximateSelectorMatch(const std::string &selec
                    ancestorCompounds.push_back(std::move(aCompound));
                }
            }
            if (!ancestorChainSatisfies(ancestorCompounds, *ancestors, &usedUnverifiableAncestor))
                continue;
            if (!ancestorChainSatisfies(ancestorCompounds, *ancestors)) continue;
        }
        if (isBareTagAfterCombinator(compound, hadCombinator) && (!ancestors || usedUnverifiableAncestor))
            continue;
        return true;
    }
    return false;
@@ -1242,14 +1235,6 @@ void libhtmlpp::CSSStyleSheet::collectApproximateMatches(
                         (key == "visibility" && value == "hidden")))
                        continue;

                    if (key == "width" &&
                        (std::find(classes.begin(), classes.end(), "status-dot") != classes.end() ||
                         std::find(classes.begin(), classes.end(), "appointment-badge") != classes.end())) {
                        fprintf(stderr, "[WDBG] class=%s width=%s important=%d sel=[%s] spec=(%d,%d,%d)\n",
                                cssClass.c_str(), value.c_str(), isImportant,
                                branch.trimmedSelector.c_str(),
                                branch.specificity[0], branch.specificity[1], branch.specificity[2]);
                    }
                    props[key] = value;
                    specOf[key] = branch.specificity;
                    if (isImportant) importantKeys.insert(key);