Skip to content

Commit

Permalink
[view-transitions] Implement parsing & checking for pseudo-elements
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=265167
rdar://118666802

Reviewed by Cameron McCormack.

This implements parsing and adapts CSS highlight resolution code for view-transition pseudos.

The following pseudo-elements are covered:
- ::view-transition
- ::view-transition-group()
- ::view-transition-image-pair()
- ::view-transition-old()
- ::view-transition-new()

https://drafts.csswg.org/css-view-transitions/#pseudo

* LayoutTests/imported/w3c/web-platform-tests/css/css-cascade/all-prop-initial-xml.html:
* LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/parsing/pseudo-elements-invalid-expected.txt: Added.
* LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/parsing/pseudo-elements-invalid.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/parsing/pseudo-elements-valid-expected.txt: Added.
* LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/parsing/pseudo-elements-valid.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/parsing/view-transition-name-computed-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/parsing/view-transition-name-computed.html:
* LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/parsing/view-transition-name-invalid-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/parsing/view-transition-name-invalid.html:
* Source/WebCore/animation/WebAnimationUtilities.cpp:
(WebCore::pseudoIdFromString):
* Source/WebCore/css/CSSComputedStyleDeclaration.cpp:
(WebCore::CSSComputedStyleDeclaration::CSSComputedStyleDeclaration):
* Source/WebCore/css/CSSSelector.cpp:
(WebCore::CSSSelector::pseudoId):
(WebCore::CSSSelector::parsePseudoElementType):
(WebCore::CSSSelector::selectorText const):
* Source/WebCore/css/CSSSelector.h:
* Source/WebCore/css/SelectorChecker.cpp:
(WebCore::SelectorChecker::checkOne const):
* Source/WebCore/css/SelectorChecker.h:
* Source/WebCore/css/SelectorPseudoElementTypeMap.in:
* Source/WebCore/css/parser/CSSParserContext.cpp:
(WebCore::CSSParserContext::CSSParserContext):
* Source/WebCore/css/parser/CSSParserSelector.cpp:
(WebCore::CSSParserSelector::parsePseudoElementSelector):
* Source/WebCore/css/parser/CSSParserSelector.h:
* Source/WebCore/css/parser/CSSSelectorParser.cpp:
(WebCore::isOnlyPseudoElementFunction):
(WebCore::CSSSelectorParser::consumePseudo):
* Source/WebCore/css/parser/CSSSelectorParserContext.cpp:
(WebCore::CSSSelectorParserContext::CSSSelectorParserContext):
(WebCore::add):
* Source/WebCore/css/parser/CSSSelectorParserContext.h:
* Source/WebCore/cssjit/SelectorCompiler.cpp:
(WebCore::SelectorCompiler::constructFragmentsInternal):
* Source/WebCore/page/LocalDOMWindow.cpp:
(WebCore::LocalDOMWindow::getMatchedCSSRules const):
* Source/WebCore/rendering/style/RenderStyle.h:
* Source/WebCore/rendering/style/RenderStyleConstants.cpp:
(WebCore::operator<<):
* Source/WebCore/rendering/style/RenderStyleConstants.h:
* Source/WebCore/style/ElementRuleCollector.cpp:
(WebCore::Style::ElementRuleCollector::ruleMatches):
* Source/WebCore/style/ElementRuleCollector.h:
(WebCore::Style::PseudoElementRequest::PseudoElementRequest):

Canonical link: https://commits.webkit.org/271040@main
  • Loading branch information
nt1m committed Nov 22, 2023
1 parent ac0de29 commit 331b2c1
Show file tree
Hide file tree
Showing 29 changed files with 728 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,26 @@
// we need the empty stylesheet to avoid default XSLT views of the XML
const style = iframe.contentDocument.createElementNS("http://www.w3.org/1999/xhtml", "style");
root.appendChild(style);
const cs = iframe.contentWindow.getComputedStyle(root);

// Grab initial styles from a random element, as the root can get non-initial UA styling.
const div = iframe.contentDocument.createElementNS("http://www.w3.org/1999/xhtml", "div");
root.appendChild(div);
const cs = iframe.contentWindow.getComputedStyle(div);

let actual_initial = Object.create(null);
for (let i = 0; i < cs.length; i++) {
let prop_name = cs[i];
actual_initial[prop_name] = cs[prop_name];
}
const rootCS = iframe.contentWindow.getComputedStyle(root);
test(() => {
style.textContent = ":root { color: blue }";
assert_equals(cs["color"], "rgb(0, 0, 255)");
assert_equals(rootCS["color"], "rgb(0, 0, 255)");
}, "stylesheet takes effect");
style.textContent = ":root { all: initial; direction: initial; unicode-bidi: initial; } style { display: none; }";
for (let prop_name in actual_initial) {
test(() => {
assert_equals(cs[prop_name], actual_initial[prop_name]);
assert_equals(rootCS[prop_name], actual_initial[prop_name]);
}, prop_name);
}
});
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!doctype html>
<meta charset="utf-8">
<title>Test pseudo elements parsing of invalid selectors</title>
<link rel="author" href="https://github.com/nt1m">
<link rel="help" href="https://drafts.csswg.org/css-view-transitions-1/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/css/support/parsing-testcommon.js"></script>
<script>
const functionPseudoElements = [
"::view-transition-group",
"::view-transition-image-pair",
"::view-transition-old",
"::view-transition-new",
];

function test_invalid_selector_combinations(pseudo) {
test_invalid_selector(`${pseudo}.a`);
test_invalid_selector(`${pseudo} div`);
test_invalid_selector(`${pseudo}:hover`);
test_invalid_selector(`:not(${pseudo})`);
test_invalid_selector(`:has(${pseudo})`);
}

test_invalid_selector_combinations("::view-transition");
test_invalid_selector("::view-transition(*)");
test_invalid_selector("::view-transition(valid)");
test_invalid_selector("::view-transition(root)");

for (const fn of functionPseudoElements) {
for (const validArgument of ["*", "valid", "root"]) {
test_invalid_selector_combinations(`${fn}(${validArgument})`);
}

// Test function without argument.
test_invalid_selector(fn);
test_invalid_selector_combinations(fn);

// Test function with empty argument.
test_invalid_selector(`${fn}()`);
test_invalid_selector_combinations(`${fn}()`);

// Test function with keywords excluded from <custom-ident>.
test_invalid_selector(`${fn}(default)`);
test_invalid_selector_combinations(`${fn}(default)`);
test_invalid_selector(`${fn}(unset)`);
test_invalid_selector_combinations(`${fn}(unset)`);
test_invalid_selector(`${fn}(initial)`);
test_invalid_selector_combinations(`${fn}(initial)`);
test_invalid_selector(`${fn}(inherit)`);
test_invalid_selector_combinations(`${fn}(inherit)`);
test_invalid_selector(`${fn}(revert)`);
test_invalid_selector_combinations(`${fn}(revert)`);
test_invalid_selector(`${fn}(revert-layer)`);
test_invalid_selector_combinations(`${fn}(revert-layer)`);

// Test function with multiple arguments.
test_invalid_selector(`${fn}(foo, bar)`);
test_invalid_selector_combinations(`${fn}(foo, bar)`);
test_invalid_selector(`${fn}(foo bar)`);
test_invalid_selector_combinations(`${fn}(foo bar)`);

// Test function with selector arguments.
test_invalid_selector(`${fn}(.foo)`);
test_invalid_selector_combinations(`${fn}(.foo)`);
test_invalid_selector(`${fn}(#bar)`);
test_invalid_selector_combinations(`${fn}(#bar)`);
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

PASS "::view-transition" should be a valid selector
PASS ":root::view-transition" should be a valid selector
PASS ".a::view-transition" should be a valid selector
PASS "div ::view-transition" should be a valid selector
PASS "::view-transition-group(*)" should be a valid selector
PASS ":root::view-transition-group(*)" should be a valid selector
PASS ".a::view-transition-group(*)" should be a valid selector
PASS "div ::view-transition-group(*)" should be a valid selector
FAIL "::view-transition-group(*):only-child" should be a valid selector '::view-transition-group(*):only-child' is not a valid selector.
FAIL ":root::view-transition-group(*):only-child" should be a valid selector ':root::view-transition-group(*):only-child' is not a valid selector.
FAIL ".a::view-transition-group(*):only-child" should be a valid selector '.a::view-transition-group(*):only-child' is not a valid selector.
FAIL "div ::view-transition-group(*):only-child" should be a valid selector 'div ::view-transition-group(*):only-child' is not a valid selector.
PASS "::view-transition-group(root)" should be a valid selector
PASS ":root::view-transition-group(root)" should be a valid selector
PASS ".a::view-transition-group(root)" should be a valid selector
PASS "div ::view-transition-group(root)" should be a valid selector
FAIL "::view-transition-group(root):only-child" should be a valid selector '::view-transition-group(root):only-child' is not a valid selector.
FAIL ":root::view-transition-group(root):only-child" should be a valid selector ':root::view-transition-group(root):only-child' is not a valid selector.
FAIL ".a::view-transition-group(root):only-child" should be a valid selector '.a::view-transition-group(root):only-child' is not a valid selector.
FAIL "div ::view-transition-group(root):only-child" should be a valid selector 'div ::view-transition-group(root):only-child' is not a valid selector.
PASS "::view-transition-group(dashed-ident)" should be a valid selector
PASS ":root::view-transition-group(dashed-ident)" should be a valid selector
PASS ".a::view-transition-group(dashed-ident)" should be a valid selector
PASS "div ::view-transition-group(dashed-ident)" should be a valid selector
FAIL "::view-transition-group(dashed-ident):only-child" should be a valid selector '::view-transition-group(dashed-ident):only-child' is not a valid selector.
FAIL ":root::view-transition-group(dashed-ident):only-child" should be a valid selector ':root::view-transition-group(dashed-ident):only-child' is not a valid selector.
FAIL ".a::view-transition-group(dashed-ident):only-child" should be a valid selector '.a::view-transition-group(dashed-ident):only-child' is not a valid selector.
FAIL "div ::view-transition-group(dashed-ident):only-child" should be a valid selector 'div ::view-transition-group(dashed-ident):only-child' is not a valid selector.
PASS "::view-transition-image-pair(*)" should be a valid selector
PASS ":root::view-transition-image-pair(*)" should be a valid selector
PASS ".a::view-transition-image-pair(*)" should be a valid selector
PASS "div ::view-transition-image-pair(*)" should be a valid selector
FAIL "::view-transition-image-pair(*):only-child" should be a valid selector '::view-transition-image-pair(*):only-child' is not a valid selector.
FAIL ":root::view-transition-image-pair(*):only-child" should be a valid selector ':root::view-transition-image-pair(*):only-child' is not a valid selector.
FAIL ".a::view-transition-image-pair(*):only-child" should be a valid selector '.a::view-transition-image-pair(*):only-child' is not a valid selector.
FAIL "div ::view-transition-image-pair(*):only-child" should be a valid selector 'div ::view-transition-image-pair(*):only-child' is not a valid selector.
PASS "::view-transition-image-pair(root)" should be a valid selector
PASS ":root::view-transition-image-pair(root)" should be a valid selector
PASS ".a::view-transition-image-pair(root)" should be a valid selector
PASS "div ::view-transition-image-pair(root)" should be a valid selector
FAIL "::view-transition-image-pair(root):only-child" should be a valid selector '::view-transition-image-pair(root):only-child' is not a valid selector.
FAIL ":root::view-transition-image-pair(root):only-child" should be a valid selector ':root::view-transition-image-pair(root):only-child' is not a valid selector.
FAIL ".a::view-transition-image-pair(root):only-child" should be a valid selector '.a::view-transition-image-pair(root):only-child' is not a valid selector.
FAIL "div ::view-transition-image-pair(root):only-child" should be a valid selector 'div ::view-transition-image-pair(root):only-child' is not a valid selector.
PASS "::view-transition-image-pair(dashed-ident)" should be a valid selector
PASS ":root::view-transition-image-pair(dashed-ident)" should be a valid selector
PASS ".a::view-transition-image-pair(dashed-ident)" should be a valid selector
PASS "div ::view-transition-image-pair(dashed-ident)" should be a valid selector
FAIL "::view-transition-image-pair(dashed-ident):only-child" should be a valid selector '::view-transition-image-pair(dashed-ident):only-child' is not a valid selector.
FAIL ":root::view-transition-image-pair(dashed-ident):only-child" should be a valid selector ':root::view-transition-image-pair(dashed-ident):only-child' is not a valid selector.
FAIL ".a::view-transition-image-pair(dashed-ident):only-child" should be a valid selector '.a::view-transition-image-pair(dashed-ident):only-child' is not a valid selector.
FAIL "div ::view-transition-image-pair(dashed-ident):only-child" should be a valid selector 'div ::view-transition-image-pair(dashed-ident):only-child' is not a valid selector.
PASS "::view-transition-old(*)" should be a valid selector
PASS ":root::view-transition-old(*)" should be a valid selector
PASS ".a::view-transition-old(*)" should be a valid selector
PASS "div ::view-transition-old(*)" should be a valid selector
FAIL "::view-transition-old(*):only-child" should be a valid selector '::view-transition-old(*):only-child' is not a valid selector.
FAIL ":root::view-transition-old(*):only-child" should be a valid selector ':root::view-transition-old(*):only-child' is not a valid selector.
FAIL ".a::view-transition-old(*):only-child" should be a valid selector '.a::view-transition-old(*):only-child' is not a valid selector.
FAIL "div ::view-transition-old(*):only-child" should be a valid selector 'div ::view-transition-old(*):only-child' is not a valid selector.
PASS "::view-transition-old(root)" should be a valid selector
PASS ":root::view-transition-old(root)" should be a valid selector
PASS ".a::view-transition-old(root)" should be a valid selector
PASS "div ::view-transition-old(root)" should be a valid selector
FAIL "::view-transition-old(root):only-child" should be a valid selector '::view-transition-old(root):only-child' is not a valid selector.
FAIL ":root::view-transition-old(root):only-child" should be a valid selector ':root::view-transition-old(root):only-child' is not a valid selector.
FAIL ".a::view-transition-old(root):only-child" should be a valid selector '.a::view-transition-old(root):only-child' is not a valid selector.
FAIL "div ::view-transition-old(root):only-child" should be a valid selector 'div ::view-transition-old(root):only-child' is not a valid selector.
PASS "::view-transition-old(dashed-ident)" should be a valid selector
PASS ":root::view-transition-old(dashed-ident)" should be a valid selector
PASS ".a::view-transition-old(dashed-ident)" should be a valid selector
PASS "div ::view-transition-old(dashed-ident)" should be a valid selector
FAIL "::view-transition-old(dashed-ident):only-child" should be a valid selector '::view-transition-old(dashed-ident):only-child' is not a valid selector.
FAIL ":root::view-transition-old(dashed-ident):only-child" should be a valid selector ':root::view-transition-old(dashed-ident):only-child' is not a valid selector.
FAIL ".a::view-transition-old(dashed-ident):only-child" should be a valid selector '.a::view-transition-old(dashed-ident):only-child' is not a valid selector.
FAIL "div ::view-transition-old(dashed-ident):only-child" should be a valid selector 'div ::view-transition-old(dashed-ident):only-child' is not a valid selector.
PASS "::view-transition-new(*)" should be a valid selector
PASS ":root::view-transition-new(*)" should be a valid selector
PASS ".a::view-transition-new(*)" should be a valid selector
PASS "div ::view-transition-new(*)" should be a valid selector
FAIL "::view-transition-new(*):only-child" should be a valid selector '::view-transition-new(*):only-child' is not a valid selector.
FAIL ":root::view-transition-new(*):only-child" should be a valid selector ':root::view-transition-new(*):only-child' is not a valid selector.
FAIL ".a::view-transition-new(*):only-child" should be a valid selector '.a::view-transition-new(*):only-child' is not a valid selector.
FAIL "div ::view-transition-new(*):only-child" should be a valid selector 'div ::view-transition-new(*):only-child' is not a valid selector.
PASS "::view-transition-new(root)" should be a valid selector
PASS ":root::view-transition-new(root)" should be a valid selector
PASS ".a::view-transition-new(root)" should be a valid selector
PASS "div ::view-transition-new(root)" should be a valid selector
FAIL "::view-transition-new(root):only-child" should be a valid selector '::view-transition-new(root):only-child' is not a valid selector.
FAIL ":root::view-transition-new(root):only-child" should be a valid selector ':root::view-transition-new(root):only-child' is not a valid selector.
FAIL ".a::view-transition-new(root):only-child" should be a valid selector '.a::view-transition-new(root):only-child' is not a valid selector.
FAIL "div ::view-transition-new(root):only-child" should be a valid selector 'div ::view-transition-new(root):only-child' is not a valid selector.
PASS "::view-transition-new(dashed-ident)" should be a valid selector
PASS ":root::view-transition-new(dashed-ident)" should be a valid selector
PASS ".a::view-transition-new(dashed-ident)" should be a valid selector
PASS "div ::view-transition-new(dashed-ident)" should be a valid selector
FAIL "::view-transition-new(dashed-ident):only-child" should be a valid selector '::view-transition-new(dashed-ident):only-child' is not a valid selector.
FAIL ":root::view-transition-new(dashed-ident):only-child" should be a valid selector ':root::view-transition-new(dashed-ident):only-child' is not a valid selector.
FAIL ".a::view-transition-new(dashed-ident):only-child" should be a valid selector '.a::view-transition-new(dashed-ident):only-child' is not a valid selector.
FAIL "div ::view-transition-new(dashed-ident):only-child" should be a valid selector 'div ::view-transition-new(dashed-ident):only-child' is not a valid selector.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!doctype html>
<meta charset="utf-8">
<title>Test pseudo elements parsing of valid selectors</title>
<link rel="author" href="https://github.com/nt1m">
<link rel="help" href="https://drafts.csswg.org/css-view-transitions-1/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/css/support/parsing-testcommon.js"></script>
<script>
const functionPseudoElements = [
"::view-transition-group",
"::view-transition-image-pair",
"::view-transition-old",
"::view-transition-new",
];

function test_valid_selector_combinations(pseudo) {
test_valid_selector(pseudo);
test_valid_selector(`:root${pseudo}`);
test_valid_selector(`.a${pseudo}`);
test_valid_selector(`div ${pseudo}`);
}

test_valid_selector_combinations("::view-transition");

for (const functionName of functionPseudoElements) {
for (const validArgument of ["*", "root", "dashed-ident"]) {
test_valid_selector_combinations(`${functionName}(${validArgument})`);
test_valid_selector_combinations(`${functionName}(${validArgument}):only-child`);
}
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ PASS Property view-transition-name value 'none'
PASS Property view-transition-name value 'foo'
PASS Property view-transition-name value 'bar'
PASS Property view-transition-name value 'baz'
PASS Property view-transition-name value 'unset'
PASS Property view-transition-name value 'initial'
PASS Property view-transition-name value 'inherit'
PASS Property view-transition-name value 'revert'
PASS Property view-transition-name value 'revert-layer'

Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
</head>
<body>
<div id=target></div>
<div id=scratch></div>
<script>

test_computed_value("view-transition-name", "none");
test_computed_value("view-transition-name", "foo");
test_computed_value("view-transition-name", "bar");
test_computed_value("view-transition-name", "baz");
test_computed_value("view-transition-name", "unset", "none");
test_computed_value("view-transition-name", "initial", "none");
test_computed_value("view-transition-name", "inherit", "none");
test_computed_value("view-transition-name", "revert", "none");
test_computed_value("view-transition-name", "revert-layer", "none");
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

PASS e.style['view-transition-name'] = "default" should not set the property value
PASS e.style['view-transition-name'] = "none none" should not set the property value
PASS e.style['view-transition-name'] = "\"none\"" should not set the property value
PASS e.style['view-transition-name'] = "\"foo\"" should not set the property value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</head>
<body>
<script>
test_invalid_value("view-transition-name", "default"); // `default` isn't allowed by the `<custom-ident>` syntax.
test_invalid_value("view-transition-name", "none none");
test_invalid_value("view-transition-name", `"none"`);
test_invalid_value("view-transition-name", `"foo"`);
Expand Down
7 changes: 6 additions & 1 deletion Source/WebCore/animation/WebAnimationUtilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
#include "AnimationPlaybackEvent.h"
#include "CSSAnimation.h"
#include "CSSAnimationEvent.h"
#include "CSSParserContext.h"
#include "CSSPropertyNames.h"
#include "CSSSelector.h"
#include "CSSSelectorParser.h"
#include "CSSTransition.h"
#include "CSSTransitionEvent.h"
#include "DeclarativeAnimation.h"
Expand Down Expand Up @@ -327,7 +329,10 @@ ExceptionOr<PseudoId> pseudoIdFromString(const String& pseudoElement)
auto isLegacy = pseudoElement == ":before"_s || pseudoElement == ":after"_s || pseudoElement == ":first-letter"_s || pseudoElement == ":first-line"_s;
if (!isLegacy && !pseudoElement.startsWith("::"_s))
return Exception { ExceptionCode::SyntaxError };
auto pseudoType = CSSSelector::parsePseudoElementType(StringView(pseudoElement).substring(isLegacy ? 1 : 2));

// FIXME: This parserContext should include a document to get the proper settings.
CSSSelectorParserContext parserContext { CSSParserContext { HTMLStandardMode } };
auto pseudoType = CSSSelector::parsePseudoElementType(StringView(pseudoElement).substring(isLegacy ? 1 : 2), parserContext);
if (pseudoType == CSSSelector::PseudoElementUnknown || pseudoType == CSSSelector::PseudoElementWebKitCustom)
return Exception { ExceptionCode::SyntaxError };
return CSSSelector::pseudoId(pseudoType);
Expand Down
3 changes: 2 additions & 1 deletion Source/WebCore/css/CSSComputedStyleDeclaration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "CSSPropertyAnimation.h"
#include "CSSPropertyParser.h"
#include "CSSSelector.h"
#include "CSSSelectorParser.h"
#include "CSSValuePool.h"
#include "ComposedTreeAncestorIterator.h"
#include "ComputedStyleExtractor.h"
Expand All @@ -55,7 +56,7 @@ CSSComputedStyleDeclaration::CSSComputedStyleDeclaration(Element& element, bool
name = name.substring(1);
if (name.startsWith(':'))
name = name.substring(1);
m_pseudoElementSpecifier = CSSSelector::pseudoId(CSSSelector::parsePseudoElementType(name));
m_pseudoElementSpecifier = CSSSelector::pseudoId(CSSSelector::parsePseudoElementType(name, CSSSelectorParserContext { element.document() }));
}

CSSComputedStyleDeclaration::~CSSComputedStyleDeclaration() = default;
Expand Down
Loading

0 comments on commit 331b2c1

Please sign in to comment.