mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 00:50:22 +00:00
LibWeb: Add pseudoElement parameter to GetAnimationsOptions
This corresponds to: https://github.com/w3c/csswg-drafts/pull/11050 For now, we don't do anything useful with this parameter, because we don't yet support animating pseudo-elements.
This commit is contained in:
parent
c5c5d97544
commit
b0e58054aa
Notes:
github-actions[bot]
2024-11-09 14:46:39 +00:00
Author: https://github.com/AtkinsSJ Commit: https://github.com/LadybirdBrowser/ladybird/commit/b0e58054aa6 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2227
14 changed files with 141 additions and 78 deletions
|
@ -16,5 +16,7 @@ source_set("Animations") {
|
|||
"DocumentTimeline.h",
|
||||
"KeyframeEffect.cpp",
|
||||
"KeyframeEffect.h",
|
||||
"PseudoElementParsing.cpp",
|
||||
"PseudoElementParsing.h",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <LibWeb/Animations/Animatable.h>
|
||||
#include <LibWeb/Animations/Animation.h>
|
||||
#include <LibWeb/Animations/DocumentTimeline.h>
|
||||
#include <LibWeb/Animations/PseudoElementParsing.h>
|
||||
#include <LibWeb/CSS/CSSTransition.h>
|
||||
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
|
||||
|
@ -58,43 +59,55 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Animation>> Animatable::animate(Optional<JS
|
|||
return animation;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#dom-animatable-getanimations
|
||||
Vector<JS::NonnullGCPtr<Animation>> Animatable::get_animations(GetAnimationsOptions options)
|
||||
// https://drafts.csswg.org/web-animations-1/#dom-animatable-getanimations
|
||||
WebIDL::ExceptionOr<Vector<JS::NonnullGCPtr<Animation>>> Animatable::get_animations(Optional<GetAnimationsOptions> options)
|
||||
{
|
||||
verify_cast<DOM::Element>(*this).document().update_style();
|
||||
return get_animations_internal(options);
|
||||
}
|
||||
|
||||
Vector<JS::NonnullGCPtr<Animation>> Animatable::get_animations_internal(GetAnimationsOptions options)
|
||||
WebIDL::ExceptionOr<Vector<JS::NonnullGCPtr<Animation>>> Animatable::get_animations_internal(Optional<GetAnimationsOptions> options)
|
||||
{
|
||||
// Returns the set of relevant animations for this object, or, if an options parameter is passed with subtree set to
|
||||
// true, returns the set of relevant animations for a subtree for this object.
|
||||
// 1. Let object be the object on which this method was called.
|
||||
|
||||
// The returned list is sorted using the composite order described for the associated animations of effects in
|
||||
// §5.4.2 The effect stack.
|
||||
if (!m_is_sorted_by_composite_order) {
|
||||
quick_sort(m_associated_animations, [](JS::NonnullGCPtr<Animation>& a, JS::NonnullGCPtr<Animation>& b) {
|
||||
auto& a_effect = verify_cast<KeyframeEffect>(*a->effect());
|
||||
auto& b_effect = verify_cast<KeyframeEffect>(*b->effect());
|
||||
return KeyframeEffect::composite_order(a_effect, b_effect) < 0;
|
||||
});
|
||||
m_is_sorted_by_composite_order = true;
|
||||
// 2. Let pseudoElement be the result of pseudo-element parsing applied to pseudoElement of options, or null if options is not passed.
|
||||
// FIXME: Currently only DOM::Element includes Animatable, but that might not always be true.
|
||||
Optional<CSS::Selector::PseudoElement> pseudo_element;
|
||||
if (options.has_value() && options->pseudo_element.has_value()) {
|
||||
auto& realm = static_cast<DOM::Element&>(*this).realm();
|
||||
pseudo_element = TRY(pseudo_element_parsing(realm, options->pseudo_element));
|
||||
}
|
||||
|
||||
// 3. If pseudoElement is not null, then let target be the pseudo-element identified by pseudoElement with object as the originating element.
|
||||
// Otherwise, let target be object.
|
||||
// FIXME: We can't refer to pseudo-elements directly, and they also can't be animated yet.
|
||||
(void)pseudo_element;
|
||||
JS::NonnullGCPtr target { *static_cast<DOM::Element*>(this) };
|
||||
|
||||
// 4. If options is passed with subtree set to true, then return the set of relevant animations for a subtree of target.
|
||||
// Otherwise, return the set of relevant animations for target.
|
||||
Vector<JS::NonnullGCPtr<Animation>> relevant_animations;
|
||||
for (auto const& animation : m_associated_animations) {
|
||||
if (animation->is_relevant())
|
||||
relevant_animations.append(*animation);
|
||||
}
|
||||
|
||||
if (options.subtree) {
|
||||
JS::NonnullGCPtr target { *static_cast<DOM::Element*>(this) };
|
||||
target->for_each_child_of_type<DOM::Element>([&](auto& child) {
|
||||
relevant_animations.extend(child.get_animations(options));
|
||||
if (options.has_value() && options->subtree) {
|
||||
Optional<WebIDL::Exception> exception;
|
||||
TRY(target->for_each_child_of_type_fallible<DOM::Element>([&](auto& child) -> WebIDL::ExceptionOr<IterationDecision> {
|
||||
relevant_animations.extend(TRY(child.get_animations(options)));
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
// The returned list is sorted using the composite order described for the associated animations of effects in
|
||||
// §5.4.2 The effect stack.
|
||||
quick_sort(relevant_animations, [](JS::NonnullGCPtr<Animation>& a, JS::NonnullGCPtr<Animation>& b) {
|
||||
auto& a_effect = verify_cast<KeyframeEffect>(*a->effect());
|
||||
auto& b_effect = verify_cast<KeyframeEffect>(*b->effect());
|
||||
return KeyframeEffect::composite_order(a_effect, b_effect) < 0;
|
||||
});
|
||||
|
||||
return relevant_animations;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,18 +17,19 @@ class CSSTransition;
|
|||
|
||||
namespace Web::Animations {
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#dictdef-keyframeanimationoptions
|
||||
// https://drafts.csswg.org/web-animations-1/#dictdef-keyframeanimationoptions
|
||||
struct KeyframeAnimationOptions : public KeyframeEffectOptions {
|
||||
FlyString id { ""_fly_string };
|
||||
Optional<JS::GCPtr<AnimationTimeline>> timeline;
|
||||
};
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#dictdef-getanimationsoptions
|
||||
// https://drafts.csswg.org/web-animations-1/#dictdef-getanimationsoptions
|
||||
struct GetAnimationsOptions {
|
||||
bool subtree { false };
|
||||
Optional<String> pseudo_element {};
|
||||
};
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#animatable
|
||||
// https://drafts.csswg.org/web-animations-1/#animatable
|
||||
class Animatable {
|
||||
public:
|
||||
struct TransitionAttributes {
|
||||
|
@ -40,8 +41,8 @@ public:
|
|||
virtual ~Animatable() = default;
|
||||
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<Animation>> animate(Optional<JS::Handle<JS::Object>> keyframes, Variant<Empty, double, KeyframeAnimationOptions> options = {});
|
||||
Vector<JS::NonnullGCPtr<Animation>> get_animations(GetAnimationsOptions options = {});
|
||||
Vector<JS::NonnullGCPtr<Animation>> get_animations_internal(GetAnimationsOptions options = {});
|
||||
WebIDL::ExceptionOr<Vector<JS::NonnullGCPtr<Animation>>> get_animations(Optional<GetAnimationsOptions> options = {});
|
||||
WebIDL::ExceptionOr<Vector<JS::NonnullGCPtr<Animation>>> get_animations_internal(Optional<GetAnimationsOptions> options = {});
|
||||
|
||||
void associate_with_animation(JS::NonnullGCPtr<Animation>);
|
||||
void disassociate_with_animation(JS::NonnullGCPtr<Animation>);
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
#import <Animations/Animation.idl>
|
||||
#import <Animations/KeyframeEffect.idl>
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#the-animatable-interface-mixin
|
||||
// https://drafts.csswg.org/web-animations-1/#the-animatable-interface-mixin
|
||||
interface mixin Animatable {
|
||||
Animation animate(object? keyframes, optional (unrestricted double or KeyframeAnimationOptions) options = {});
|
||||
sequence<Animation> getAnimations(optional GetAnimationsOptions options = {});
|
||||
};
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#dictdef-keyframeanimationoptions
|
||||
// https://drafts.csswg.org/web-animations-1/#dictdef-keyframeanimationoptions
|
||||
dictionary KeyframeAnimationOptions : KeyframeEffectOptions {
|
||||
DOMString id = "";
|
||||
AnimationTimeline? timeline;
|
||||
};
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#dictdef-getanimationsoptions
|
||||
// https://drafts.csswg.org/web-animations-1/#dictdef-getanimationsoptions
|
||||
dictionary GetAnimationsOptions {
|
||||
boolean subtree = false;
|
||||
CSSOMString? pseudoElement = null;
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <LibJS/Runtime/Iterator.h>
|
||||
#include <LibWeb/Animations/Animation.h>
|
||||
#include <LibWeb/Animations/KeyframeEffect.h>
|
||||
#include <LibWeb/Animations/PseudoElementParsing.h>
|
||||
#include <LibWeb/Bindings/KeyframeEffectPrototype.h>
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
|
@ -783,42 +784,13 @@ Optional<String> KeyframeEffect::pseudo_element() const
|
|||
return MUST(String::formatted("::{}", m_target_pseudo_selector->name()));
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#dom-keyframeeffect-pseudoelement
|
||||
WebIDL::ExceptionOr<void> KeyframeEffect::set_pseudo_element(Optional<String> pseudo_element)
|
||||
// https://drafts.csswg.org/web-animations-1/#dom-keyframeeffect-pseudoelement
|
||||
WebIDL::ExceptionOr<void> KeyframeEffect::set_pseudo_element(Optional<String> value)
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
|
||||
// On setting, sets the target pseudo-selector of the animation effect to the provided value after applying the
|
||||
// following exceptions:
|
||||
|
||||
// FIXME:
|
||||
// - If one of the legacy Selectors Level 2 single-colon selectors (':before', ':after', ':first-letter', or
|
||||
// ':first-line') is specified, the target pseudo-selector must be set to the equivalent two-colon selector
|
||||
// (e.g. '::before').
|
||||
if (pseudo_element.has_value()) {
|
||||
auto value = pseudo_element.value();
|
||||
|
||||
if (value == ":before" || value == ":after" || value == ":first-letter" || value == ":first-line") {
|
||||
m_target_pseudo_selector = CSS::Selector::PseudoElement::from_string(MUST(value.substring_from_byte_offset(1)));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// - If the provided value is not null and is an invalid <pseudo-element-selector>, the user agent must throw a
|
||||
// DOMException with error name SyntaxError and leave the target pseudo-selector of this animation effect
|
||||
// unchanged.
|
||||
if (pseudo_element.has_value()) {
|
||||
if (pseudo_element->starts_with_bytes("::"sv)) {
|
||||
if (auto value = CSS::Selector::PseudoElement::from_string(MUST(pseudo_element->substring_from_byte_offset(2))); value.has_value()) {
|
||||
m_target_pseudo_selector = value;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return WebIDL::SyntaxError::create(realm, MUST(String::formatted("Invalid pseudo-element selector: \"{}\"", pseudo_element.value())));
|
||||
}
|
||||
|
||||
m_target_pseudo_selector = {};
|
||||
// On setting, sets the target pseudo-selector of the animation effect to the result of
|
||||
// pseudo-element parsing on the provided value, defined as the following:
|
||||
// NOTE: The actual definition is in pseudo_element_parsing().
|
||||
m_target_pseudo_selector = TRY(pseudo_element_parsing(realm(), value));
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "PseudoElementParsing.h"
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
|
||||
namespace Web::Animations {
|
||||
|
||||
// https://drafts.csswg.org/web-animations-1/#dom-keyframeeffect-pseudo-element-parsing
|
||||
WebIDL::ExceptionOr<Optional<CSS::Selector::PseudoElement>> pseudo_element_parsing(JS::Realm& realm, Optional<String> const& value)
|
||||
{
|
||||
// 1. Given the value value, perform the following steps:
|
||||
|
||||
// 2. If value is not null and is an invalid <pseudo-element-selector>,
|
||||
Optional<CSS::Selector::PseudoElement> pseudo_element;
|
||||
if (value.has_value()) {
|
||||
pseudo_element = parse_pseudo_element_selector(CSS::Parser::ParsingContext { realm }, *value);
|
||||
if (!pseudo_element.has_value()) {
|
||||
// 1. Throw a DOMException with error name "SyntaxError".
|
||||
// 2. Abort.
|
||||
return WebIDL::SyntaxError::create(realm, MUST(String::formatted("Invalid pseudo-element selector: \"{}\"", value.value())));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. If value is one of the legacy Selectors Level 2 single-colon selectors (':before', ':after', ':first-letter', or ':first-line'),
|
||||
// then return the equivalent two-colon selector (e.g. '::before').
|
||||
if (value.has_value() && value->is_one_of(":before", ":after", ":first-letter", ":first-line")) {
|
||||
return CSS::Selector::PseudoElement::from_string(MUST(value->substring_from_byte_offset(1)));
|
||||
}
|
||||
|
||||
// 4. Otherwise, return value.
|
||||
return pseudo_element;
|
||||
}
|
||||
|
||||
}
|
19
Userland/Libraries/LibWeb/Animations/PseudoElementParsing.h
Normal file
19
Userland/Libraries/LibWeb/Animations/PseudoElementParsing.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibWeb/CSS/Selector.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::Animations {
|
||||
|
||||
// https://drafts.csswg.org/web-animations-1/#dom-keyframeeffect-pseudo-element-parsing
|
||||
WebIDL::ExceptionOr<Optional<CSS::Selector::PseudoElement>> pseudo_element_parsing(JS::Realm&, Optional<String> const&);
|
||||
|
||||
}
|
|
@ -10,6 +10,7 @@ set(SOURCES
|
|||
Animations/AnimationTimeline.cpp
|
||||
Animations/DocumentTimeline.cpp
|
||||
Animations/KeyframeEffect.cpp
|
||||
Animations/PseudoElementParsing.cpp
|
||||
ARIA/AriaData.cpp
|
||||
ARIA/ARIAMixin.cpp
|
||||
ARIA/Roles.cpp
|
||||
|
|
|
@ -1579,12 +1579,16 @@ void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element
|
|||
}
|
||||
}
|
||||
|
||||
auto animations = element.get_animations_internal({ .subtree = false });
|
||||
for (auto& animation : animations) {
|
||||
if (auto effect = animation->effect(); effect && effect->is_keyframe_effect()) {
|
||||
auto& keyframe_effect = *static_cast<Animations::KeyframeEffect*>(effect.ptr());
|
||||
if (keyframe_effect.pseudo_element_type() == pseudo_element)
|
||||
collect_animation_into(element, pseudo_element, keyframe_effect, style);
|
||||
auto animations = element.get_animations_internal(Animations::GetAnimationsOptions { .subtree = false });
|
||||
if (animations.is_exception()) {
|
||||
dbgln("Error getting animations for element {}", element.debug_description());
|
||||
} else {
|
||||
for (auto& animation : animations.value()) {
|
||||
if (auto effect = animation->effect(); effect && effect->is_keyframe_effect()) {
|
||||
auto& keyframe_effect = *static_cast<Animations::KeyframeEffect*>(effect.ptr());
|
||||
if (keyframe_effect.pseudo_element_type() == pseudo_element)
|
||||
collect_animation_into(element, pseudo_element, keyframe_effect, style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4695,13 +4695,13 @@ void Document::remove_replaced_animations()
|
|||
}
|
||||
}
|
||||
|
||||
Vector<JS::NonnullGCPtr<Animations::Animation>> Document::get_animations()
|
||||
WebIDL::ExceptionOr<Vector<JS::NonnullGCPtr<Animations::Animation>>> Document::get_animations()
|
||||
{
|
||||
Vector<JS::NonnullGCPtr<Animations::Animation>> relevant_animations;
|
||||
for_each_child_of_type<Element>([&](auto& child) {
|
||||
relevant_animations.extend(child.get_animations({ .subtree = true }));
|
||||
TRY(for_each_child_of_type_fallible<Element>([&](auto& child) -> WebIDL::ExceptionOr<IterationDecision> {
|
||||
relevant_animations.extend(TRY(child.get_animations(Animations::GetAnimationsOptions { .subtree = true })));
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}));
|
||||
return relevant_animations;
|
||||
}
|
||||
|
||||
|
|
|
@ -636,7 +636,7 @@ public:
|
|||
void update_animations_and_send_events(Optional<double> const& timestamp);
|
||||
void remove_replaced_animations();
|
||||
|
||||
Vector<JS::NonnullGCPtr<Animations::Animation>> get_animations();
|
||||
WebIDL::ExceptionOr<Vector<JS::NonnullGCPtr<Animations::Animation>>> get_animations();
|
||||
|
||||
bool ready_to_run_scripts() const { return m_ready_to_run_scripts; }
|
||||
void set_ready_to_run_scripts() { m_ready_to_run_scripts = true; }
|
||||
|
|
|
@ -637,6 +637,18 @@ public:
|
|||
return const_cast<Node*>(this)->template for_each_child_of_type<U>(move(callback));
|
||||
}
|
||||
|
||||
template<typename U, typename Callback>
|
||||
WebIDL::ExceptionOr<void> for_each_child_of_type_fallible(Callback callback)
|
||||
{
|
||||
for (auto* node = first_child(); node; node = node->next_sibling()) {
|
||||
if (is<U>(node)) {
|
||||
if (TRY(callback(verify_cast<U>(*node))) == IterationDecision::Break)
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
U const* next_sibling_of_type() const
|
||||
{
|
||||
|
|
|
@ -174,13 +174,13 @@ void ShadowRoot::for_each_css_style_sheet(Function<void(CSS::CSSStyleSheet&)>&&
|
|||
}
|
||||
}
|
||||
|
||||
Vector<JS::NonnullGCPtr<Animations::Animation>> ShadowRoot::get_animations()
|
||||
WebIDL::ExceptionOr<Vector<JS::NonnullGCPtr<Animations::Animation>>> ShadowRoot::get_animations()
|
||||
{
|
||||
Vector<JS::NonnullGCPtr<Animations::Animation>> relevant_animations;
|
||||
for_each_child_of_type<Element>([&](auto& child) {
|
||||
relevant_animations.extend(child.get_animations({ .subtree = true }));
|
||||
TRY(for_each_child_of_type_fallible<Element>([&](auto& child) -> WebIDL::ExceptionOr<IterationDecision> {
|
||||
relevant_animations.extend(TRY(child.get_animations(Animations::GetAnimationsOptions { .subtree = true })));
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}));
|
||||
return relevant_animations;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ public:
|
|||
|
||||
void for_each_css_style_sheet(Function<void(CSS::CSSStyleSheet&)>&& callback) const;
|
||||
|
||||
Vector<JS::NonnullGCPtr<Animations::Animation>> get_animations();
|
||||
WebIDL::ExceptionOr<Vector<JS::NonnullGCPtr<Animations::Animation>>> get_animations();
|
||||
|
||||
virtual void finalize() override;
|
||||
|
||||
|
|
Loading…
Reference in a new issue