Changeset 291098 in webkit


Ignore:
Timestamp:
Mar 10, 2022 2:22:14 AM (4 months ago)
Author:
Antti Koivisto
Message:

[CSS Container Queries] Implement new container selection algorithm
https://bugs.webkit.org/show_bug.cgi?id=237657

Reviewed by Antoine Quint.

LayoutTests/imported/w3c:

  • web-platform-tests/css/css-contain/container-queries/container-selection-expected.txt:
  • web-platform-tests/css/css-contain/container-queries/unsupported-axis-expected.txt:

Source/WebCore:

"For each element, the query container to be queried is selected from among the element’s
ancestor query containers that have a valid container-type for all the container features
in the <container-condition>."

https://drafts.csswg.org/css-contain-3/#container-rule

  • css/ContainerQuery.cpp:

(WebCore::CQ::requiredAxesForFeature):

  • css/ContainerQuery.h:
  • css/ContainerQueryParser.cpp:

(WebCore::ContainerQueryParser::consumeFilteredContainerQuery):

Move container name parsing to ContainerQueryParser too.

(WebCore::ContainerQueryParser::consumeSizeQuery):

Collect required axes during parsing.

  • css/ContainerQueryParser.h:
  • css/parser/CSSParserImpl.cpp:

(WebCore::CSSParserImpl::consumeContainerRule):

  • style/ContainerQueryEvaluator.cpp:

(WebCore::Style::ContainerQueryEvaluator::evaluate const):
(WebCore::Style::ContainerQueryEvaluator::selectContainer const):

Select container based on required axes for the features being used.

(WebCore::Style::ContainerQueryEvaluator::evaluateQuery const):
(WebCore::Style::ContainerQueryEvaluator::evaluateCondition const):
(WebCore::Style::ContainerQueryEvaluator::evaluateSizeFeature const):

No need to check axes during evaluation anymore. We only evaluate against containers that support them.

(WebCore::Style::ContainerQueryEvaluator::resolveContainer const): Deleted.

Rename resolveContainer -> selectContainer to match the spec.

  • style/ContainerQueryEvaluator.h:
Location:
trunk
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/imported/w3c/ChangeLog

    r291092 r291098  
     12022-03-10  Antti Koivisto  <antti@apple.com>
     2
     3        [CSS Container Queries] Implement new container selection algorithm
     4        https://bugs.webkit.org/show_bug.cgi?id=237657
     5
     6        Reviewed by Antoine Quint.
     7
     8        * web-platform-tests/css/css-contain/container-queries/container-selection-expected.txt:
     9        * web-platform-tests/css/css-contain/container-queries/unsupported-axis-expected.txt:
     10
    1112022-03-09  Youenn Fablet  <youenn@apple.com>
    212
  • trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-contain/container-queries/container-selection-expected.txt

    r291036 r291098  
    33PASS (height: 16px) for .inline > .size > span
    44PASS (width: 16px) for .inline > .size > span
    5 FAIL (height: 32px) for .size > .inline > span assert_equals: expected "true" but got ""
     5PASS (height: 32px) for .size > .inline > span
    66PASS (height: 16px) for .size > .inline > span
    77PASS a (width: 32px) for .a-size > .b-size > span
     
    1515PASS a (width: 8px) for .a-size > .b-size > .a-inline > span
    1616PASS b (width: 16px) for .a-size > .b-size > .a-inline > span
    17 FAIL a (height: 32px) for .a-size > .b-size > .a-inline > span assert_equals: expected "true" but got ""
     17PASS a (height: 32px) for .a-size > .b-size > .a-inline > span
    1818PASS a (height) for .a-inline > .b-size
    1919PASS a (inline-size: 8px) for .a-size > .b-size > .a-inline > span
    2020PASS b (inline-size: 16px) for .a-size > .b-size > .a-inline > span
    21 FAIL a (block-size: 32px) for .a-size > .b-size > .a-inline > span assert_equals: expected "true" but got ""
     21PASS a (block-size: 32px) for .a-size > .b-size > .a-inline > span
    2222PASS a (block-size) for .a-inline > .b-size
    2323
  • trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-contain/container-queries/unsupported-axis-expected.txt

    r291036 r291098  
    33PASS (width > 0px)
    44PASS (height > 0px)
    5 FAIL ((height > 0px) or (width > 0px)) assert_equals: expected "" but got "true"
    6 FAIL ((width > 0px) or (height > 0px)) assert_equals: expected "" but got "true"
    7 FAIL ((orientation: landscape) or (width > 0px)) assert_equals: expected "" but got "true"
    8 FAIL ((width > 0px) or (orientation: landscape)) assert_equals: expected "" but got "true"
     5PASS ((height > 0px) or (width > 0px))
     6PASS ((width > 0px) or (height > 0px))
     7PASS ((orientation: landscape) or (width > 0px))
     8PASS ((width > 0px) or (orientation: landscape))
    99PASS ((height > 0px) or (orientation: landscape))
    1010PASS ((height > 0px) or (orientation: landscape)), with contain:size
  • trunk/Source/WebCore/ChangeLog

    r291096 r291098  
     12022-03-10  Antti Koivisto  <antti@apple.com>
     2
     3        [CSS Container Queries] Implement new container selection algorithm
     4        https://bugs.webkit.org/show_bug.cgi?id=237657
     5
     6        Reviewed by Antoine Quint.
     7
     8        "For each element, the query container to be queried is selected from among the element’s
     9        ancestor query containers that have a valid container-type for all the container features
     10        in the <container-condition>."
     11
     12        https://drafts.csswg.org/css-contain-3/#container-rule
     13
     14        * css/ContainerQuery.cpp:
     15        (WebCore::CQ::requiredAxesForFeature):
     16        * css/ContainerQuery.h:
     17        * css/ContainerQueryParser.cpp:
     18        (WebCore::ContainerQueryParser::consumeFilteredContainerQuery):
     19
     20        Move container name parsing to ContainerQueryParser too.
     21
     22        (WebCore::ContainerQueryParser::consumeSizeQuery):
     23
     24        Collect required axes during parsing.
     25
     26        * css/ContainerQueryParser.h:
     27        * css/parser/CSSParserImpl.cpp:
     28        (WebCore::CSSParserImpl::consumeContainerRule):
     29        * style/ContainerQueryEvaluator.cpp:
     30        (WebCore::Style::ContainerQueryEvaluator::evaluate const):
     31        (WebCore::Style::ContainerQueryEvaluator::selectContainer const):
     32
     33        Select container based on required axes for the features being used.
     34
     35        (WebCore::Style::ContainerQueryEvaluator::evaluateQuery const):
     36        (WebCore::Style::ContainerQueryEvaluator::evaluateCondition const):
     37        (WebCore::Style::ContainerQueryEvaluator::evaluateSizeFeature const):
     38
     39        No need to check axes during evaluation anymore. We only evaluate against containers that support them.
     40
     41        (WebCore::Style::ContainerQueryEvaluator::resolveContainer const): Deleted.
     42
     43        Rename resolveContainer -> selectContainer to match the spec.
     44
     45        * style/ContainerQueryEvaluator.h:
     46
    1472022-03-07  Carlos Garcia Campos  <cgarcia@igalia.com>
    248
  • trunk/Source/WebCore/css/ContainerQuery.cpp

    r289838 r291098  
    2828#include <wtf/NeverDestroyed.h>
    2929
    30 namespace WebCore::CQ::FeatureNames {
     30namespace WebCore::CQ {
     31
     32namespace FeatureNames {
    3133
    3234const AtomString& width()
     
    6870}
    6971
     72OptionSet<Axis> requiredAxesForFeature(const AtomString& featureName)
     73{
     74    if (featureName == FeatureNames::width())
     75        return { Axis::Width };
     76    if (featureName == FeatureNames::height())
     77        return { Axis::Height };
     78    if (featureName == FeatureNames::inlineSize())
     79        return { Axis::Inline };
     80    if (featureName == FeatureNames::blockSize())
     81        return { Axis::Block };
     82    if (featureName == FeatureNames::aspectRatio() || featureName == FeatureNames::orientation())
     83        return { Axis::Inline, Axis::Block };
     84    return { };
     85}
     86
     87}
     88
  • trunk/Source/WebCore/css/ContainerQuery.h

    r290037 r291098  
    7575};
    7676
     77enum class Axis : uint8_t {
     78    Block   = 1 << 0,
     79    Inline  = 1 << 1,
     80    Width   = 1 << 2,
     81    Height  = 1 << 3,
     82};
     83OptionSet<Axis> requiredAxesForFeature(const AtomString&);
     84
    7785}
    7886
     
    8189struct FilteredContainerQuery {
    8290    AtomString nameFilter;
     91    OptionSet<CQ::Axis> axisFilter;
    8392    ContainerQuery query;
    8493};
  • trunk/Source/WebCore/css/ContainerQueryParser.cpp

    r291046 r291098  
    3131namespace WebCore {
    3232
    33 std::optional<ContainerQuery> ContainerQueryParser::consumeContainerQuery(CSSParserTokenRange& range, const CSSParserContext& context)
     33std::optional<FilteredContainerQuery> ContainerQueryParser::consumeFilteredContainerQuery(CSSParserTokenRange& range, const CSSParserContext& context)
    3434{
    3535    ContainerQueryParser parser(context);
    36     return parser.consumeContainerQuery(range);
     36    return parser.consumeFilteredContainerQuery(range);
     37}
     38
     39std::optional<FilteredContainerQuery> ContainerQueryParser::consumeFilteredContainerQuery(CSSParserTokenRange& range)
     40{
     41    auto consumeName = [&]() -> AtomString {
     42        if (range.peek().type() == LeftParenthesisToken || range.peek().type() == FunctionToken)
     43            return nullAtom();
     44        auto nameValue = CSSPropertyParserHelpers::consumeSingleContainerName(range);
     45        if (!nameValue)
     46            return nullAtom();
     47        return nameValue->stringValue();
     48    };
     49
     50    auto name = consumeName();
     51
     52    m_requiredAxes = { };
     53
     54    auto query = consumeContainerQuery(range);
     55    if (!query)
     56        return { };
     57
     58    return FilteredContainerQuery { name, m_requiredAxes, *query };
    3759}
    3860
     
    138160    if (!sizeFeature)
    139161        return { };
     162
     163    m_requiredAxes.add(CQ::requiredAxesForFeature(sizeFeature->name));
    140164
    141165    return { *sizeFeature };
  • trunk/Source/WebCore/css/ContainerQueryParser.h

    r290037 r291098  
    3535class ContainerQueryParser {
    3636public:
    37     static std::optional<ContainerQuery> consumeContainerQuery(CSSParserTokenRange&, const CSSParserContext&);
     37    static std::optional<FilteredContainerQuery> consumeFilteredContainerQuery(CSSParserTokenRange&, const CSSParserContext&);
    3838
    3939private:
     40    std::optional<FilteredContainerQuery> consumeFilteredContainerQuery(CSSParserTokenRange&);
    4041    std::optional<CQ::ContainerQuery> consumeContainerQuery(CSSParserTokenRange&);
    4142    std::optional<CQ::SizeQuery> consumeSizeQuery(CSSParserTokenRange&);
     
    5051
    5152    const CSSParserContext m_context;
     53
     54    OptionSet<CQ::Axis> m_requiredAxes;
    5255};
    5356
  • trunk/Source/WebCore/css/parser/CSSParserImpl.cpp

    r289742 r291098  
    856856        return nullptr;
    857857
    858     auto consumeName = [&]() -> AtomString {
    859         if (prelude.peek().type() == LeftParenthesisToken || prelude.peek().type() == FunctionToken)
    860             return nullAtom();
    861         auto nameValue = CSSPropertyParserHelpers::consumeSingleContainerName(prelude);
    862         if (!nameValue)
    863             return nullAtom();
    864         return nameValue->stringValue();
    865     };
    866 
    867     auto name = consumeName();
    868 
    869     auto query = ContainerQueryParser::consumeContainerQuery(prelude, m_context);
     858    auto query = ContainerQueryParser::consumeFilteredContainerQuery(prelude, m_context);
    870859    if (!query)
    871860        return nullptr;
     
    876865
    877866    if (m_deferredParser)
    878         return StyleRuleContainer::create({ name, *query }, makeUnique<DeferredStyleGroupRuleList>(block, *m_deferredParser));
     867        return StyleRuleContainer::create(WTFMove(*query), makeUnique<DeferredStyleGroupRuleList>(block, *m_deferredParser));
    879868
    880869    Vector<RefPtr<StyleRuleBase>> rules;
     
    894883        m_observerWrapper->observer().endRuleBody(m_observerWrapper->endOffset(block));
    895884
    896     return StyleRuleContainer::create({ name, *query }, WTFMove(rules));
     885    return StyleRuleContainer::create(WTFMove(*query), WTFMove(rules));
    897886}
    898887   
  • trunk/Source/WebCore/style/ContainerQueryEvaluator.cpp

    r290681 r291098  
    3939namespace WebCore::Style {
    4040
    41 struct ContainerQueryEvaluator::ResolvedContainer {
     41struct ContainerQueryEvaluator::SelectedContainer {
    4242    const RenderBox* renderer { nullptr };
    4343    CSSToLengthConversionData conversionData;
     
    5353bool ContainerQueryEvaluator::evaluate(const FilteredContainerQuery& filteredContainerQuery) const
    5454{
    55     auto container = resolveContainer(filteredContainerQuery);
     55    auto container = selectContainer(filteredContainerQuery);
    5656    if (!container)
    5757        return false;
     
    6060}
    6161
    62 auto ContainerQueryEvaluator::resolveContainer(const FilteredContainerQuery& filteredContainerQuery) const -> std::optional<ResolvedContainer>
    63 {
    64     auto makeResolvedContainer = [](const Element& element) -> ResolvedContainer {
     62auto ContainerQueryEvaluator::selectContainer(const FilteredContainerQuery& filteredContainerQuery) const -> std::optional<SelectedContainer>
     63{
     64    // "For each element, the query container to be queried is selected from among the element’s
     65    // ancestor query containers that have a valid container-type for all the container features
     66    // in the <container-condition>. The optional <container-name> filters the set of query containers
     67    // considered to just those with a matching query container name."
     68    // https://drafts.csswg.org/css-contain-3/#container-rule
     69
     70    auto makeSelectedContainer = [](const Element& element) -> SelectedContainer {
    6571        auto* renderer = dynamicDowncast<RenderBox>(element.renderer());
    6672        if (!renderer)
    6773            return { };
    6874        auto& view = renderer->view();
    69         return ResolvedContainer {
     75        return {
    7076            renderer,
    7177            CSSToLengthConversionData { &renderer->style(), &view.style(), nullptr, &view, 1 }
    7278        };
     79    };
     80
     81    auto computeUnsupportedAxes = [&](ContainerType containerType, const RenderElement* principalBox) -> OptionSet<CQ::Axis> {
     82        switch (containerType) {
     83        case ContainerType::Size:
     84            return { };
     85        case ContainerType::InlineSize:
     86            // Without a principal box the container matches but the query against it will evaluate to Unknown.
     87            if (!principalBox)
     88                return { };
     89            if (!principalBox->isHorizontalWritingMode())
     90                return { CQ::Axis::Width, CQ::Axis::Block };
     91            return { CQ::Axis::Height, CQ::Axis::Block };
     92        case ContainerType::None:
     93            return { CQ::Axis::Width, CQ::Axis::Height, CQ::Axis::Inline, CQ::Axis::Block };
     94        }
     95        RELEASE_ASSERT_NOT_REACHED();
    7396    };
    7497
     
    77100        if (!style)
    78101            return false;
    79         if (style->containerType() == ContainerType::None)
     102        auto unsupportedAxes = computeUnsupportedAxes(style->containerType(), element.renderer());
     103        if (filteredContainerQuery.axisFilter.containsAny(unsupportedAxes))
    80104            return false;
    81105        if (filteredContainerQuery.nameFilter.isEmpty())
    82106            return true;
    83         return style->containerNames().contains(filteredContainerQuery.nameFilter);
     107        return element.existingComputedStyle()->containerNames().contains(filteredContainerQuery.nameFilter);
    84108    };
    85109
     
    87111        for (auto& container : makeReversedRange(m_selectorMatchingState->queryContainers)) {
    88112            if (isContainerForQuery(container))
    89                 return makeResolvedContainer(container);
     113                return makeSelectedContainer(container);
    90114        }
    91115        return { };
     
    94118    if (m_pseudoId != PseudoId::None) {
    95119        if (isContainerForQuery(m_element))
    96             return makeResolvedContainer(m_element);
     120            return makeSelectedContainer(m_element);
    97121    }
    98122
    99123    for (auto& ancestor : composedTreeAncestors(const_cast<Element&>(m_element.get()))) {
    100124        if (isContainerForQuery(ancestor))
    101             return makeResolvedContainer(ancestor);
     125            return makeSelectedContainer(ancestor);
    102126    }
    103127    return { };
    104128}
    105129
    106 
    107 auto ContainerQueryEvaluator::evaluateQuery(const CQ::ContainerQuery& containerQuery, const ResolvedContainer& container) const -> EvaluationResult
     130auto ContainerQueryEvaluator::evaluateQuery(const CQ::ContainerQuery& containerQuery, const SelectedContainer& container) const -> EvaluationResult
    108131{
    109132    return WTF::switchOn(containerQuery, [&](const CQ::ContainerCondition& containerCondition) {
     
    116139}
    117140
    118 auto ContainerQueryEvaluator::evaluateQuery(const CQ::SizeQuery& sizeQuery, const ResolvedContainer& container) const -> EvaluationResult
     141auto ContainerQueryEvaluator::evaluateQuery(const CQ::SizeQuery& sizeQuery, const SelectedContainer& container) const -> EvaluationResult
    119142{
    120143    return WTF::switchOn(sizeQuery, [&](const CQ::SizeCondition& sizeCondition) {
     
    126149
    127150template<typename ConditionType>
    128 auto ContainerQueryEvaluator::evaluateCondition(const ConditionType& condition, const ResolvedContainer& container) const -> EvaluationResult
     151auto ContainerQueryEvaluator::evaluateCondition(const ConditionType& condition, const SelectedContainer& container) const -> EvaluationResult
    129152{
    130153    if (condition.queries.isEmpty())
     
    177200}
    178201
    179 auto ContainerQueryEvaluator::evaluateSizeFeature(const CQ::SizeFeature& sizeFeature, const ResolvedContainer& container) const -> EvaluationResult
     202auto ContainerQueryEvaluator::evaluateSizeFeature(const CQ::SizeFeature& sizeFeature, const SelectedContainer& container) const -> EvaluationResult
    180203{
    181204    // "If the query container does not have a principal box ... then the result of evaluating the size feature is unknown."
     
    250273    };
    251274
    252     enum class Axis : uint8_t { Both, Block, Inline, Width, Height };
    253     auto containerSupportsRequiredAxis = [&](Axis axis) {
    254         switch (renderer.style().containerType()) {
    255         case ContainerType::Size:
    256             return true;
    257         case ContainerType::InlineSize:
    258             if (axis == Axis::Width)
    259                 return renderer.isHorizontalWritingMode();
    260             if (axis == Axis::Height)
    261                 return !renderer.isHorizontalWritingMode();
    262             return axis == Axis::Inline;
    263         case ContainerType::None:
    264             RELEASE_ASSERT_NOT_REACHED();
    265         }
    266         RELEASE_ASSERT_NOT_REACHED();
    267     };
    268 
    269     if (sizeFeature.name == CQ::FeatureNames::width()) {
    270         if (!containerSupportsRequiredAxis(Axis::Width))
    271             return EvaluationResult::Unknown;
    272 
     275    if (sizeFeature.name == CQ::FeatureNames::width())
    273276        return evaluateSize(renderer.contentWidth());
    274     }
    275 
    276     if (sizeFeature.name == CQ::FeatureNames::height()) {
    277         if (!containerSupportsRequiredAxis(Axis::Height))
    278             return EvaluationResult::Unknown;
    279 
     277
     278    if (sizeFeature.name == CQ::FeatureNames::height())
    280279        return evaluateSize(renderer.contentHeight());
    281     }
    282 
    283     if (sizeFeature.name == CQ::FeatureNames::inlineSize()) {
    284         if (!containerSupportsRequiredAxis(Axis::Inline))
    285             return EvaluationResult::Unknown;
    286 
     280
     281    if (sizeFeature.name == CQ::FeatureNames::inlineSize())
    287282        return evaluateSize(renderer.contentLogicalWidth());
    288     }
    289 
    290     if (sizeFeature.name == CQ::FeatureNames::blockSize()) {
    291         if (!containerSupportsRequiredAxis(Axis::Block))
    292             return EvaluationResult::Unknown;
    293 
     283
     284    if (sizeFeature.name == CQ::FeatureNames::blockSize())
    294285        return evaluateSize(renderer.contentLogicalHeight());
    295     }
    296286
    297287    if (sizeFeature.name == CQ::FeatureNames::aspectRatio()) {
    298         if (!containerSupportsRequiredAxis(Axis::Both))
    299             return EvaluationResult::Unknown;
    300 
    301288        auto boxRatio = renderer.contentWidth().toDouble() / renderer.contentHeight().toDouble();
    302289       
     
    311298
    312299    if (sizeFeature.name == CQ::FeatureNames::orientation()) {
    313         if (!containerSupportsRequiredAxis(Axis::Both))
    314             return EvaluationResult::Unknown;
    315 
    316300        if (!sizeFeature.rightComparison)
    317301            return EvaluationResult::Unknown;
  • trunk/Source/WebCore/style/ContainerQueryEvaluator.h

    r290681 r291098  
    4444
    4545private:
    46     struct ResolvedContainer;
    47     std::optional<ResolvedContainer> resolveContainer(const FilteredContainerQuery&) const;
     46    struct SelectedContainer;
     47    std::optional<SelectedContainer> selectContainer(const FilteredContainerQuery&) const;
    4848
    49     EvaluationResult evaluateQuery(const CQ::ContainerQuery&, const ResolvedContainer&) const;
    50     EvaluationResult evaluateQuery(const CQ::SizeQuery&, const ResolvedContainer&) const;
    51     template<typename ConditionType> EvaluationResult evaluateCondition(const ConditionType&, const ResolvedContainer&) const;
    52     EvaluationResult evaluateSizeFeature(const CQ::SizeFeature&, const ResolvedContainer&) const;
     49    EvaluationResult evaluateQuery(const CQ::ContainerQuery&, const SelectedContainer&) const;
     50    EvaluationResult evaluateQuery(const CQ::SizeQuery&, const SelectedContainer&) const;
     51    template<typename ConditionType> EvaluationResult evaluateCondition(const ConditionType&, const SelectedContainer&) const;
     52    EvaluationResult evaluateSizeFeature(const CQ::SizeFeature&, const SelectedContainer&) const;
    5353
    5454    const Ref<const Element> m_element;
Note: See TracChangeset for help on using the changeset viewer.