diff --git a/AK/Debug.h.in b/AK/Debug.h.in index b81b9a0853..087aa34a35 100644 --- a/AK/Debug.h.in +++ b/AK/Debug.h.in @@ -242,6 +242,10 @@ # cmakedefine01 LIBWEB_CSS_DEBUG #endif +#ifndef LIBWEB_CSS_ANIMATION_DEBUG +# cmakedefine01 LIBWEB_CSS_ANIMATION_DEBUG +#endif + #ifndef LINE_EDITOR_DEBUG # cmakedefine01 LINE_EDITOR_DEBUG #endif diff --git a/Base/res/html/misc/css-animations.html b/Base/res/html/misc/css-animations.html new file mode 100644 index 0000000000..99862f96c1 --- /dev/null +++ b/Base/res/html/misc/css-animations.html @@ -0,0 +1,50 @@ + + +
+
+
+
+
+
+
diff --git a/Base/res/html/misc/welcome.html b/Base/res/html/misc/welcome.html index 1ed1c42dbd..800828bb20 100644 --- a/Base/res/html/misc/welcome.html +++ b/Base/res/html/misc/welcome.html @@ -163,6 +163,7 @@
  • Styling "inline" elements
  • Pseudo-elements (::before, ::after, etc)
  • Effects with opacity and transforms
  • +
  • CSS Animations
  • JavaScript/Wasm

    diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index 6c5e6d7a5a..2a2b9a2611 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -92,6 +92,7 @@ set(KEYBOARD_SHORTCUTS_DEBUG ON) set(KMALLOC_DEBUG ON) set(LANGUAGE_SERVER_DEBUG ON) set(LEXER_DEBUG ON) +set(LIBWEB_CSS_ANIMATION_DEBUG ON) set(LIBWEB_CSS_DEBUG ON) set(LINE_EDITOR_DEBUG ON) set(LOCAL_SOCKET_DEBUG ON) diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index a03cd83ffe..8d9e79f8e0 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -22,6 +22,8 @@ set(SOURCES CSS/CSSConditionRule.cpp CSS/CSSGroupingRule.cpp CSS/CSSImportRule.cpp + CSS/CSSKeyframeRule.cpp + CSS/CSSKeyframesRule.cpp CSS/CSSFontFaceRule.cpp CSS/CSSMediaRule.cpp CSS/CSSRule.cpp diff --git a/Userland/Libraries/LibWeb/CSS/CSSConditionRule.cpp b/Userland/Libraries/LibWeb/CSS/CSSConditionRule.cpp index 5e4be880de..172a23e78c 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSConditionRule.cpp +++ b/Userland/Libraries/LibWeb/CSS/CSSConditionRule.cpp @@ -22,6 +22,12 @@ void CSSConditionRule::for_each_effective_style_rule(Function const& callback) const +{ + if (condition_matches()) + CSSGroupingRule::for_each_effective_keyframes_at_rule(callback); +} + JS::ThrowCompletionOr CSSConditionRule::initialize(JS::Realm& realm) { MUST_OR_THROW_OOM(Base::initialize(realm)); diff --git a/Userland/Libraries/LibWeb/CSS/CSSConditionRule.h b/Userland/Libraries/LibWeb/CSS/CSSConditionRule.h index 825106bb38..b705f47ac3 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSConditionRule.h +++ b/Userland/Libraries/LibWeb/CSS/CSSConditionRule.h @@ -23,6 +23,7 @@ public: virtual bool condition_matches() const = 0; virtual void for_each_effective_style_rule(Function const& callback) const override; + virtual void for_each_effective_keyframes_at_rule(Function const& callback) const override; protected: CSSConditionRule(JS::Realm&, CSSRuleList&); diff --git a/Userland/Libraries/LibWeb/CSS/CSSGroupingRule.cpp b/Userland/Libraries/LibWeb/CSS/CSSGroupingRule.cpp index 23958d2b78..b91aa011e0 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSGroupingRule.cpp +++ b/Userland/Libraries/LibWeb/CSS/CSSGroupingRule.cpp @@ -54,6 +54,11 @@ void CSSGroupingRule::for_each_effective_style_rule(Functionfor_each_effective_style_rule(callback); } +void CSSGroupingRule::for_each_effective_keyframes_at_rule(Function const& callback) const +{ + m_rules->for_each_effective_keyframes_at_rule(callback); +} + void CSSGroupingRule::set_parent_style_sheet(CSSStyleSheet* parent_style_sheet) { CSSRule::set_parent_style_sheet(parent_style_sheet); diff --git a/Userland/Libraries/LibWeb/CSS/CSSGroupingRule.h b/Userland/Libraries/LibWeb/CSS/CSSGroupingRule.h index f39b20ab44..826cae8ee2 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSGroupingRule.h +++ b/Userland/Libraries/LibWeb/CSS/CSSGroupingRule.h @@ -27,6 +27,7 @@ public: WebIDL::ExceptionOr delete_rule(u32 index); virtual void for_each_effective_style_rule(Function const& callback) const; + virtual void for_each_effective_keyframes_at_rule(Function const& callback) const; virtual void set_parent_style_sheet(CSSStyleSheet*) override; diff --git a/Userland/Libraries/LibWeb/CSS/CSSKeyframeRule.cpp b/Userland/Libraries/LibWeb/CSS/CSSKeyframeRule.cpp new file mode 100644 index 0000000000..35de9eaa74 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/CSSKeyframeRule.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "CSSKeyframeRule.h" +#include + +namespace Web::CSS { + +void CSSKeyframeRule::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_declarations); +} + +JS::ThrowCompletionOr CSSKeyframeRule::initialize(JS::Realm&) +{ + return {}; +} + +DeprecatedString CSSKeyframeRule::serialized() const +{ + StringBuilder builder; + builder.appendff("{}% {{ {} }}", key().value(), style()->serialized()); + return builder.to_deprecated_string(); +} + +} diff --git a/Userland/Libraries/LibWeb/CSS/CSSKeyframeRule.h b/Userland/Libraries/LibWeb/CSS/CSSKeyframeRule.h new file mode 100644 index 0000000000..843f3a9aac --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/CSSKeyframeRule.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Web::CSS { + +// https://drafts.csswg.org/css-animations/#interface-csskeyframerule +class CSSKeyframeRule final : public CSSRule { + WEB_PLATFORM_OBJECT(CSSKeyframeRule, CSSRule); + +public: + static WebIDL::ExceptionOr> create(JS::Realm& realm, CSS::Percentage key, CSSStyleDeclaration& declarations) + { + return MUST_OR_THROW_OOM(realm.heap().allocate(realm, realm, key, declarations)); + } + + virtual ~CSSKeyframeRule() = default; + + virtual Type type() const override { return Type::Keyframe; }; + + CSS::Percentage key() const { return m_key; } + JS::NonnullGCPtr style() const { return m_declarations; } + +private: + CSSKeyframeRule(JS::Realm& realm, CSS::Percentage key, CSSStyleDeclaration& declarations) + : CSSRule(realm) + , m_key(key) + , m_declarations(declarations) + { + } + + virtual void visit_edges(Visitor&) override; + virtual JS::ThrowCompletionOr initialize(JS::Realm&) override; + virtual DeprecatedString serialized() const override; + + CSS::Percentage m_key; + JS::NonnullGCPtr m_declarations; +}; + +template<> +inline bool CSSRule::fast_is() const { return type() == CSSRule::Type::Keyframe; } + +} diff --git a/Userland/Libraries/LibWeb/CSS/CSSKeyframeRule.idl b/Userland/Libraries/LibWeb/CSS/CSSKeyframeRule.idl new file mode 100644 index 0000000000..fd9c8de99f --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/CSSKeyframeRule.idl @@ -0,0 +1,7 @@ +#import + +[Exposed = Window] +interface CSSKeyframeRule : CSSRule { + attribute CSSOMString keyText; + [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style; +}; diff --git a/Userland/Libraries/LibWeb/CSS/CSSKeyframesRule.cpp b/Userland/Libraries/LibWeb/CSS/CSSKeyframesRule.cpp new file mode 100644 index 0000000000..33a061d6e3 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/CSSKeyframesRule.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "CSSKeyframesRule.h" + +namespace Web::CSS { + +void CSSKeyframesRule::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + for (auto& keyframe : m_keyframes) + visitor.visit(keyframe); +} + +JS::ThrowCompletionOr CSSKeyframesRule::initialize(JS::Realm&) +{ + return {}; +} + +DeprecatedString CSSKeyframesRule::serialized() const +{ + StringBuilder builder; + builder.appendff("@keyframes \"{}\"", name()); + builder.append(" { "sv); + for (auto& keyframe : keyframes()) { + builder.append(keyframe->css_text()); + builder.append(' '); + } + builder.append('}'); + return builder.to_deprecated_string(); +} + +} diff --git a/Userland/Libraries/LibWeb/CSS/CSSKeyframesRule.h b/Userland/Libraries/LibWeb/CSS/CSSKeyframesRule.h new file mode 100644 index 0000000000..6c67f6cbc7 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/CSSKeyframesRule.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Web::CSS { + +// https://drafts.csswg.org/css-animations/#interface-csskeyframesrule +class CSSKeyframesRule final : public CSSRule { + WEB_PLATFORM_OBJECT(CSSKeyframesRule, CSSRule); + +public: + static WebIDL::ExceptionOr> create(JS::Realm& realm, FlyString name, Vector> keyframes) + { + return MUST_OR_THROW_OOM(realm.heap().allocate(realm, realm, move(name), move(keyframes))); + } + + virtual ~CSSKeyframesRule() = default; + + virtual Type type() const override { return Type::Keyframes; }; + + Vector> const& keyframes() const { return m_keyframes; } + FlyString const& name() const { return m_name; } + +private: + CSSKeyframesRule(JS::Realm& realm, FlyString name, Vector> keyframes) + : CSSRule(realm) + , m_name(move(name)) + , m_keyframes(move(keyframes)) + { + } + + virtual void visit_edges(Visitor&) override; + + virtual JS::ThrowCompletionOr initialize(JS::Realm&) override; + virtual DeprecatedString serialized() const override; + + FlyString m_name; + Vector> m_keyframes; +}; + +template<> +inline bool CSSRule::fast_is() const { return type() == CSSRule::Type::Keyframes; } + +} diff --git a/Userland/Libraries/LibWeb/CSS/CSSKeyframesRule.idl b/Userland/Libraries/LibWeb/CSS/CSSKeyframesRule.idl new file mode 100644 index 0000000000..e307938403 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/CSSKeyframesRule.idl @@ -0,0 +1,13 @@ +#import + +[Exposed=Window] +interface CSSKeyframesRule : CSSRule { + attribute CSSOMString name; + readonly attribute CSSRuleList cssRules; + readonly attribute unsigned long length; + + getter CSSKeyframeRule (unsigned long index); + undefined appendRule(CSSOMString rule); + undefined deleteRule(CSSOMString select); + CSSKeyframeRule? findRule(CSSOMString select); +}; diff --git a/Userland/Libraries/LibWeb/CSS/CSSRule.h b/Userland/Libraries/LibWeb/CSS/CSSRule.h index 61c229eb86..db9ae0bcb7 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSRule.h +++ b/Userland/Libraries/LibWeb/CSS/CSSRule.h @@ -27,6 +27,8 @@ public: Import = 3, Media = 4, FontFace = 5, + Keyframes = 7, + Keyframe = 8, Supports = 12, }; diff --git a/Userland/Libraries/LibWeb/CSS/CSSRule.idl b/Userland/Libraries/LibWeb/CSS/CSSRule.idl index bd40b41bd6..0c9d44327f 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSRule.idl +++ b/Userland/Libraries/LibWeb/CSS/CSSRule.idl @@ -16,6 +16,8 @@ interface CSSRule { const unsigned short MEDIA_RULE = 4; const unsigned short FONT_FACE_RULE = 5; const unsigned short PAGE_RULE = 6; + const unsigned short KEYFRAMES_RULE = 7; + const unsigned short KEYFRAME_RULE = 8; const unsigned short MARGIN_RULE = 9; const unsigned short NAMESPACE_RULE = 10; const unsigned short SUPPORTS_RULE = 12; diff --git a/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp b/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp index 27c3e5335f..60b9894a84 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp +++ b/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -141,6 +142,38 @@ void CSSRuleList::for_each_effective_style_rule(Function(*rule).for_each_effective_style_rule(callback); break; + case CSSRule::Type::Keyframe: + case CSSRule::Type::Keyframes: + break; + } + } +} + +void CSSRuleList::for_each_effective_keyframes_at_rule(Function const& callback) const +{ + for (auto const& rule : m_rules) { + switch (rule->type()) { + case CSSRule::Type::FontFace: + break; + case CSSRule::Type::Import: { + auto const& import_rule = static_cast(*rule); + if (import_rule.loaded_style_sheet()) + import_rule.loaded_style_sheet()->for_each_effective_keyframes_at_rule(callback); + break; + } + case CSSRule::Type::Media: + static_cast(*rule).for_each_effective_keyframes_at_rule(callback); + break; + case CSSRule::Type::Style: + break; + case CSSRule::Type::Supports: + static_cast(*rule).for_each_effective_keyframes_at_rule(callback); + break; + case CSSRule::Type::Keyframe: + break; + case CSSRule::Type::Keyframes: + callback(static_cast(*rule)); + break; } } } @@ -177,6 +210,9 @@ bool CSSRuleList::evaluate_media_queries(HTML::Window const& window) any_media_queries_changed_match_state = true; break; } + case CSSRule::Type::Keyframe: + case CSSRule::Type::Keyframes: + break; } } diff --git a/Userland/Libraries/LibWeb/CSS/CSSRuleList.h b/Userland/Libraries/LibWeb/CSS/CSSRuleList.h index 7d9bd7149d..199adfc085 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSRuleList.h +++ b/Userland/Libraries/LibWeb/CSS/CSSRuleList.h @@ -59,6 +59,7 @@ public: void for_each_effective_style_rule(Function const& callback) const; // Returns whether the match state of any media queries changed after evaluation. bool evaluate_media_queries(HTML::Window const&); + void for_each_effective_keyframes_at_rule(Function const& callback) const; private: explicit CSSRuleList(JS::Realm&); diff --git a/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.h b/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.h index c349dc4a20..139994eaf2 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.h +++ b/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.h @@ -53,7 +53,8 @@ class PropertyOwningCSSStyleDeclaration : public CSSStyleDeclaration { friend class ElementInlineCSSStyleDeclaration; public: - static WebIDL::ExceptionOr> create(JS::Realm&, Vector, HashMap custom_properties); + static WebIDL::ExceptionOr> + create(JS::Realm&, Vector, HashMap custom_properties); virtual ~PropertyOwningCSSStyleDeclaration() override = default; diff --git a/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.cpp b/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.cpp index 8a05413f46..7c09a573aa 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.cpp +++ b/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.cpp @@ -112,6 +112,12 @@ void CSSStyleSheet::for_each_effective_style_rule(Function const& callback) const +{ + if (m_media->matches()) + m_rules->for_each_effective_keyframes_at_rule(callback); +} + bool CSSStyleSheet::evaluate_media_queries(HTML::Window const& window) { bool any_media_queries_changed_match_state = false; diff --git a/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.h b/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.h index 157c7ddefe..d9114ced0f 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.h +++ b/Userland/Libraries/LibWeb/CSS/CSSStyleSheet.h @@ -45,6 +45,7 @@ public: void for_each_effective_style_rule(Function const& callback) const; // Returns whether the match state of any media queries changed after evaluation. bool evaluate_media_queries(HTML::Window const&); + void for_each_effective_keyframes_at_rule(Function const& callback) const; void set_style_sheet_list(Badge, StyleSheetList*); diff --git a/Userland/Libraries/LibWeb/CSS/Enums.json b/Userland/Libraries/LibWeb/CSS/Enums.json index 34cdc7fb46..2859f31787 100644 --- a/Userland/Libraries/LibWeb/CSS/Enums.json +++ b/Userland/Libraries/LibWeb/CSS/Enums.json @@ -32,6 +32,18 @@ "stretch", "unsafe" ], + "animation-fill-mode": [ + "backwards", + "both", + "forwards", + "none" + ], + "animation-direction": [ + "alternate", + "alternate-reverse", + "normal", + "reverse" + ], "appearance": [ "auto", "button", diff --git a/Userland/Libraries/LibWeb/CSS/Identifiers.json b/Userland/Libraries/LibWeb/CSS/Identifiers.json index 3110d40ee3..5a0fa0df50 100644 --- a/Userland/Libraries/LibWeb/CSS/Identifiers.json +++ b/Userland/Libraries/LibWeb/CSS/Identifiers.json @@ -61,9 +61,12 @@ "alias", "all", "all-scroll", + "alternate", + "alternate-reverse", "anywhere", "auto", "back", + "backwards", "baseline", "blink", "block", @@ -109,6 +112,10 @@ "dotted", "double", "e-resize", + "ease", + "ease-in", + "ease-in-out", + "ease-out", "enabled", "end", "ew-resize", @@ -126,6 +133,7 @@ "flow", "flow-root", "from-font", + "forwards", "full-size-kana", "full-width", "fullscreen", @@ -161,6 +169,7 @@ "less", "light", "lighter", + "linear", "line-through", "list-item", "local", @@ -204,6 +213,7 @@ "p3", "padding-box", "paged", + "paused", "pixelated", "pointer", "portrait", @@ -220,6 +230,7 @@ "repeat", "repeat-x", "repeat-y", + "reverse", "ridge", "right", "round", @@ -232,6 +243,7 @@ "ruby-base-container", "ruby-text", "ruby-text-container", + "running", "run-in", "radio", "s-resize", diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 407fcfaff8..8c75b3b8d5 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -3164,6 +3166,108 @@ CSSRule* Parser::convert_to_rule(NonnullRefPtr 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> keyframes; + while (child_tokens.has_next_token()) { + child_tokens.skip_whitespace(); + // keyframe-selector = | + // keyframe-keyword = "from" | "to" + // selector = # + // keyframes-block = "{" ? "}" + // keyframe-rule = + + auto selectors = Vector {}; + 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()); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/TokenStream.h b/Userland/Libraries/LibWeb/CSS/Parser/TokenStream.h index 7cbc4f2e84..ea654e7137 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/TokenStream.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/TokenStream.h @@ -54,11 +54,18 @@ public: bool m_commit { false }; }; - explicit TokenStream(Vector const& tokens) + explicit TokenStream(Span tokens) : m_tokens(tokens) , m_eof(make_eof()) { } + + explicit TokenStream(Vector const& tokens) + : m_tokens(tokens.span()) + , m_eof(make_eof()) + { + } + TokenStream(TokenStream const&) = delete; TokenStream(TokenStream&&) = default; @@ -128,7 +135,7 @@ public: } private: - Vector const& m_tokens; + Span m_tokens; int m_iterator_offset { -1 }; T make_eof() diff --git a/Userland/Libraries/LibWeb/CSS/Properties.json b/Userland/Libraries/LibWeb/CSS/Properties.json index 38d884c8c0..42bd632c05 100644 --- a/Userland/Libraries/LibWeb/CSS/Properties.json +++ b/Userland/Libraries/LibWeb/CSS/Properties.json @@ -30,6 +30,103 @@ "align-self" ] }, + "animation": { + "affects-layout": true, + "inherited": false, + "initial": "none 0s ease 1 normal running 0s none", + "longhands": [ + "animation-name", + "animation-duration", + "animation-timing-function", + "animation-iteration-count", + "animation-direction", + "animation-play-state", + "animation-delay", + "animation-fill-mode" + ] + }, + "animation-name": { + "affects-layout": true, + "inherited": false, + "initial": "none", + "valid-types": [ + "string", "custom-ident" + ], + "valid-identifiers": [ + "none" + ] + }, + "animation-duration": { + "affects-layout": true, + "inherited": false, + "initial": "0s", + "valid-types": [ + "time [0,∞]" + ] + }, + "animation-timing-function": { + "affects-layout": true, + "inherited": false, + "initial": "ease", + "__comment": "FIXME: This is like...wrong.", + "valid-identifiers": [ + "ease", + "linear", + "ease-in-out", + "ease-in", + "ease-out" + ] + }, + "animation-iteration-count": { + "affects-layout": true, + "inherited": false, + "initial": "1", + "valid-types": [ + "number [0,∞]" + ], + "valid-identifiers": [ + "infinite" + ] + }, + "animation-direction": { + "affects-layout": false, + "inherited": false, + "initial": "normal", + "valid-identifiers": [ + "normal", + "reverse", + "alternate", + "alternate-reverse" + ] + }, + "animation-play-state": { + "affects-layout": false, + "inherited": false, + "initial": "running", + "valid-identifiers": [ + "running", + "paused" + ] + }, + "animation-delay": { + "affects-layout": true, + "inherited": false, + "initial": "0s", + "valid-types": [ + "time" + ] + }, + "animation-fill-mode": { + "affects-layout": true, + "inherited": false, + "initial": "none", + "valid-identifiers": [ + "none", + "forwards", + "backwards", + "both" + ] + }, "appearance": { "inherited": false, "initial": "auto", @@ -237,7 +334,7 @@ "affects-layout": false, "initial": "currentcolor", "inherited": false, - "valid-types": [ + "valid-types": [ "color" ], "quirks": [ diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index 86a104efdb..88b029a002 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -986,6 +986,297 @@ static ErrorOr cascade_custom_properties(DOM::Element& element, Optional time_step_ms) { + remaining_delay = CSS::Time { static_cast(delay_ms - time_step_ms), CSS::Time::Type::Ms }; + return AnimationStepTransition::NoTransition; + } + + remaining_delay = CSS::Time { 0, CSS::Time::Type::Ms }; + time_step_ms -= delay_ms; + + float added_progress = static_cast(time_step_ms / duration.to_milliseconds()); + auto new_progress = progress.as_fraction() + added_progress; + auto changed_iteration = false; + if (new_progress >= 1) { + if (iteration_count.has_value()) { + if (iteration_count.value() == 0) { + progress = CSS::Percentage(100); + return AnimationStepTransition::ActiveToAfter; + } + --iteration_count.value(); + changed_iteration = true; + } + new_progress = 0; + } + progress = CSS::Percentage(new_progress * 100); + + if (changed_iteration) + return AnimationStepTransition::ActiveToActiveChangingTheIteration; + + return AnimationStepTransition::AfterToActive; +} + +static ErrorOr> interpolate_property(StyleValue const& from, StyleValue const& to, float delta) +{ + if (from.type() != to.type()) { + if (delta > 0.999f) + return to; + return from; + } + + auto interpolate_raw = [delta = static_cast(delta)](auto from, auto to) { + return static_cast>(static_cast(from) + static_cast(to - from) * delta); + }; + + switch (from.type()) { + case StyleValue::Type::Angle: + return AngleStyleValue::create(Angle::make_degrees(interpolate_raw(from.as_angle().angle().to_degrees(), to.as_angle().angle().to_degrees()))); + case StyleValue::Type::Color: { + auto from_color = from.as_color().color(); + auto to_color = to.as_color().color(); + auto from_hsv = from_color.to_hsv(); + auto to_hsv = to_color.to_hsv(); + + auto color = Color::from_hsv( + interpolate_raw(from_hsv.hue, to_hsv.hue), + interpolate_raw(from_hsv.saturation, to_hsv.saturation), + interpolate_raw(from_hsv.value, to_hsv.value)); + color.set_alpha(interpolate_raw(from_color.alpha(), to_color.alpha())); + + return ColorStyleValue::create(color); + } + case StyleValue::Type::Length: { + auto& from_length = from.as_length().length(); + auto& to_length = to.as_length().length(); + return LengthStyleValue::create(Length(interpolate_raw(from_length.raw_value(), to_length.raw_value()), from_length.type())); + } + case StyleValue::Type::Numeric: + return NumericStyleValue::create_float(interpolate_raw(from.as_numeric().number(), to.as_numeric().number())); + case StyleValue::Type::Percentage: + return PercentageStyleValue::create(Percentage(interpolate_raw(from.as_percentage().percentage().value(), to.as_percentage().percentage().value()))); + case StyleValue::Type::Position: { + auto& from_position = from.as_position(); + auto& to_position = to.as_position(); + return PositionStyleValue::create( + TRY(interpolate_property(from_position.edge_x(), to_position.edge_x(), delta)), + TRY(interpolate_property(from_position.edge_y(), to_position.edge_y(), delta))); + } + case StyleValue::Type::Rect: { + auto from_rect = from.as_rect().rect(); + auto to_rect = to.as_rect().rect(); + return RectStyleValue::create({ + Length(interpolate_raw(from_rect.top_edge.raw_value(), to_rect.top_edge.raw_value()), from_rect.top_edge.type()), + Length(interpolate_raw(from_rect.right_edge.raw_value(), to_rect.right_edge.raw_value()), from_rect.right_edge.type()), + Length(interpolate_raw(from_rect.bottom_edge.raw_value(), to_rect.bottom_edge.raw_value()), from_rect.bottom_edge.type()), + Length(interpolate_raw(from_rect.left_edge.raw_value(), to_rect.left_edge.raw_value()), from_rect.left_edge.type()), + }); + } + case StyleValue::Type::Transformation: { + auto& from_transform = from.as_transformation(); + auto& to_transform = to.as_transformation(); + if (from_transform.transform_function() != to_transform.transform_function()) + return from; + + auto from_input_values = from_transform.values(); + auto to_input_values = to_transform.values(); + if (from_input_values.size() != to_input_values.size()) + return from; + + StyleValueVector interpolated_values; + interpolated_values.ensure_capacity(from_input_values.size()); + for (size_t i = 0; i < from_input_values.size(); ++i) + interpolated_values.append(TRY(interpolate_property(*from_input_values[i], *to_input_values[i], delta))); + + return TransformationStyleValue::create(from_transform.transform_function(), move(interpolated_values)); + } + case StyleValue::Type::ValueList: { + auto& from_list = from.as_value_list(); + auto& to_list = to.as_value_list(); + if (from_list.size() != to_list.size()) + return from; + + StyleValueVector interpolated_values; + interpolated_values.ensure_capacity(from_list.size()); + for (size_t i = 0; i < from_list.size(); ++i) + interpolated_values.append(TRY(interpolate_property(from_list.values()[i], to_list.values()[i], delta))); + + return StyleValueList::create(move(interpolated_values), from_list.separator()); + } + default: + return from; + } +} + +ErrorOr StyleComputer::Animation::collect_into(StyleProperties& style_properties, RuleCache const& rule_cache) const +{ + if (remaining_delay.to_milliseconds() != 0) + return {}; + + auto matching_keyframes = rule_cache.rules_by_animation_keyframes.get(name); + if (!matching_keyframes.has_value()) + return {}; + + auto& keyframes = matching_keyframes.value()->keyframes_by_key; + + auto key = static_cast(progress.value() * AnimationKeyFrameKeyScaleFactor); + auto matching_keyframe_it = keyframes.find_largest_not_above_iterator(key); + if (matching_keyframe_it.is_end()) { + if constexpr (LIBWEB_CSS_ANIMATION_DEBUG) { + dbgln(" Did not find any start keyframe for the current state ({}) :(", key); + dbgln(" (have {} keyframes)", keyframes.size()); + for (auto it = keyframes.begin(); it != keyframes.end(); ++it) + dbgln(" - {}", it.key()); + } + return {}; + } + + auto keyframe_start = matching_keyframe_it.key(); + auto keyframe_values = *matching_keyframe_it; + + auto keyframe_end_it = ++matching_keyframe_it; + if (keyframe_end_it.is_end()) { + if constexpr (LIBWEB_CSS_ANIMATION_DEBUG) { + dbgln(" Did not find any end keyframe for the current state ({}) :(", key); + dbgln(" (have {} keyframes)", keyframes.size()); + for (auto it = keyframes.begin(); it != keyframes.end(); ++it) + dbgln(" - {}", it.key()); + } + return {}; + } + + auto keyframe_end = keyframe_end_it.key(); + auto keyframe_end_values = *keyframe_end_it; + + auto progress_in_keyframe = (progress.value() * AnimationKeyFrameKeyScaleFactor - keyframe_start) / (keyframe_end - keyframe_start); + + auto valid_properties = 0; + for (auto const& property : keyframe_values.resolved_properties) { + if (property.has()) + continue; + valid_properties++; + } + + dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "Animation {} contains {} properties to interpolate, progress = {}%", name, valid_properties, progress_in_keyframe * 100); + + UnderlyingType property_id_value = 0; + for (auto const& property : keyframe_values.resolved_properties) { + auto property_id = static_cast(property_id_value++); + if (property.has()) + continue; + + auto resolve_property = [&](auto& property) { + return property.visit( + [](Empty) -> RefPtr { VERIFY_NOT_REACHED(); }, + [&](AnimationKeyFrameSet::ResolvedKeyFrame::UseInitial) { + if (auto value = initial_state[to_underlying(property_id)]) + return value; + + auto value = style_properties.maybe_null_property(property_id); + initial_state[to_underlying(property_id)] = value; + return value; + }, + [&](RefPtr value) { return value; }); + }; + + auto resolved_start_property = resolve_property(property); + + auto const& end_property = keyframe_end_values.resolved_properties[to_underlying(property_id)]; + if (end_property.has()) { + if (resolved_start_property) { + style_properties.set_property(property_id, resolved_start_property.release_nonnull()); + dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "No end property for property {}, using {}", string_from_property_id(property_id), resolved_start_property->to_string()); + } + continue; + } + + auto resolved_end_property = resolve_property(end_property); + + if (!resolved_start_property || !resolved_end_property) + continue; + + auto start = resolved_start_property.release_nonnull(); + auto end = resolved_end_property.release_nonnull(); + + // FIXME: This should be a function of the animation-timing-function. + auto next_value = TRY(interpolate_property(*start, *end, progress_in_keyframe)); + dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "Interpolated value for property {} at {}: {} -> {} = {}", string_from_property_id(property_id), progress_in_keyframe, start->to_string(), end->to_string(), next_value->to_string()); + style_properties.set_property(property_id, next_value); + } + + return {}; +} + +bool StyleComputer::Animation::is_done() const +{ + return progress.as_fraction() >= 0.9999f && iteration_count.has_value() && iteration_count.value() == 0; +} + +void StyleComputer::ensure_animation_timer() const +{ + constexpr static auto timer_delay_ms = 1000 / 60; + if (!m_animation_driver_timer) { + m_animation_driver_timer = Platform::Timer::create_repeating(timer_delay_ms, [this] { + HashTable animations_to_remove; + HashTable owning_elements_to_invalidate; + + for (auto& it : m_active_animations) { + if (!it.value->owning_element) { + // The element disappeared since we last ran, just discard the animation. + animations_to_remove.set(it.key); + continue; + } + + auto transition = it.value->step(CSS::Time { timer_delay_ms, CSS::Time::Type::Ms }); + owning_elements_to_invalidate.set(it.value->owning_element); + + switch (transition) { + case AnimationStepTransition::NoTransition: + break; + case AnimationStepTransition::IdleOrBeforeToActive: + // FIXME: Dispatch `animationstart`. + break; + case AnimationStepTransition::IdleOrBeforeToAfter: + // FIXME: Dispatch `animationstart` then `animationend`. + break; + case AnimationStepTransition::ActiveToBefore: + // FIXME: Dispatch `animationend`. + break; + case AnimationStepTransition::ActiveToActiveChangingTheIteration: + // FIXME: Dispatch `animationiteration`. + break; + case AnimationStepTransition::ActiveToAfter: + // FIXME: Dispatch `animationend`. + break; + case AnimationStepTransition::AfterToActive: + // FIXME: Dispatch `animationstart`. + break; + case AnimationStepTransition::AfterToBefore: + // FIXME: Dispatch `animationstart` then `animationend`. + break; + case AnimationStepTransition::Cancelled: + // FIXME: Dispatch `animationcancel`. + break; + } + if (it.value->is_done()) + animations_to_remove.set(it.key); + } + + for (auto key : animations_to_remove) + m_active_animations.remove(key); + + for (auto* element : owning_elements_to_invalidate) + element->invalidate_style(); + }); + } + + m_animation_driver_timer->start(); +} + // https://www.w3.org/TR/css-cascade/#cascading ErrorOr StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element& element, Optional pseudo_element, bool& did_match_any_pseudo_element_rules, ComputeStyleMode mode) const { @@ -1036,7 +1327,56 @@ ErrorOr StyleComputer::compute_cascaded_values(StyleProperties& style, DOM // Normal author declarations cascade_declarations(style, element, pseudo_element, matching_rule_set.author_rules, CascadeOrigin::Author, Important::No); - // FIXME: Animation declarations [css-animations-1] + // Animation declarations [css-animations-2] + if (auto animation_name = style.maybe_null_property(PropertyID::AnimationName)) { + ensure_animation_timer(); + + if (auto source_declaration = style.property_source_declaration(PropertyID::AnimationName)) { + AnimationKey animation_key { + .source_declaration = source_declaration, + .element = &element, + }; + if (auto name = TRY(animation_name->to_string()); !name.is_empty()) { + auto active_animation = m_active_animations.get(animation_key); + if (!active_animation.has_value()) { + // New animation! + CSS::Time duration { 0, CSS::Time::Type::S }; + if (auto duration_value = style.maybe_null_property(PropertyID::AnimationDuration); duration_value && duration_value->is_time()) + duration = duration_value->as_time().time(); + + CSS::Time delay { 0, CSS::Time::Type::S }; + if (auto delay_value = style.maybe_null_property(PropertyID::AnimationDelay); delay_value && delay_value->is_time()) + delay = delay_value->as_time().time(); + + Optional iteration_count = 1; + if (auto iteration_count_value = style.maybe_null_property(PropertyID::AnimationIterationCount); iteration_count_value) { + if (iteration_count_value->is_identifier() && iteration_count_value->to_identifier() == ValueID::Infinite) + iteration_count = {}; + else if (iteration_count_value->is_numeric()) + iteration_count = static_cast(iteration_count_value->as_numeric().number()); + } + + auto animation = make(Animation { + .name = move(name), + .duration = duration, + .delay = delay, + .iteration_count = iteration_count, + .direction = Animation::Direction::Normal, + .fill_mode = Animation::FillMode::None, + .owning_element = TRY(element.try_make_weak_ptr()), + .progress = CSS::Percentage(0), + .remaining_delay = delay, + }); + active_animation = animation; + m_active_animations.set(animation_key, move(animation)); + } + + TRY((*active_animation)->collect_into(style, rule_cache_for_cascade_origin(CascadeOrigin::Author))); + } else { + m_active_animations.remove(animation_key); + } + } + } // Important author declarations cascade_declarations(style, element, pseudo_element, matching_rule_set.author_rules, CascadeOrigin::Author, Important::Yes); @@ -1709,6 +2049,93 @@ NonnullOwnPtr StyleComputer::make_rule_cache_for_casca } ++rule_index; }); + + sheet.for_each_effective_keyframes_at_rule([&](CSSKeyframesRule const& rule) { + auto keyframe_set = make(); + AnimationKeyFrameSet::ResolvedKeyFrame resolved_keyframe; + + // Forwards pass, resolve all the user-specified keyframe properties. + for (auto const& keyframe : rule.keyframes()) { + auto key = static_cast(keyframe->key().value() * AnimationKeyFrameKeyScaleFactor); + auto keyframe_rule = keyframe->style(); + + if (!is(*keyframe_rule)) + continue; + + auto current_keyframe = resolved_keyframe; + auto& keyframe_style = static_cast(*keyframe_rule); + for (auto& property : keyframe_style.properties()) + current_keyframe.resolved_properties[to_underlying(property.property_id)] = property.value; + + resolved_keyframe = move(current_keyframe); + keyframe_set->keyframes_by_key.insert(key, resolved_keyframe); + } + + // If there is no 'from' keyframe, make a synthetic one. + auto made_a_synthetic_from_keyframe = false; + if (!keyframe_set->keyframes_by_key.find(0)) { + keyframe_set->keyframes_by_key.insert(0, AnimationKeyFrameSet::ResolvedKeyFrame()); + made_a_synthetic_from_keyframe = true; + } + + // Backwards pass, resolve all the implied properties, go read to see why. + auto first = true; + for (auto const& keyframe : rule.keyframes().in_reverse()) { + auto key = static_cast(keyframe->key().value() * AnimationKeyFrameKeyScaleFactor); + auto keyframe_rule = keyframe->style(); + + if (!is(*keyframe_rule)) + continue; + + // The last keyframe is already fully resolved. + if (first) { + first = false; + continue; + } + + auto next_keyframe = resolved_keyframe; + auto& current_keyframes = *keyframe_set->keyframes_by_key.find(key); + + for (auto it = next_keyframe.resolved_properties.begin(); !it.is_end(); ++it) { + auto& current_property = current_keyframes.resolved_properties[it.index()]; + if (!current_property.has() || it->has()) + continue; + + if (key == 0) + current_property = AnimationKeyFrameSet::ResolvedKeyFrame::UseInitial(); + else + current_property = *it; + } + + resolved_keyframe = current_keyframes; + } + + if (made_a_synthetic_from_keyframe && !first) { + auto next_keyframe = resolved_keyframe; + auto& current_keyframes = *keyframe_set->keyframes_by_key.find(0); + + for (auto it = next_keyframe.resolved_properties.begin(); !it.is_end(); ++it) { + auto& current_property = current_keyframes.resolved_properties[it.index()]; + if (!current_property.has() || it->has()) + continue; + current_property = AnimationKeyFrameSet::ResolvedKeyFrame::UseInitial(); + } + + resolved_keyframe = current_keyframes; + } + + if constexpr (LIBWEB_CSS_DEBUG) { + dbgln("Resolved keyframe set '{}' into {} keyframes:", rule.name(), keyframe_set->keyframes_by_key.size()); + for (auto it = keyframe_set->keyframes_by_key.begin(); it != keyframe_set->keyframes_by_key.end(); ++it) { + size_t props = 0; + for (auto& entry : it->resolved_properties) + props += !entry.has(); + dbgln(" - keyframe {}: {} properties", it.key(), props); + } + } + + rule_cache->rules_by_animation_keyframes.set(rule.name(), move(keyframe_set)); + }); ++style_sheet_index; }); diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.h b/Userland/Libraries/LibWeb/CSS/StyleComputer.h index 83b997f94f..7c9fbf711e 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.h @@ -10,7 +10,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -88,6 +90,11 @@ public: void load_fonts_from_sheet(CSSStyleSheet const&); + struct AnimationKey { + CSS::CSSStyleDeclaration const* source_declaration; + DOM::Element const* element; + }; + private: enum class ComputeStyleMode { Normal, @@ -126,17 +133,29 @@ private: JS::NonnullGCPtr m_document; + struct AnimationKeyFrameSet { + struct ResolvedKeyFrame { + struct UseInitial { }; + Array>, to_underlying(last_property_id) + 1> resolved_properties {}; + }; + RedBlackTree keyframes_by_key; + }; + struct RuleCache { HashMap> rules_by_id; HashMap> rules_by_class; HashMap> rules_by_tag_name; Vector other_rules; + + HashMap> rules_by_animation_keyframes; }; NonnullOwnPtr make_rule_cache_for_cascade_origin(CascadeOrigin); RuleCache const& rule_cache_for_cascade_origin(CascadeOrigin) const; + void ensure_animation_timer() const; + OwnPtr m_author_rule_cache; OwnPtr m_user_agent_rule_cache; @@ -145,6 +164,57 @@ private: Length::FontMetrics m_default_font_metrics; Length::FontMetrics m_root_element_font_metrics; + + constexpr static u64 AnimationKeyFrameKeyScaleFactor = 1000; // 0..100000 + + enum class AnimationStepTransition { + NoTransition, + IdleOrBeforeToActive, + IdleOrBeforeToAfter, + ActiveToBefore, + ActiveToActiveChangingTheIteration, + ActiveToAfter, + AfterToActive, + AfterToBefore, + Cancelled, + }; + enum class AnimationState { + Before, + After, + Idle, + Active, + }; + + struct Animation { + String name; + CSS::Time duration; + CSS::Time delay; + Optional iteration_count; // Infinite if not set. + CSS::AnimationDirection direction; + CSS::AnimationFillMode fill_mode; + WeakPtr owning_element; + + CSS::Percentage progress { 0 }; + CSS::Time remaining_delay { 0, CSS::Time::Type::Ms }; + AnimationState current_state { AnimationState::Before }; + mutable Array, to_underlying(last_property_id) + 1> initial_state {}; + + AnimationStepTransition step(CSS::Time const& time_step); + ErrorOr collect_into(StyleProperties&, RuleCache const&) const; + bool is_done() const; + }; + + mutable HashMap> m_active_animations; + mutable RefPtr m_animation_driver_timer; }; } + +template<> +struct AK::Traits : public AK::GenericTraits { + static unsigned hash(Web::CSS::StyleComputer::AnimationKey const& k) { return pair_int_hash(ptr_hash(k.source_declaration), ptr_hash(k.element)); } + static bool equals(Web::CSS::StyleComputer::AnimationKey const& a, Web::CSS::StyleComputer::AnimationKey const& b) + { + return a.element == b.element && a.source_declaration == b.source_declaration; + } +}; diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp index 98316e1817..332d0e11cf 100644 --- a/Userland/Libraries/LibWeb/Dump.cpp +++ b/Userland/Libraries/LibWeb/Dump.cpp @@ -652,6 +652,9 @@ ErrorOr dump_rule(StringBuilder& builder, CSS::CSSRule const& rule, int in case CSS::CSSRule::Type::Supports: TRY(dump_supports_rule(builder, verify_cast(rule), indent_levels)); break; + case CSS::CSSRule::Type::Keyframe: + case CSS::CSSRule::Type::Keyframes: + break; } return {}; } diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index d072d50821..d47b0ab8b7 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -68,16 +68,12 @@ class BackgroundStyleValue; class BorderRadiusShorthandStyleValue; class BorderRadiusStyleValue; class BorderStyleValue; -class CalculatedStyleValue; -class Clip; -class ColorStyleValue; -class CompositeStyleValue; -class ConicGradientStyleValue; -class ContentStyleValue; class CSSConditionRule; class CSSFontFaceRule; class CSSGroupingRule; class CSSImportRule; +class CSSKeyframeRule; +class CSSKeyframesRule; class CSSMediaRule; class CSSRule; class CSSRuleList; @@ -85,6 +81,12 @@ class CSSStyleDeclaration; class CSSStyleRule; class CSSStyleSheet; class CSSSupportsRule; +class CalculatedStyleValue; +class Clip; +class ColorStyleValue; +class CompositeStyleValue; +class ConicGradientStyleValue; +class ContentStyleValue; class CustomIdentStyleValue; class Display; class DisplayStyleValue; @@ -157,10 +159,10 @@ class TimeOrCalculated; class TimePercentage; class TimeStyleValue; class TransformationStyleValue; +class URLStyleValue; class UnicodeRange; class UnresolvedStyleValue; class UnsetStyleValue; -class URLStyleValue; enum class MediaFeatureID; enum class PropertyID;