[CSS Container Queries] Basic parsing support for query syntax
https://bugs.webkit.org/show_bug.cgi?id=235712

Reviewed by Darin Adler.

LayoutTests/imported/w3c:

* web-platform-tests/css/css-contain/container-queries/at-container-parsing-expected.txt:

Source/WebCore:

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

Use MediaQueryParser to parse the query syntax. Note that because the limitations
of MediaQueryParser only simple non-nested queries can be parsed for now.

* css/StyleRule.h:
* css/parser/CSSParserImpl.cpp:
(WebCore::filterProperties):
(WebCore::CSSParserImpl::consumeAtRule):
(WebCore::CSSParserImpl::consumeContainerRule):
* css/parser/CSSPropertyParser.cpp:
(WebCore::consumeContainerName):
* css/parser/CSSPropertyParserHelpers.cpp:
(WebCore::CSSPropertyParserHelpers::consumeSingleContainerName):
* css/parser/CSSPropertyParserHelpers.h:
* css/parser/MediaQueryParser.cpp:
(WebCore::MediaQueryParser::parseContainerQuery):
(WebCore::MediaQueryParser::MediaQueryParser):
(WebCore::MediaQueryParser::readContainerQuery):
(WebCore::MediaQueryParser::handleBlocks):
(WebCore::MediaQueryParser::processToken):
(WebCore::MediaQueryParser::parseInternal):
* css/parser/MediaQueryParser.h:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@288675 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/imported/w3c/ChangeLog b/LayoutTests/imported/w3c/ChangeLog
index b598431..f7fa83d 100644
--- a/LayoutTests/imported/w3c/ChangeLog
+++ b/LayoutTests/imported/w3c/ChangeLog
@@ -1,3 +1,12 @@
+2022-01-27  Antti Koivisto  <antti@apple.com>
+
+        [CSS Container Queries] Basic parsing support for query syntax
+        https://bugs.webkit.org/show_bug.cgi?id=235712
+
+        Reviewed by Darin Adler.
+
+        * web-platform-tests/css/css-contain/container-queries/at-container-parsing-expected.txt:
+
 2022-01-26  Alexey Shvayka  <ashvayka@apple.com>
 
         globalThis.queueMicrotask() should report thrown exceptions
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-contain/container-queries/at-container-parsing-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-contain/container-queries/at-container-parsing-expected.txt
index e0178c2..502b9f7 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/css/css-contain/container-queries/at-container-parsing-expected.txt
+++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-contain/container-queries/at-container-parsing-expected.txt
@@ -33,14 +33,14 @@
 PASS (color-index >= 1)
 PASS size(grid)
 PASS (grid)
-FAIL screen assert_equals: expected 0 but got 1
-FAIL print assert_equals: expected 0 but got 1
-FAIL not print assert_equals: expected 0 but got 1
-FAIL only print assert_equals: expected 0 but got 1
-FAIL screen and (width: 100px) assert_equals: expected 0 but got 1
-FAIL screen or (width: 100px) assert_equals: expected 0 but got 1
-FAIL not screen and (width: 100px) assert_equals: expected 0 but got 1
-FAIL not screen or (width: 100px) assert_equals: expected 0 but got 1
+PASS screen
+PASS print
+PASS not print
+PASS only print
+PASS screen and (width: 100px)
+PASS screen or (width: 100px)
+PASS not screen and (width: 100px)
+PASS not screen or (width: 100px)
 FAIL (width: 100px), (height: 100px) assert_equals: expected 0 but got 1
 PASS Container selector:  foo
 PASS Container selector:  foo
@@ -52,9 +52,9 @@
 PASS Container selector: type(block-size)
 PASS Container selector: name(bar) type(block-size)
 PASS Container selector: type(block-size) name(bar)
-FAIL Container selector: foo foo assert_equals: expected 0 but got 1
-FAIL Container selector: 1px assert_equals: expected 0 but got 1
-FAIL Container selector: 50gil assert_equals: expected 0 but got 1
+PASS Container selector: foo foo
+PASS Container selector: 1px
+PASS Container selector: 50gil
 FAIL Container selector: name(1px) assert_equals: expected 0 but got 1
 FAIL Container selector: type(1px) assert_equals: expected 0 but got 1
 FAIL Container selector: type(red) assert_equals: expected 0 but got 1
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 7d96218..df4dceb 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,34 @@
+2022-01-27  Antti Koivisto  <antti@apple.com>
+
+        [CSS Container Queries] Basic parsing support for query syntax
+        https://bugs.webkit.org/show_bug.cgi?id=235712
+
+        Reviewed by Darin Adler.
+
+        https://drafts.csswg.org/css-contain-3/#at-ruledef-container
+
+        Use MediaQueryParser to parse the query syntax. Note that because the limitations
+        of MediaQueryParser only simple non-nested queries can be parsed for now.
+
+        * css/StyleRule.h:
+        * css/parser/CSSParserImpl.cpp:
+        (WebCore::filterProperties):
+        (WebCore::CSSParserImpl::consumeAtRule):
+        (WebCore::CSSParserImpl::consumeContainerRule):
+        * css/parser/CSSPropertyParser.cpp:
+        (WebCore::consumeContainerName):
+        * css/parser/CSSPropertyParserHelpers.cpp:
+        (WebCore::CSSPropertyParserHelpers::consumeSingleContainerName):
+        * css/parser/CSSPropertyParserHelpers.h:
+        * css/parser/MediaQueryParser.cpp:
+        (WebCore::MediaQueryParser::parseContainerQuery):
+        (WebCore::MediaQueryParser::MediaQueryParser):
+        (WebCore::MediaQueryParser::readContainerQuery):
+        (WebCore::MediaQueryParser::handleBlocks):
+        (WebCore::MediaQueryParser::processToken):
+        (WebCore::MediaQueryParser::parseInternal):
+        * css/parser/MediaQueryParser.h:
+
 2022-01-27  Tyler Wilcock  <tyler_w@apple.com>
 
         AX ITM: Defer to the tree when determining AX object loading progress
diff --git a/Source/WebCore/css/StyleRule.h b/Source/WebCore/css/StyleRule.h
index cd15f87..5e1c07b 100644
--- a/Source/WebCore/css/StyleRule.h
+++ b/Source/WebCore/css/StyleRule.h
@@ -314,7 +314,10 @@
     std::variant<CascadeLayerName, Vector<CascadeLayerName>> m_nameVariant;
 };
 
-struct ContainerQuery { };
+struct ContainerQuery {
+    AtomString containerName;
+    Ref<MediaQuerySet> query;
+};
 
 class StyleRuleContainer final : public StyleRuleGroup {
 public:
diff --git a/Source/WebCore/css/parser/CSSParserImpl.cpp b/Source/WebCore/css/parser/CSSParserImpl.cpp
index 9d1a6e9..f7d5592 100644
--- a/Source/WebCore/css/parser/CSSParserImpl.cpp
+++ b/Source/WebCore/css/parser/CSSParserImpl.cpp
@@ -123,12 +123,6 @@
             output[--unusedEntries] = property;
             continue;
         }
-        
-        // FIXME-NEWPARSER: We won't support @apply yet.
-        /*else if (property.id() == CSSPropertyApplyAtRule) {
-         // FIXME: Do we need to do anything here?
-         } */
-        
 
         if (seenProperties.test(propertyIDIndex))
             continue;
@@ -430,11 +424,6 @@
             return consumeNamespaceRule(prelude);
         if (allowedRules <= RegularRules && id == CSSAtRuleLayer)
             return consumeLayerRule(prelude, { });
-        // FIXME-NEWPARSER: Support "apply"
-        /*if (allowedRules == ApplyRules && id == CSSAtRuleApply) {
-            consumeApplyRule(prelude);
-            return nullptr; // consumeApplyRule just updates m_parsedProperties
-        }*/
         return nullptr; // Parse error, unrecognised at-rule without block
     }
 
@@ -623,7 +612,7 @@
     if (m_observerWrapper)
         m_observerWrapper->observer().endRuleBody(m_observerWrapper->endOffset(block));
 
-    return StyleRuleMedia::create(MediaQueryParser::parseMediaQuerySet(prelude, MediaQueryParserContext(m_context)).releaseNonNull(), WTFMove(rules));
+    return StyleRuleMedia::create(MediaQueryParser::parseMediaQuerySet(prelude, { m_context }).releaseNonNull(), WTFMove(rules));
 }
 
 RefPtr<StyleRuleSupports> CSSParserImpl::consumeSupportsRule(CSSParserTokenRange prelude, CSSParserTokenRange block)
@@ -862,8 +851,26 @@
     if (!m_context.containerQueriesEnabled)
         return nullptr;
 
+    if (prelude.atEnd())
+        return nullptr;
+
+    auto consumeName = [&]() -> AtomString {
+        if (prelude.peek().type() == LeftParenthesisToken || prelude.peek().type() == FunctionToken)
+            return nullAtom();
+        auto nameValue = CSSPropertyParserHelpers::consumeSingleContainerName(prelude);
+        if (!nameValue)
+            return nullAtom();
+        return nameValue->stringValue();
+    };
+
+    auto name = consumeName();
+
+    auto query = MediaQueryParser::parseContainerQuery(prelude, MediaQueryParserContext(m_context));
+    if (!query)
+        return nullptr;
+
     if (m_deferredParser)
-        return StyleRuleContainer::create({ }, makeUnique<DeferredStyleGroupRuleList>(block, *m_deferredParser));
+        return StyleRuleContainer::create({ name, query.releaseNonNull() }, makeUnique<DeferredStyleGroupRuleList>(block, *m_deferredParser));
 
     Vector<RefPtr<StyleRuleBase>> rules;
 
@@ -881,7 +888,7 @@
     if (m_observerWrapper)
         m_observerWrapper->observer().endRuleBody(m_observerWrapper->endOffset(block));
 
-    return StyleRuleContainer::create({ }, WTFMove(rules));
+    return StyleRuleContainer::create({ name, query.releaseNonNull() }, WTFMove(rules));
 }
     
 RefPtr<StyleRuleKeyframe> CSSParserImpl::consumeKeyframeStyleRule(CSSParserTokenRange prelude, CSSParserTokenRange block)
diff --git a/Source/WebCore/css/parser/CSSPropertyParser.cpp b/Source/WebCore/css/parser/CSSPropertyParser.cpp
index 6757abb..bfb9b61 100644
--- a/Source/WebCore/css/parser/CSSPropertyParser.cpp
+++ b/Source/WebCore/css/parser/CSSPropertyParser.cpp
@@ -3806,19 +3806,9 @@
     if (range.peek().id() == CSSValueNone)
         return consumeIdent(range);
 
-    auto consumeName = [&]() -> RefPtr<CSSValue> {
-        if (range.peek().id() == CSSValueNone)
-            return nullptr;
-        if (auto ident = consumeCustomIdent(range))
-            return ident;
-        if (auto string = consumeString(range))
-            return string;
-        return nullptr;
-    };
-
     auto list = CSSValueList::createSpaceSeparated();
     do {
-        auto name = consumeName();
+        auto name = consumeSingleContainerName(range);
         if (!name)
             return nullptr;
         list->append(name.releaseNonNull());
diff --git a/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp b/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp
index e3f1a92..89809f1 100644
--- a/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp
+++ b/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp
@@ -4279,6 +4279,17 @@
     return isPredefinedCounterStyle(nameToken.id()) ? name.convertToASCIILowercaseAtom() : name.toAtomString();
 }
 
+RefPtr<CSSPrimitiveValue> consumeSingleContainerName(CSSParserTokenRange& range)
+{
+    if (range.peek().id() == CSSValueNone)
+        return nullptr;
+    if (auto ident = consumeCustomIdent(range))
+        return ident;
+    if (auto string = consumeString(range))
+        return string;
+    return nullptr;
+}
+
 std::optional<CSSValueID> consumeFontVariantCSS21Raw(CSSParserTokenRange& range)
 {
     return consumeIdentRaw<CSSValueNormal, CSSValueSmallCaps>(range);
diff --git a/Source/WebCore/css/parser/CSSPropertyParserHelpers.h b/Source/WebCore/css/parser/CSSPropertyParserHelpers.h
index d118292..2a7eac3 100644
--- a/Source/WebCore/css/parser/CSSPropertyParserHelpers.h
+++ b/Source/WebCore/css/parser/CSSPropertyParserHelpers.h
@@ -193,6 +193,7 @@
 bool isPredefinedCounterStyle(CSSValueID);
 RefPtr<CSSPrimitiveValue> consumeCounterStyleName(CSSParserTokenRange&);
 AtomString consumeCounterStyleNameInPrelude(CSSParserTokenRange&);
+RefPtr<CSSPrimitiveValue> consumeSingleContainerName(CSSParserTokenRange&);
 
 std::optional<CSSValueID> consumeFontVariantCSS21Raw(CSSParserTokenRange&);
 std::optional<CSSValueID> consumeFontWeightKeywordValueRaw(CSSParserTokenRange&);
diff --git a/Source/WebCore/css/parser/MediaQueryParser.cpp b/Source/WebCore/css/parser/MediaQueryParser.cpp
index 876c99b..cb5a08a 100644
--- a/Source/WebCore/css/parser/MediaQueryParser.cpp
+++ b/Source/WebCore/css/parser/MediaQueryParser.cpp
@@ -55,8 +55,18 @@
     return MediaQueryParser(MediaConditionParser, context).parseInternal(range);
 }
 
+RefPtr<MediaQuerySet> MediaQueryParser::parseContainerQuery(CSSParserTokenRange range, MediaQueryParserContext context)
+{
+    if (range.atEnd())
+        return nullptr;
+    if (range.peek().type() != LeftParenthesisToken && range.peek().type() != FunctionToken)
+        return nullptr;
+    return MediaQueryParser(ContainerQueryParser, context).parseInternal(range);
+}
+
 const MediaQueryParser::State MediaQueryParser::ReadRestrictor = &MediaQueryParser::readRestrictor;
 const MediaQueryParser::State MediaQueryParser::ReadMediaNot = &MediaQueryParser::readMediaNot;
+const MediaQueryParser::State MediaQueryParser::ReadContainerQuery = &MediaQueryParser::readContainerQuery;
 const MediaQueryParser::State MediaQueryParser::ReadMediaType = &MediaQueryParser::readMediaType;
 const MediaQueryParser::State MediaQueryParser::ReadAnd = &MediaQueryParser::readAnd;
 const MediaQueryParser::State MediaQueryParser::ReadFeatureStart = &MediaQueryParser::readFeatureStart;
@@ -74,10 +84,17 @@
     , m_querySet(MediaQuerySet::create())
     
 {
-    if (parserType == MediaQuerySetParser)
+    switch (m_parserType) {
+    case MediaQuerySetParser:
         m_state = &MediaQueryParser::readRestrictor;
-    else // MediaConditionParser
+        break;
+    case MediaConditionParser:
         m_state = &MediaQueryParser::readMediaNot;
+        break;
+    case ContainerQueryParser:
+        m_state = &MediaQueryParser::readContainerQuery;
+        break;
+    }
 }
 
 MediaQueryParser::~MediaQueryParser() = default;
@@ -102,6 +119,12 @@
         readFeatureStart(type, token, range);
 }
 
+void MediaQueryParser::readContainerQuery(CSSParserTokenType type, const CSSParserToken&, CSSParserTokenRange&)
+{
+    if (type == FunctionToken || type == LeftParenthesisToken)
+        m_state = ReadFeature;
+}
+
 static bool isRestrictorOrLogicalOperator(const CSSParserToken& token)
 {
     // FIXME: it would be more efficient to use lower-case always for tokenValue.
@@ -239,29 +262,45 @@
 
 void MediaQueryParser::handleBlocks(const CSSParserToken& token)
 {
-    if (token.getBlockType() == CSSParserToken::BlockStart
-        && (token.type() != LeftParenthesisToken || m_blockWatcher.blockLevel()))
-            m_state = SkipUntilBlockEnd;
+    if (token.getBlockType() != CSSParserToken::BlockStart)
+        return;
+    auto shouldSkipBlock = [&] {
+        // FIXME: Nested blocks should be supported.
+        if (m_blockWatcher.blockLevel())
+            return true;
+        if (token.type() == LeftParenthesisToken)
+            return false;
+        if (m_parserType == ContainerQueryParser && token.type() == FunctionToken)
+            return !equalLettersIgnoringASCIICase(token.value(), "size");
+        return true;
+    }();
+    if (shouldSkipBlock)
+        m_state = SkipUntilBlockEnd;
 }
 
 void MediaQueryParser::processToken(const CSSParserToken& token, CSSParserTokenRange& range)
 {
     CSSParserTokenType type = token.type();
 
-    if (m_state != ReadFeatureValue || type == WhitespaceToken) {
+    if (type == WhitespaceToken) {
+        range.consume();
+        return;
+    }
+
+    if (m_state != ReadFeatureValue) {
         handleBlocks(token);
         m_blockWatcher.handleToken(token);
         range.consume();
     }
 
     // Call the function that handles current state
-    if (type != WhitespaceToken)
-        ((this)->*(m_state))(type, token, range);
+    ((this)->*(m_state))(type, token, range);
 }
 
 // The state machine loop
-RefPtr<MediaQuerySet> MediaQueryParser::parseInternal(CSSParserTokenRange range)
+RefPtr<MediaQuerySet> MediaQueryParser::parseInternal(CSSParserTokenRange& range)
 {
+    
     while (!range.atEnd())
         processToken(range.peek(), range);
 
diff --git a/Source/WebCore/css/parser/MediaQueryParser.h b/Source/WebCore/css/parser/MediaQueryParser.h
index 6de823c..e6c15ec 100644
--- a/Source/WebCore/css/parser/MediaQueryParser.h
+++ b/Source/WebCore/css/parser/MediaQueryParser.h
@@ -48,22 +48,25 @@
     static RefPtr<MediaQuerySet> parseMediaQuerySet(const String&, MediaQueryParserContext);
     static RefPtr<MediaQuerySet> parseMediaQuerySet(CSSParserTokenRange, MediaQueryParserContext);
     static RefPtr<MediaQuerySet> parseMediaCondition(CSSParserTokenRange, MediaQueryParserContext);
+    static RefPtr<MediaQuerySet> parseContainerQuery(CSSParserTokenRange, MediaQueryParserContext);
 
 private:
     enum ParserType {
         MediaQuerySetParser,
         MediaConditionParser,
+        ContainerQueryParser,
     };
 
     MediaQueryParser(ParserType, MediaQueryParserContext);
     virtual ~MediaQueryParser();
 
-    RefPtr<MediaQuerySet> parseInternal(CSSParserTokenRange);
+    RefPtr<MediaQuerySet> parseInternal(CSSParserTokenRange&);
 
     void processToken(const CSSParserToken&, CSSParserTokenRange&);
 
     void readRestrictor(CSSParserTokenType, const CSSParserToken&, CSSParserTokenRange&);
     void readMediaNot(CSSParserTokenType, const CSSParserToken&, CSSParserTokenRange&);
+    void readContainerQuery(CSSParserTokenType, const CSSParserToken&, CSSParserTokenRange&);
     void readMediaType(CSSParserTokenType, const CSSParserToken&, CSSParserTokenRange&);
     void readAnd(CSSParserTokenType, const CSSParserToken&, CSSParserTokenRange&);
     void readFeatureStart(CSSParserTokenType, const CSSParserToken&, CSSParserTokenRange&);
@@ -124,6 +127,7 @@
 
     const static State ReadRestrictor;
     const static State ReadMediaNot;
+    const static State ReadContainerQuery;
     const static State ReadMediaType;
     const static State ReadAnd;
     const static State ReadFeatureStart;