Skip to content

Commit

Permalink
CSS nesting: parsing of style rule inside style rule.
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=249263
rdar://103146832

Reviewed by Antti Koivisto.

This implements experimental support to parse nested style rule inside style rule.
This is based on the css-nesting-1 editor draft as of today,
which describes the so-called "option 3" (non letter start) syntax.

https://github.com/w3c/csswg-drafts/blob/main/css-nesting-1/proposals.md

This is missing:
  - any support for having nested at-rule inside style rule.
  - CSSOM
  - selector matching

* LayoutTests/fast/css/style-enumerate-properties-expected.txt:
* LayoutTests/fast/css/style-enumerate-properties.html:
* LayoutTests/imported/w3c/web-platform-tests/css/css-nesting/cssom-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-nesting/invalidation-004-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-nesting/parsing-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-nesting/parsing.html:
* LayoutTests/imported/w3c/web-platform-tests/css/css-nesting/serialize-group-rules-with-decls.tentative-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-nesting/serialize-group-rules-with-decls.tentative.html:
* LayoutTests/imported/w3c/web-platform-tests/css/selectors/nesting-parsing-expected.txt: Removed.
* LayoutTests/imported/w3c/web-platform-tests/css/selectors/nesting-parsing.html: Removed.
* LayoutTests/imported/w3c/web-platform-tests/css/selectors/old-tests/css3-modsel-156.xml:
* LayoutTests/imported/w3c/web-platform-tests/css/selectors/old-tests/css3-modsel-156b.xml:
* LayoutTests/imported/w3c/web-platform-tests/css/selectors/old-tests/css3-modsel-156c.xml:
* LayoutTests/imported/w3c/web-platform-tests/dom/nodes/Element-matches-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/dom/nodes/Element-webkitMatchesSelector-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/dom/nodes/ParentNode-querySelector-All-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/dom/nodes/ParentNode-querySelector-All-xht-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/dom/nodes/selectors.js:
* Source/WTF/Scripts/Preferences/WebPreferencesExperimental.yaml:
* Source/WebCore/css/CSSComputedStyleDeclaration.cpp:
(WebCore::CSSComputedStyleDeclaration::cssRules const):
* Source/WebCore/css/CSSComputedStyleDeclaration.h:
* Source/WebCore/css/CSSSelector.cpp:
(WebCore::simpleSelectorSpecificity):
(WebCore::CSSSelector::selectorText const):
* Source/WebCore/css/CSSSelector.h:
* Source/WebCore/css/CSSStyleDeclaration.h:
* Source/WebCore/css/CSSStyleDeclaration.idl:
* Source/WebCore/css/CSSStyleRule.cpp:
(WebCore::CSSStyleRule::cssText const):
* Source/WebCore/css/PropertySetCSSStyleDeclaration.h:
* Source/WebCore/css/SelectorChecker.cpp:
(WebCore::SelectorChecker::checkOne const):
* Source/WebCore/css/StyleProperties.cpp:
(WebCore::MutableStyleProperties::createEmpty):
* Source/WebCore/css/StyleProperties.h:
* Source/WebCore/css/StyleRule.cpp:
(WebCore::StyleRule::averageSizeInBytes):
(WebCore::StyleRule::StyleRule):
(WebCore::StyleRule::create):
(WebCore::StyleRule::createForSplitting):
* Source/WebCore/css/StyleRule.h:
* Source/WebCore/css/parser/CSSParser.cpp:
(WebCore::CSSParser::parseSelector):
* Source/WebCore/css/parser/CSSParserContext.cpp:
(WebCore::operator==):
(WebCore::add):
* Source/WebCore/css/parser/CSSParserContext.h:
* Source/WebCore/css/parser/CSSParserImpl.cpp:
(WebCore::CSSParserImpl::parseRule):
(WebCore::CSSParserImpl::consumeRuleList):
(WebCore::CSSParserImpl::consumeQualifiedRule):
(WebCore::CSSParserImpl::consumeStyleRule):
(WebCore::CSSParserImpl::consumeDeclarationListOrStyleBlockHelper):
(WebCore::CSSParserImpl::consumeDeclarationList):
(WebCore::CSSParserImpl::consumeStyleBlock):
* Source/WebCore/css/parser/CSSParserImpl.h:
(WebCore::CSSParserImpl::consumeQualifiedRule):
(WebCore::CSSParserImpl::consumeStyleRule):
(WebCore::CSSParserImpl::consumeDeclarationListOrStyleBlockHelper):
(WebCore::CSSParserImpl::consumeStyleBlock):
* Source/WebCore/css/parser/CSSSelectorParser.cpp:
(WebCore::parseCSSSelector):
(WebCore::CSSSelectorParser::CSSSelectorParser):
(WebCore::CSSSelectorParser::consumeSimpleSelector):
(WebCore::CSSSelectorParser::consumeNesting):
* Source/WebCore/css/parser/CSSSelectorParser.h:
* Source/WebCore/cssjit/SelectorCompiler.cpp:
(WebCore::SelectorCompiler::addPseudoClassType):

Canonical link: https://commits.webkit.org/258079@main
  • Loading branch information
mdubet committed Dec 19, 2022
1 parent bb7f2dd commit 2dc309f
Show file tree
Hide file tree
Showing 40 changed files with 310 additions and 153 deletions.
Expand Up @@ -16,7 +16,7 @@ PASS initialIndexOfGroup3 > initialIndexOfGroup2 is true
PASS initialIndexOfGroup4 > initialIndexOfGroup3 is true
PASS initialIndexOfGroup5 > initialIndexOfGroup4 is true
PASS initialIndexOfGroup6 > initialIndexOfGroup5 is true
PASS group1 is ["0", "1", "cssText", "length", "parentRule", "cssFloat"]
PASS group1 is ["0", "1", "cssText", "cssRules", "length", "parentRule", "cssFloat"]
PASS group2 is group2Clone
PASS group2.some(property => property.startsWith("Webkit")) is true
PASS group2.some(property => property.startsWith("Epub")) is true
Expand Down
2 changes: 1 addition & 1 deletion LayoutTests/fast/css/style-enumerate-properties.html
Expand Up @@ -60,7 +60,7 @@
let group6 = properties.slice(initialIndexOfGroup6);

// Group 1
shouldBe(`group1`, `["0", "1", "cssText", "length", "parentRule", "cssFloat"]`);
shouldBe(`group1`, `["0", "1", "cssText", "cssRules", "length", "parentRule", "cssFloat"]`);

// Group 2 - Camel Case
let group2Clone = [...group2];
Expand Down
Expand Up @@ -7,5 +7,5 @@ FAIL Simple CSSOM manipulation of subrules 4 undefined is not an object (evaluat
FAIL Simple CSSOM manipulation of subrules 5 assert_throws_dom: function "() => { ss.cssRules[0].insertRule('% {}'); }" threw object "TypeError: ss.cssRules[0].insertRule is not a function. (In 'ss.cssRules[0].insertRule('% {}')', 'ss.cssRules[0].insertRule' is undefined)" that is not a DOMException SyntaxError: property "code" is equal to undefined, expected 12
FAIL Simple CSSOM manipulation of subrules 6 undefined is not an object (evaluating 'ss.cssRules[0].cssRules[1]')
FAIL Simple CSSOM manipulation of subrules 7 ss.cssRules[0].insertRule is not a function. (In 'ss.cssRules[0].insertRule('@supports selector(&) { & div { font-size: 10px; }}', 1)', 'ss.cssRules[0].insertRule' is undefined)
FAIL Simple CSSOM manipulation of subrules 8 assert_equals: color is changed, new rule is ignored expected ".a {\n color: olivedrab;\n & .b { color: green; }\n & .c { color: blue; }\n}" but got ".a { color: olivedrab; }"
FAIL Simple CSSOM manipulation of subrules 8 assert_equals: color is changed, new rule is ignored expected ".a {\n color: olivedrab;\n & .b { color: green; }\n & .c { color: blue; }\n}" but got ".a {\n color: olivedrab;& .b { color: green; }\n}& .c { color: blue; }\n}"

@@ -1,4 +1,4 @@
Test passes if color is green.

FAIL CSS Selectors nested invalidation through @media by selectorText undefined is not an object (evaluating 'document.styleSheets[0].rules[1].selectorText = '.a'')
FAIL CSS Selectors nested invalidation through @media by selectorText assert_equals: expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"

@@ -1,45 +1,65 @@

FAIL .foo {
PASS .foo {
& { color: green; }
} assert_equals: expected ".foo {\n & { color: green; }\n}" but got ".foo { }"
FAIL .foo {
}
PASS .foo { //color: red; color: green; }
PASS .foo {
&.bar { color: green; }
} assert_equals: expected ".foo {\n &.bar { color: green; }\n}" but got ".foo { }"
FAIL .foo {
}
PASS .foo {
& .bar { color: green; }
} assert_equals: expected ".foo {\n & .bar { color: green; }\n}" but got ".foo { }"
FAIL .foo {
}
PASS .foo {
& > .bar { color: green; }
} assert_equals: expected ".foo {\n & > .bar { color: green; }\n}" but got ".foo { }"
FAIL .foo {
}
PASS .foo {
&:is(.bar, &.baz) { color: green; }
} assert_equals: expected ".foo {\n &:is(.bar, &.baz) { color: green; }\n}" but got ".foo { }"
FAIL .foo {
}
PASS .foo {
.bar& { color: green; }
} assert_equals: expected ".foo {\n .bar& { color: green; }\n}" but got ".foo { }"
FAIL .foo {
}
PASS .foo {
.bar & { color: green; }
} assert_equals: expected ".foo {\n .bar & { color: green; }\n}" but got ".foo { }"
FAIL .foo {
}
PASS .foo {
.bar > & { color: green; }
} assert_equals: expected ".foo {\n .bar > & { color: green; }\n}" but got ".foo { }"
FAIL .foo, .bar {
}
PASS .foo, .bar {
& + .baz, &.qux { color: green; }
} assert_equals: expected ".foo, .bar {\n & + .baz, &.qux { color: green; }\n}" but got ".foo, .bar { }"
FAIL .foo {
}
PASS .foo {
& .bar & .baz & .qux { color: green; }
} assert_equals: expected ".foo {\n & .bar & .baz & .qux { color: green; }\n}" but got ".foo { }"
}
FAIL .foo {
@media (min-width: 50px) {
& { color: green; }
}
} assert_equals: expected ".foo {\n @media (min-width: 50px) {\n & { color: green; }\n}\n}" but got ".foo { }"
} assert_equals: expected ".foo {\n @media (min-width: 50px) { color: green; }\n}" but got ".foo { }"
FAIL .foo {
@media (min-width: 50px) { color: green; }
} assert_equals: expected ".foo {\n @media (min-width: 50px) { color: green; }\n}" but got ".foo { }"
FAIL main {
PASS main {
& > section, & > article {
& > header { color: green; }
}
} assert_equals: expected "main {\n & > section, & > article {\n & > header { color: green; }\n}\n}" but got "main { }"
}
PASS .foo {
:has(div) { color: green; }
}
PASS .foo {
.bar {
&& { color: green; }
}
}
PASS .foo {
color: red; ident { color: green; }
}
PASS .foo {
//color: comment; & { color: green; }
}
PASS .foo {
.bar {
functionalnotation(div) { color: green; }

}

Expand Up @@ -16,6 +16,8 @@

const testRules = [
`.foo {\n & { color: green; }\n}`, // 🐰
// nesting should not mess with single line comment legacy special case
[`.foo { //color: red; color: green; }`,`.foo { color: green; }`],
`.foo {\n &.bar { color: green; }\n}`,
`.foo {\n & .bar { color: green; }\n}`,
`.foo {\n & > .bar { color: green; }\n}`,
Expand All @@ -25,19 +27,36 @@
`.foo {\n .bar > & { color: green; }\n}`,
`.foo, .bar {\n & + .baz, &.qux { color: green; }\n}`,
`.foo {\n & .bar & .baz & .qux { color: green; }\n}`,
`.foo {\n @media (min-width: 50px) {\n & { color: green; }\n}\n}`,
// implicit nested rule
[`.foo {\n @media (min-width: 50px) {\n & { color: green; }\n}\n}`,`.foo {\n @media (min-width: 50px) { color: green; }\n}`],
`.foo {\n @media (min-width: 50px) { color: green; }\n}`,
`main {\n & > section, & > article {\n & > header { color: green; }\n}\n}`,
`.foo {\n :has(div) { color: green; }\n}`,
`.foo {\n .bar {\n && { color: green; }\n}\n}`,
[`.foo {\n color: red; ident { color: green; }\n}`, `.foo { color: red; }`],
[`.foo {\n //color: comment; & { color: green; }\n}`, `.foo {\n & { color: green; }\n}`],
[`.foo {\n .bar {\n functionalnotation(div) { color: green; }\n\n}`, `.foo {\n .bar { }\n}`],
]

testRules.forEach(testRule => {
var inputRule = null
var expectedRule = null

if (Array.isArray(testRule)) {
inputRule = testRule[0]
expectedRule = testRule[1]
} else {
inputRule = expectedRule = testRule
}

test(function() {
//console.log(testRule)
beforeEach()
ss.insertRule(testRule)
ss.insertRule(inputRule)
// todo?
// when parsing is being ready/prototyped,
// switch to crawling nested rules instead of comparing text
assert_equals(ss.rules[0].cssText, testRule)
}, testRule)
assert_equals(ss.rules[0].cssText, expectedRule)
}, inputRule)
})
</script>
Expand Up @@ -2,6 +2,6 @@
FAIL Serialization of declarations in group rules assert_equals: expected "div {\n @media screen { color: red; background-color: green; }\n}" but got "div { }"
FAIL Serialization of declarations in group rules 1 assert_equals: expected "div {\n @supports selector(&) {\n color: red; background-color: green;\n &:hover { color: navy; }\n}\n}" but got "div { }"
FAIL Serialization of declarations in group rules 2 assert_equals: expected "div {\n @media screen { color: red; }\n}" but got "div { }"
FAIL Serialization of declarations in group rules 3 assert_equals: expected "div {\n & { color: red; }\n}" but got "div { }"
FAIL Serialization of declarations in group rules 3 assert_equals: expected "div {\n & { color: red; }\n}" but got "div {\n & { color: red; }\n}"
FAIL Serialization of declarations in group rules 4 assert_equals: expected "div {\n @media screen {\n}\n}" but got "div { }"

Expand Up @@ -55,7 +55,7 @@

// They are not removed from regular rules.
test(() => {
assert_becomes("div { & { color: red; } }", "div {\n & { color: red; }\n}");
assert_becomes("div { & { color: red; } }", "div {\n & { color: red; }\n}");
});

// Empty rules (confusingly?) serialize different between style rules
Expand Down

This file was deleted.

This file was deleted.

Expand Up @@ -4,7 +4,7 @@
<title>Syntax and parsing</title>
<style type="text/css"><![CDATA[
p { background: lime; }
foo & address, p { background: red; }
foo % address, p { background: red; }
]]></style>
<link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
<link rel="help" href="https://www.w3.org/TR/css3-selectors/#selectors"/> <!-- bogus link to make sure it gets found -->
Expand Down
Expand Up @@ -3,7 +3,7 @@
<head>
<title>Syntax and parsing</title>
<style type="text/css"><![CDATA[
foo & address, p { background: red; }
foo % address, p { background: red; }
p { background: lime; }
]]></style>
<link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
Expand Down
Expand Up @@ -3,7 +3,7 @@
<head>
<title>Syntax and parsing</title>
<style type="text/css"><![CDATA[
foo & address, p { background: red ! important; }
foo % address, p { background: red ! important; }
p { background: lime; }
]]></style>
<link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
Expand Down
Expand Up @@ -30,7 +30,7 @@ PASS Detached Element.matches: Invalid class: .5cm
PASS Detached Element.matches: Invalid class: ..test
PASS Detached Element.matches: Invalid class: .foo..quux
PASS Detached Element.matches: Invalid class: .bar.
PASS Detached Element.matches: Invalid combinator: div & address, p
PASS Detached Element.matches: Invalid combinator: div % address, p
PASS Detached Element.matches: Invalid combinator: div ++ address, p
PASS Detached Element.matches: Invalid combinator: div ~~ address, p
PASS Detached Element.matches: Invalid [att=value] selector: [*=test]
Expand Down Expand Up @@ -64,7 +64,7 @@ PASS In-document Element.matches: Invalid class: .5cm
PASS In-document Element.matches: Invalid class: ..test
PASS In-document Element.matches: Invalid class: .foo..quux
PASS In-document Element.matches: Invalid class: .bar.
PASS In-document Element.matches: Invalid combinator: div & address, p
PASS In-document Element.matches: Invalid combinator: div % address, p
PASS In-document Element.matches: Invalid combinator: div ++ address, p
PASS In-document Element.matches: Invalid combinator: div ~~ address, p
PASS In-document Element.matches: Invalid [att=value] selector: [*=test]
Expand Down
Expand Up @@ -30,7 +30,7 @@ PASS Detached Element.webkitMatchesSelector: Invalid class: .5cm
PASS Detached Element.webkitMatchesSelector: Invalid class: ..test
PASS Detached Element.webkitMatchesSelector: Invalid class: .foo..quux
PASS Detached Element.webkitMatchesSelector: Invalid class: .bar.
PASS Detached Element.webkitMatchesSelector: Invalid combinator: div & address, p
PASS Detached Element.webkitMatchesSelector: Invalid combinator: div % address, p
PASS Detached Element.webkitMatchesSelector: Invalid combinator: div ++ address, p
PASS Detached Element.webkitMatchesSelector: Invalid combinator: div ~~ address, p
PASS Detached Element.webkitMatchesSelector: Invalid [att=value] selector: [*=test]
Expand Down Expand Up @@ -64,7 +64,7 @@ PASS In-document Element.webkitMatchesSelector: Invalid class: .5cm
PASS In-document Element.webkitMatchesSelector: Invalid class: ..test
PASS In-document Element.webkitMatchesSelector: Invalid class: .foo..quux
PASS In-document Element.webkitMatchesSelector: Invalid class: .bar.
PASS In-document Element.webkitMatchesSelector: Invalid combinator: div & address, p
PASS In-document Element.webkitMatchesSelector: Invalid combinator: div % address, p
PASS In-document Element.webkitMatchesSelector: Invalid combinator: div ++ address, p
PASS In-document Element.webkitMatchesSelector: Invalid combinator: div ~~ address, p
PASS In-document Element.webkitMatchesSelector: Invalid [att=value] selector: [*=test]
Expand Down
Expand Up @@ -81,8 +81,8 @@ PASS Document.querySelector: Invalid class: .foo..quux
PASS Document.querySelectorAll: Invalid class: .foo..quux
PASS Document.querySelector: Invalid class: .bar.
PASS Document.querySelectorAll: Invalid class: .bar.
PASS Document.querySelector: Invalid combinator: div & address, p
PASS Document.querySelectorAll: Invalid combinator: div & address, p
PASS Document.querySelector: Invalid combinator: div % address, p
PASS Document.querySelectorAll: Invalid combinator: div % address, p
PASS Document.querySelector: Invalid combinator: div ++ address, p
PASS Document.querySelectorAll: Invalid combinator: div ++ address, p
PASS Document.querySelector: Invalid combinator: div ~~ address, p
Expand Down Expand Up @@ -149,8 +149,8 @@ PASS Detached Element.querySelector: Invalid class: .foo..quux
PASS Detached Element.querySelectorAll: Invalid class: .foo..quux
PASS Detached Element.querySelector: Invalid class: .bar.
PASS Detached Element.querySelectorAll: Invalid class: .bar.
PASS Detached Element.querySelector: Invalid combinator: div & address, p
PASS Detached Element.querySelectorAll: Invalid combinator: div & address, p
PASS Detached Element.querySelector: Invalid combinator: div % address, p
PASS Detached Element.querySelectorAll: Invalid combinator: div % address, p
PASS Detached Element.querySelector: Invalid combinator: div ++ address, p
PASS Detached Element.querySelectorAll: Invalid combinator: div ++ address, p
PASS Detached Element.querySelector: Invalid combinator: div ~~ address, p
Expand Down Expand Up @@ -217,8 +217,8 @@ PASS Fragment.querySelector: Invalid class: .foo..quux
PASS Fragment.querySelectorAll: Invalid class: .foo..quux
PASS Fragment.querySelector: Invalid class: .bar.
PASS Fragment.querySelectorAll: Invalid class: .bar.
PASS Fragment.querySelector: Invalid combinator: div & address, p
PASS Fragment.querySelectorAll: Invalid combinator: div & address, p
PASS Fragment.querySelector: Invalid combinator: div % address, p
PASS Fragment.querySelectorAll: Invalid combinator: div % address, p
PASS Fragment.querySelector: Invalid combinator: div ++ address, p
PASS Fragment.querySelectorAll: Invalid combinator: div ++ address, p
PASS Fragment.querySelector: Invalid combinator: div ~~ address, p
Expand Down Expand Up @@ -285,8 +285,8 @@ PASS In-document Element.querySelector: Invalid class: .foo..quux
PASS In-document Element.querySelectorAll: Invalid class: .foo..quux
PASS In-document Element.querySelector: Invalid class: .bar.
PASS In-document Element.querySelectorAll: Invalid class: .bar.
PASS In-document Element.querySelector: Invalid combinator: div & address, p
PASS In-document Element.querySelectorAll: Invalid combinator: div & address, p
PASS In-document Element.querySelector: Invalid combinator: div % address, p
PASS In-document Element.querySelectorAll: Invalid combinator: div % address, p
PASS In-document Element.querySelector: Invalid combinator: div ++ address, p
PASS In-document Element.querySelectorAll: Invalid combinator: div ++ address, p
PASS In-document Element.querySelector: Invalid combinator: div ~~ address, p
Expand Down Expand Up @@ -353,8 +353,8 @@ PASS Empty Element.querySelector: Invalid class: .foo..quux
PASS Empty Element.querySelectorAll: Invalid class: .foo..quux
PASS Empty Element.querySelector: Invalid class: .bar.
PASS Empty Element.querySelectorAll: Invalid class: .bar.
PASS Empty Element.querySelector: Invalid combinator: div & address, p
PASS Empty Element.querySelectorAll: Invalid combinator: div & address, p
PASS Empty Element.querySelector: Invalid combinator: div % address, p
PASS Empty Element.querySelectorAll: Invalid combinator: div % address, p
PASS Empty Element.querySelector: Invalid combinator: div ++ address, p
PASS Empty Element.querySelectorAll: Invalid combinator: div ++ address, p
PASS Empty Element.querySelector: Invalid combinator: div ~~ address, p
Expand Down

0 comments on commit 2dc309f

Please sign in to comment.