mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 21:17:44 +00:00
LibWeb: Partially implement the "process a keyframes argument" procedure
Keyframes can be given in two separate forms: - As an array of separate keyframe objects, where the keys of each keyframe represent CSS properties, and their values represents the values that those CSS properties should take e.x.: [{ color: 'red', offset: 0.3 }, { color: 'blue', offset: 0.7 }] - As a single monolithic keyframe object, where the keys of each keyframe represent CSS properties, and their values are arrays of values, where each index k represents the value of the given property at the k'th frame. e.x.: { color: ['red', 'blue'], offset: [0.3, 0.7] } This commit only implements the first option, as it is much simpler. See the next commit for the implementation of the second option.
This commit is contained in:
parent
9f404ed9c1
commit
7d69fa0ccf
1 changed files with 146 additions and 1 deletions
|
@ -7,6 +7,7 @@
|
|||
#include <AK/QuickSort.h>
|
||||
#include <LibJS/Runtime/Iterator.h>
|
||||
#include <LibWeb/Animations/KeyframeEffect.h>
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::Animations {
|
||||
|
@ -196,7 +197,7 @@ static WebIDL::ExceptionOr<KeyframeType<AL>> process_a_keyframe_like_object(JS::
|
|||
}
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#compute-missing-keyframe-offsets
|
||||
[[maybe_unused]] static void compute_missing_keyframe_offsets(Vector<BaseKeyframe>& keyframes)
|
||||
static void compute_missing_keyframe_offsets(Vector<BaseKeyframe>& keyframes)
|
||||
{
|
||||
// 1. For each keyframe, in keyframes, let the computed keyframe offset of the keyframe be equal to its keyframe
|
||||
// offset value.
|
||||
|
@ -258,6 +259,150 @@ static WebIDL::ExceptionOr<KeyframeType<AL>> process_a_keyframe_like_object(JS::
|
|||
}
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#loosely-sorted-by-offset
|
||||
static bool is_loosely_sorted_by_offset(Vector<BaseKeyframe> const& keyframes)
|
||||
{
|
||||
// The list of keyframes for a keyframe effect must be loosely sorted by offset which means that for each keyframe
|
||||
// in the list that has a keyframe offset that is not null, the offset is greater than or equal to the offset of the
|
||||
// previous keyframe in the list with a keyframe offset that is not null, if any.
|
||||
|
||||
Optional<double> last_offset;
|
||||
for (auto const& keyframe : keyframes) {
|
||||
if (!keyframe.offset.has_value())
|
||||
continue;
|
||||
|
||||
if (last_offset.has_value() && keyframe.offset.value() < last_offset.value())
|
||||
return false;
|
||||
|
||||
last_offset = keyframe.offset;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#process-a-keyframes-argument
|
||||
[[maybe_unused]] static WebIDL::ExceptionOr<Vector<BaseKeyframe>> process_a_keyframes_argument(JS::Realm& realm, JS::GCPtr<JS::Object> object)
|
||||
{
|
||||
auto& vm = realm.vm();
|
||||
|
||||
auto parse_easing_string = [&](auto& value) -> RefPtr<CSS::StyleValue const> {
|
||||
auto maybe_parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext(realm), value);
|
||||
if (maybe_parser.is_error())
|
||||
return {};
|
||||
|
||||
if (auto style_value = maybe_parser.release_value().parse_as_css_value(CSS::PropertyID::AnimationTimingFunction)) {
|
||||
if (style_value->is_easing())
|
||||
return style_value;
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
// 1. If object is null, return an empty sequence of keyframes.
|
||||
if (!object)
|
||||
return Vector<BaseKeyframe> {};
|
||||
|
||||
// 2. Let processed keyframes be an empty sequence of keyframes.
|
||||
Vector<BaseKeyframe> processed_keyframes;
|
||||
|
||||
// 3. Let method be the result of GetMethod(object, @@iterator).
|
||||
// 4. Check the completion record of method.
|
||||
auto method = TRY(JS::Value(object).get_method(vm, vm.well_known_symbol_iterator()));
|
||||
|
||||
// 5. Perform the steps corresponding to the first matching condition from below,
|
||||
|
||||
// -> If method is not undefined,
|
||||
if (method) {
|
||||
// 1. Let iter be GetIterator(object, method).
|
||||
// 2. Check the completion record of iter.
|
||||
auto iter = TRY(JS::get_iterator_from_method(vm, object, *method));
|
||||
|
||||
// 3. Repeat:
|
||||
while (true) {
|
||||
// 1. Let next be IteratorStep(iter).
|
||||
// 2. Check the completion record of next.
|
||||
auto next = TRY(JS::iterator_step(vm, iter));
|
||||
|
||||
// 3. If next is false abort this loop.
|
||||
if (!next)
|
||||
break;
|
||||
|
||||
// 4. Let nextItem be IteratorValue(next).
|
||||
// 5. Check the completion record of nextItem.
|
||||
auto next_item = TRY(JS::iterator_value(vm, *next));
|
||||
|
||||
// 6. If Type(nextItem) is not Undefined, Null or Object, then throw a TypeError and abort these steps.
|
||||
if (!next_item.is_nullish() && !next_item.is_object())
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOrNull, next_item.to_string_without_side_effects());
|
||||
|
||||
// 7. Append to processed keyframes the result of running the procedure to process a keyframe-like object
|
||||
// passing nextItem as the keyframe input and with the allow lists flag set to false.
|
||||
processed_keyframes.append(TRY(process_a_keyframe_like_object<AllowLists::No>(realm, next_item.as_object())));
|
||||
}
|
||||
}
|
||||
// -> Otherwise,
|
||||
else {
|
||||
// FIXME: Support processing a single keyframe-like object
|
||||
TODO();
|
||||
}
|
||||
|
||||
// 6. If processed keyframes is not loosely sorted by offset, throw a TypeError and abort these steps.
|
||||
if (!is_loosely_sorted_by_offset(processed_keyframes))
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Keyframes are not in ascending order based on offset"sv };
|
||||
|
||||
// 7. If there exist any keyframe in processed keyframes whose keyframe offset is non-null and less than zero or
|
||||
// greater than one, throw a TypeError and abort these steps.
|
||||
for (size_t i = 0; i < processed_keyframes.size(); i++) {
|
||||
auto const& keyframe = processed_keyframes[i];
|
||||
if (!keyframe.offset.has_value())
|
||||
continue;
|
||||
|
||||
auto offset = keyframe.offset.value();
|
||||
if (offset < 0.0 || offset > 1.0)
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Keyframe {} has invalid offset value {}"sv, i, offset)) };
|
||||
}
|
||||
|
||||
// 8. For each frame in processed keyframes, perform the following steps:
|
||||
for (auto& keyframe : processed_keyframes) {
|
||||
// 1. For each property-value pair in frame, parse the property value using the syntax specified for that
|
||||
// property.
|
||||
//
|
||||
// If the property value is invalid according to the syntax for the property, discard the property-value pair.
|
||||
// User agents that provide support for diagnosing errors in content SHOULD produce an appropriate warning
|
||||
// highlight
|
||||
BaseKeyframe::ParsedProperties parsed_properties;
|
||||
for (auto& [property_string, value_string] : keyframe.unparsed_properties()) {
|
||||
if (auto property = CSS::property_id_from_camel_case_string(property_string); property.has_value()) {
|
||||
auto maybe_parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext(realm), value_string);
|
||||
if (maybe_parser.is_error())
|
||||
continue;
|
||||
|
||||
if (auto style_value = maybe_parser.release_value().parse_as_css_value(*property))
|
||||
parsed_properties.set(*property, *style_value);
|
||||
}
|
||||
}
|
||||
keyframe.properties.set(move(parsed_properties));
|
||||
|
||||
// 2. Let the timing function of frame be the result of parsing the "easing" property on frame using the CSS
|
||||
// syntax defined for the easing member of the EffectTiming dictionary.
|
||||
//
|
||||
// If parsing the "easing" property fails, throw a TypeError and abort this procedure.
|
||||
auto easing_string = keyframe.easing.get<String>();
|
||||
auto easing_value = parse_easing_string(easing_string);
|
||||
|
||||
if (!easing_value)
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Invalid animation easing value: \"{}\"", easing_string)) };
|
||||
|
||||
keyframe.easing.set(NonnullRefPtr<CSS::StyleValue const> { *easing_value });
|
||||
}
|
||||
|
||||
// FIXME:
|
||||
// 9. Parse each of the values in unused easings using the CSS syntax defined for easing member of the EffectTiming
|
||||
// interface, and if any of the values fail to parse, throw a TypeError and abort this procedure.
|
||||
|
||||
return processed_keyframes;
|
||||
}
|
||||
|
||||
JS::NonnullGCPtr<KeyframeEffect> KeyframeEffect::create(JS::Realm& realm)
|
||||
{
|
||||
return realm.heap().allocate<KeyframeEffect>(realm, realm);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue