mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 07:28:11 +00:00
LibWeb: Add preliminary support for CSS animations
This partially implements CSS-Animations-1 (though there are references to CSS-Animations-2). Current limitations: - Multi-selector keyframes are not supported. - Most animation properties are ignored. - Timing functions are not applied. - Non-absolute values are not interpolated unless the target is also of the same non-absolute type (e.g. 10% -> 25%, but not 10% -> 20px). - The JavaScript interface is left as an exercise for the next poor soul looking at this code. With those said, this commit implements: - Interpolation for most common types - Proper keyframe resolution (including the synthetic from-keyframe containing the initial state) - Properly driven animations, and proper style invalidation Co-Authored-By: Andreas Kling <kling@serenityos.org>
This commit is contained in:
parent
f07c4ffbc8
commit
e90752cc21
31 changed files with 1062 additions and 12 deletions
|
@ -15,6 +15,8 @@
|
|||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/CSS/CSSFontFaceRule.h>
|
||||
#include <LibWeb/CSS/CSSImportRule.h>
|
||||
#include <LibWeb/CSS/CSSKeyframeRule.h>
|
||||
#include <LibWeb/CSS/CSSKeyframesRule.h>
|
||||
#include <LibWeb/CSS/CSSMediaRule.h>
|
||||
#include <LibWeb/CSS/CSSStyleDeclaration.h>
|
||||
#include <LibWeb/CSS/CSSStyleRule.h>
|
||||
|
@ -3164,6 +3166,108 @@ CSSRule* Parser::convert_to_rule(NonnullRefPtr<Rule> rule)
|
|||
auto rule_list = CSSRuleList::create(m_context.realm(), child_rules).release_value_but_fixme_should_propagate_errors();
|
||||
return CSSSupportsRule::create(m_context.realm(), supports.release_nonnull(), rule_list).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
if (rule->at_rule_name().equals_ignoring_ascii_case("keyframes"sv)) {
|
||||
auto prelude_stream = TokenStream { rule->prelude() };
|
||||
prelude_stream.skip_whitespace();
|
||||
auto token = prelude_stream.next_token();
|
||||
if (!token.is_token()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule->prelude());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto name_token = token.token();
|
||||
prelude_stream.skip_whitespace();
|
||||
|
||||
if (prelude_stream.has_next_token()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule->prelude());
|
||||
return {};
|
||||
}
|
||||
|
||||
if (name_token.is(Token::Type::Ident) && (is_builtin(name_token.ident()) || name_token.ident().equals_ignoring_ascii_case("none"sv))) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule name is invalid: {}; discarding.", name_token.ident());
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!name_token.is(Token::Type::String) && !name_token.is(Token::Type::Ident)) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule name is invalid: {}; discarding.", name_token.to_debug_string());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto name = name_token.to_string().release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
if (!rule->block())
|
||||
return {};
|
||||
|
||||
auto child_tokens = TokenStream { rule->block()->values() };
|
||||
|
||||
Vector<JS::NonnullGCPtr<CSSKeyframeRule>> keyframes;
|
||||
while (child_tokens.has_next_token()) {
|
||||
child_tokens.skip_whitespace();
|
||||
// keyframe-selector = <keyframe-keyword> | <percentage>
|
||||
// keyframe-keyword = "from" | "to"
|
||||
// selector = <keyframe-selector>#
|
||||
// keyframes-block = "{" <declaration-list>? "}"
|
||||
// keyframe-rule = <selector> <keyframes-block>
|
||||
|
||||
auto selectors = Vector<CSS::Percentage> {};
|
||||
while (child_tokens.has_next_token()) {
|
||||
child_tokens.skip_whitespace();
|
||||
if (!child_tokens.has_next_token())
|
||||
break;
|
||||
auto tok = child_tokens.next_token();
|
||||
if (!tok.is_token()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule has invalid selector: {}; discarding.", tok.to_debug_string());
|
||||
child_tokens.reconsume_current_input_token();
|
||||
break;
|
||||
}
|
||||
auto token = tok.token();
|
||||
auto read_a_selector = false;
|
||||
if (token.is(Token::Type::Ident)) {
|
||||
if (token.ident().equals_ignoring_ascii_case("from"sv)) {
|
||||
selectors.append(CSS::Percentage(0));
|
||||
read_a_selector = true;
|
||||
}
|
||||
if (token.ident().equals_ignoring_ascii_case("to"sv)) {
|
||||
selectors.append(CSS::Percentage(100));
|
||||
read_a_selector = true;
|
||||
}
|
||||
} else if (token.is(Token::Type::Percentage)) {
|
||||
selectors.append(CSS::Percentage(token.percentage()));
|
||||
read_a_selector = true;
|
||||
}
|
||||
|
||||
if (read_a_selector) {
|
||||
child_tokens.skip_whitespace();
|
||||
if (child_tokens.next_token().is(Token::Type::Comma))
|
||||
continue;
|
||||
}
|
||||
|
||||
child_tokens.reconsume_current_input_token();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!child_tokens.has_next_token())
|
||||
break;
|
||||
|
||||
child_tokens.skip_whitespace();
|
||||
auto token = child_tokens.next_token();
|
||||
if (token.is_block()) {
|
||||
auto block_tokens = token.block().values();
|
||||
auto block_stream = TokenStream { block_tokens };
|
||||
|
||||
auto block_declarations = parse_a_list_of_declarations(block_stream);
|
||||
auto style = convert_to_style_declaration(block_declarations);
|
||||
for (auto& selector : selectors) {
|
||||
auto keyframe_rule = CSSKeyframeRule::create(m_context.realm(), selector, *style).release_value_but_fixme_should_propagate_errors();
|
||||
keyframes.append(keyframe_rule);
|
||||
}
|
||||
} else {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule has invalid block: {}; discarding.", token.to_debug_string());
|
||||
}
|
||||
}
|
||||
|
||||
return CSSKeyframesRule::create(m_context.realm(), name, move(keyframes)).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
// FIXME: More at rules!
|
||||
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", rule->at_rule_name());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue