Changeset 233729 in webkit


Ignore:
Timestamp:
Jul 11, 2018 8:54:00 AM (6 years ago)
Author:
graouts@webkit.org
Message:

[Web Animations] Make WPT test at interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html pass reliably
https://bugs.webkit.org/show_bug.cgi?id=186501
<rdar://problem/41000224>

Reviewed by Dean Jackson.

LayoutTests/imported/w3c:

Mark 2 new WPT progressions.

  • web-platform-tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001-expected.txt:

Source/WebCore:

There were two remaining assertions that we were failing in this WPT test file, both related to processing iterable keyframes.
The first one was failing because didn't correctly propagate the TypeError exception in the forEachInIterable() callback. The
second one was failing because we didn't use the "process a keyframe-like object" procedure when processing iterable keyframes
and, as such, we didn't correctly sort property alphabetically before reading their values.

To fix this second issue, we make processIterableKeyframes() use processKeyframeLikeObject(). To do so, we update processKeyframeLikeObject()
to accept a new boolean flag to match the "allow lists" flag from the specification. We also ensure we sort the properties *before*
reading from them which we didn't use to do previously.

  • animation/KeyframeEffectReadOnly.cpp:

(WebCore::processKeyframeLikeObject):
(WebCore::processIterableKeyframes):
(WebCore::processPropertyIndexedKeyframes):

  • animation/KeyframeEffectReadOnly.h:
  • animation/KeyframeEffectReadOnly.idl:
Location:
trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/imported/w3c/ChangeLog

    r233720 r233729  
     12018-07-10  Antoine Quint  <graouts@apple.com>
     2
     3        [Web Animations] Make WPT test at interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html pass reliably
     4        https://bugs.webkit.org/show_bug.cgi?id=186501
     5        <rdar://problem/41000224>
     6
     7        Reviewed by Dean Jackson.
     8
     9        Mark 2 new WPT progressions.
     10
     11        * web-platform-tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001-expected.txt:
     12
    1132018-07-10  Youenn Fablet  <youenn@apple.com>
    214
  • trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001-expected.txt

    r233632 r233729  
    4343PASS Keyframes are read from a custom iterator with multiple properties specified
    4444PASS Keyframes are read from a custom iterator with where an offset is specified
    45 FAIL Reading from a custom iterator that returns a non-object keyframe should throw assert_throws: function "() => {
    46     new KeyframeEffect(null, createIterable([
    47       { done: false, value: { left: '100px' } },
    48       { done: false, value: 1234 },
    49       { done: false, value: { left: '200px' } },
    50       { done: true },
    51     ]));
    52   }" did not throw
     45PASS Reading from a custom iterator that returns a non-object keyframe should throw
    5346PASS A list of values returned from a custom iterator should be ignored
    5447PASS Only enumerable properties on keyframes are read
     
    5649PASS Only enumerable properties on property-indexed keyframes are read
    5750PASS Only properties defined directly on property-indexed keyframes are read
    58 FAIL Properties are read in ascending order by Unicode codepoint assert_array_equals: property access order property 0, expected "composite" but got "marginLeft"
     51PASS Properties are read in ascending order by Unicode codepoint
    5952
  • trunk/Source/WebCore/ChangeLog

    r233728 r233729  
     12018-07-10  Antoine Quint  <graouts@apple.com>
     2
     3        [Web Animations] Make WPT test at interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html pass reliably
     4        https://bugs.webkit.org/show_bug.cgi?id=186501
     5        <rdar://problem/41000224>
     6
     7        Reviewed by Dean Jackson.
     8
     9        There were two remaining assertions that we were failing in this WPT test file, both related to processing iterable keyframes.
     10        The first one was failing because didn't correctly propagate the TypeError exception in the forEachInIterable() callback. The
     11        second one was failing because we didn't use the "process a keyframe-like object" procedure when processing iterable keyframes
     12        and, as such, we didn't correctly sort property alphabetically before reading their values.
     13
     14        To fix this second issue, we make processIterableKeyframes() use processKeyframeLikeObject(). To do so, we update processKeyframeLikeObject()
     15        to accept a new boolean flag to match the "allow lists" flag from the specification. We also ensure we sort the properties *before*
     16        reading from them which we didn't use to do previously.
     17
     18        * animation/KeyframeEffectReadOnly.cpp:
     19        (WebCore::processKeyframeLikeObject):
     20        (WebCore::processIterableKeyframes):
     21        (WebCore::processPropertyIndexedKeyframes):
     22        * animation/KeyframeEffectReadOnly.h:
     23        * animation/KeyframeEffectReadOnly.idl:
     24
    1252018-07-11  Zalan Bujtas  <zalan@apple.com>
    226
  • trunk/Source/WebCore/animation/KeyframeEffectReadOnly.cpp

    r233676 r233729  
    5252#include "TranslateTransformOperation.h"
    5353#include "WillChangeData.h"
     54#include <JavaScriptCore/Exception.h>
    5455#include <wtf/UUID.h>
    5556
     
    155156}
    156157
    157 static inline ExceptionOr<void> processIterableKeyframes(ExecState& state, Strong<JSObject>&& keyframesInput, JSValue method, Vector<KeyframeEffectReadOnly::ParsedKeyframe>& parsedKeyframes)
    158 {
     158static inline ExceptionOr<KeyframeEffectReadOnly::KeyframeLikeObject> processKeyframeLikeObject(ExecState& state, Strong<JSObject>&& keyframesInput, bool allowLists)
     159{
     160    // https://drafts.csswg.org/web-animations-1/#process-a-keyframe-like-object
     161
    159162    VM& vm = state.vm();
    160163    auto scope = DECLARE_THROW_SCOPE(vm);
    161164
    162     // 1. Let iter be GetIterator(object, method).
    163     forEachInIterable(state, keyframesInput.get(), method, [&parsedKeyframes](VM& vm, ExecState& state, JSValue nextValue) -> ExceptionOr<void> {
    164         if (!nextValue || !nextValue.isObject())
    165             return Exception { TypeError };
    166 
    167         auto scope = DECLARE_THROW_SCOPE(vm);
    168 
    169         JSObject* keyframe = nextValue.toObject(&state);
    170         PropertyNameArray ownPropertyNames(&vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
    171         JSObject::getOwnPropertyNames(keyframe, &state, ownPropertyNames, EnumerationMode());
    172         size_t numberOfProperties = ownPropertyNames.size();
    173 
    174         KeyframeEffectReadOnly::ParsedKeyframe keyframeOutput;
    175 
    176         String easing("linear");
    177         std::optional<double> offset;
    178         std::optional<CompositeOperation> composite;
    179 
    180         for (size_t j = 0; j < numberOfProperties; ++j) {
    181             auto ownPropertyName = ownPropertyNames[j];
    182             if (ownPropertyName == "easing")
    183                 easing = convert<IDLDOMString>(state, keyframe->get(&state, ownPropertyName));
    184             else if (ownPropertyName == "offset")
    185                 offset = convert<IDLNullable<IDLDouble>>(state, keyframe->get(&state, ownPropertyName));
    186             else if (ownPropertyName == "composite")
    187                 composite = convert<IDLNullable<IDLEnumeration<CompositeOperation>>>(state, keyframe->get(&state, ownPropertyName));
    188             else {
    189                 auto cssPropertyId = IDLAttributeNameToAnimationPropertyName(ownPropertyName.string());
    190                 if (CSSPropertyAnimation::isPropertyAnimatable(cssPropertyId)) {
    191                     auto stringValue = convert<IDLDOMString>(state, keyframe->get(&state, ownPropertyName));
    192                     if (keyframeOutput.style->setProperty(cssPropertyId, stringValue))
    193                         keyframeOutput.unparsedStyle.set(cssPropertyId, stringValue);
    194                 }
    195             }
    196             RETURN_IF_EXCEPTION(scope, Exception { TypeError });
    197         }
    198 
    199         keyframeOutput.easing = easing;
    200         keyframeOutput.offset = offset;
    201         keyframeOutput.composite = composite;
    202 
    203         parsedKeyframes.append(WTFMove(keyframeOutput));
    204 
    205         return { };
    206     });
    207     RETURN_IF_EXCEPTION(scope, Exception { TypeError });
    208 
    209     return { };
    210 }
    211 
    212 static inline ExceptionOr<KeyframeEffectReadOnly::KeyframeLikeObject> processKeyframeLikeObject(ExecState& state, Strong<JSObject>&& keyframesInput)
    213 {
    214     // https://drafts.csswg.org/web-animations-1/#process-a-keyframe-like-object
    215 
    216     VM& vm = state.vm();
    217     auto scope = DECLARE_THROW_SCOPE(vm);
    218 
    219165    // 1. Run the procedure to convert an ECMAScript value to a dictionary type [WEBIDL] with keyframe input as the ECMAScript value as follows:
    220166    //
     167    //    If allow lists is true, use the following dictionary type:
     168    //
    221169    //    dictionary BasePropertyIndexedKeyframe {
    222     //        (double? or sequence<double?>)                       offset = [];
    223     //        (DOMString or sequence<DOMString>)                   easing = [];
     170    //        (double? or sequence<double?>)                         offset = [];
     171    //        (DOMString or sequence<DOMString>)                     easing = [];
    224172    //        (CompositeOperation? or sequence<CompositeOperation?>) composite = [];
    225173    //    };
    226174    //
     175    //    Otherwise, use the following dictionary type:
     176    //
     177    //    dictionary BaseKeyframe {
     178    //        double?             offset = null;
     179    //        DOMString           easing = "linear";
     180    //        CompositeOperation? composite = null;
     181    //    };
     182    //
    227183    //    Store the result of this procedure as keyframe output.
    228     auto baseProperties = convert<IDLDictionary<KeyframeEffectReadOnly::BasePropertyIndexedKeyframe>>(state, keyframesInput.get());
     184    KeyframeEffect::BasePropertyIndexedKeyframe baseProperties;
     185    if (allowLists)
     186        baseProperties = convert<IDLDictionary<KeyframeEffectReadOnly::BasePropertyIndexedKeyframe>>(state, keyframesInput.get());
     187    else {
     188        auto baseKeyframe = convert<IDLDictionary<KeyframeEffectReadOnly::BaseKeyframe>>(state, keyframesInput.get());
     189        if (baseKeyframe.offset)
     190            baseProperties.offset = baseKeyframe.offset.value();
     191        else
     192            baseProperties.offset = nullptr;
     193        baseProperties.easing = baseKeyframe.easing;
     194        if (baseKeyframe.composite)
     195            baseProperties.composite = baseKeyframe.composite.value();
     196        else
     197            baseProperties.composite = nullptr;
     198    }
    229199    RETURN_IF_EXCEPTION(scope, Exception { TypeError });
    230200
     
    245215    // 4. Make up a new list animation properties that consists of all of the properties that are in both input properties and animatable
    246216    //    properties, or which are in input properties and conform to the <custom-property-name> production.
    247 
    248     // 5. Sort animation properties in ascending order by the Unicode codepoints that define each property name.
    249     //    We only actually perform this after step 6.
    250 
    251     // 6. For each property name in animation properties,
     217    Vector<JSC::Identifier> animationProperties;
    252218    size_t numberOfProperties = inputProperties.size();
    253219    for (size_t i = 0; i < numberOfProperties; ++i) {
    254         auto cssPropertyID = IDLAttributeNameToAnimationPropertyName(inputProperties[i].string());
    255         if (!CSSPropertyAnimation::isPropertyAnimatable(cssPropertyID))
    256             continue;
    257 
     220        if (CSSPropertyAnimation::isPropertyAnimatable(IDLAttributeNameToAnimationPropertyName(inputProperties[i].string())))
     221            animationProperties.append(inputProperties[i]);
     222    }
     223
     224    // 5. Sort animation properties in ascending order by the Unicode codepoints that define each property name.
     225    std::sort(animationProperties.begin(), animationProperties.end(), [](auto& lhs, auto& rhs) {
     226        return lhs.string().utf8() < rhs.string().utf8();
     227    });
     228
     229    // 6. For each property name in animation properties,
     230    size_t numberOfAnimationProperties = animationProperties.size();
     231    for (size_t i = 0; i < numberOfAnimationProperties; ++i) {
    258232        // 1. Let raw value be the result of calling the [[Get]] internal method on keyframe input, with property name as the property
    259233        //    key and keyframe input as the receiver.
    260         auto rawValue = keyframesInput->get(&state, inputProperties[i]);
     234        auto rawValue = keyframesInput->get(&state, animationProperties[i]);
    261235
    262236        // 2. Check the completion record of raw value.
     
    265239        // 3. Convert raw value to a DOMString or sequence of DOMStrings property values as follows:
    266240        Vector<String> propertyValues;
    267         // Let property values be the result of converting raw value to IDL type (DOMString or sequence<DOMString>)
    268         // using the procedures defined for converting an ECMAScript value to an IDL value [WEBIDL].
    269         // If property values is a single DOMString, replace property values with a sequence of DOMStrings with the original value of property
    270         // Values as the only element.
    271         if (rawValue.isString())
    272             propertyValues = { rawValue.toWTFString(&state) };
    273         else if (rawValue.isObject())
    274             propertyValues = convert<IDLSequence<IDLDOMString>>(state, rawValue);
     241        if (allowLists) {
     242            // If allow lists is true,
     243            // Let property values be the result of converting raw value to IDL type (DOMString or sequence<DOMString>)
     244            // using the procedures defined for converting an ECMAScript value to an IDL value [WEBIDL].
     245            // If property values is a single DOMString, replace property values with a sequence of DOMStrings with the original value of property
     246            // Values as the only element.
     247            if (rawValue.isString())
     248                propertyValues = { rawValue.toWTFString(&state) };
     249            else if (rawValue.isObject())
     250                propertyValues = convert<IDLSequence<IDLDOMString>>(state, rawValue);
     251        } else {
     252            // Otherwise,
     253            // Let property values be the result of converting raw value to a DOMString using the procedure for converting an ECMAScript value to a DOMString.
     254            propertyValues = { convert<IDLDOMString>(state, rawValue) };
     255        }
    275256        RETURN_IF_EXCEPTION(scope, Exception { TypeError });
    276257
    277258        // 4. Calculate the normalized property name as the result of applying the IDL attribute name to animation property name algorithm to property name.
     259        auto cssPropertyID = IDLAttributeNameToAnimationPropertyName(animationProperties[i].string());
     260
    278261        // 5. Add a property to to keyframe output with normalized property name as the property name, and property values as the property value.
    279262        keyframeOuput.propertiesAndValues.append({ cssPropertyID, propertyValues });
    280263    }
    281264
    282     // Now we can perform step 5.
    283     std::sort(keyframeOuput.propertiesAndValues.begin(), keyframeOuput.propertiesAndValues.end(), [](auto& lhs, auto& rhs) {
    284         return getPropertyNameString(lhs.property).utf8() < getPropertyNameString(rhs.property).utf8();
    285     });
    286 
    287265    // 7. Return keyframe output.
    288266    return { WTFMove(keyframeOuput) };
    289267}
    290268
     269static inline ExceptionOr<void> processIterableKeyframes(ExecState& state, Strong<JSObject>&& keyframesInput, JSValue method, Vector<KeyframeEffectReadOnly::ParsedKeyframe>& parsedKeyframes)
     270{
     271    VM& vm = state.vm();
     272    auto scope = DECLARE_THROW_SCOPE(vm);
     273
     274    // 1. Let iter be GetIterator(object, method).
     275    forEachInIterable(state, keyframesInput.get(), method, [&parsedKeyframes](VM& vm, ExecState& state, JSValue nextValue) -> ExceptionOr<void> {
     276        // Steps 2 through 6 are already implemented by forEachInIterable().
     277        auto scope = DECLARE_THROW_SCOPE(vm);
     278        if (!nextValue || !nextValue.isObject()) {
     279            throwException(&state, scope, JSC::Exception::create(vm, createTypeError(&state)));
     280            return { };
     281        }
     282
     283        // 7. Append to processed keyframes the result of running the procedure to process a keyframe-like object passing nextItem
     284        // as the keyframe input and with the allow lists flag set to false.
     285        auto processKeyframeLikeObjectResult = processKeyframeLikeObject(state, Strong<JSObject>(vm, nextValue.toObject(&state)), false);
     286        if (processKeyframeLikeObjectResult.hasException())
     287            return processKeyframeLikeObjectResult.releaseException();
     288        auto keyframeLikeObject = processKeyframeLikeObjectResult.returnValue();
     289
     290        KeyframeEffectReadOnly::ParsedKeyframe keyframeOutput;
     291
     292        // When calling processKeyframeLikeObject() with the "allow lists" flag set to false, the only offset
     293        // alternatives we should expect are double and nullptr.
     294        if (WTF::holds_alternative<double>(keyframeLikeObject.baseProperties.offset))
     295            keyframeOutput.offset = WTF::get<double>(keyframeLikeObject.baseProperties.offset);
     296        else
     297            ASSERT(WTF::holds_alternative<std::nullptr_t>(keyframeLikeObject.baseProperties.offset));
     298
     299        // When calling processKeyframeLikeObject() with the "allow lists" flag set to false, the only easing
     300        // alternative we should expect is String.
     301        ASSERT(WTF::holds_alternative<String>(keyframeLikeObject.baseProperties.easing));
     302        keyframeOutput.easing = WTF::get<String>(keyframeLikeObject.baseProperties.easing);
     303
     304        // When calling processKeyframeLikeObject() with the "allow lists" flag set to false, the only composite
     305        // alternatives we should expect are CompositeOperation and nullptr.
     306        if (WTF::holds_alternative<CompositeOperation>(keyframeLikeObject.baseProperties.composite))
     307            keyframeOutput.composite = WTF::get<CompositeOperation>(keyframeLikeObject.baseProperties.composite);
     308        else
     309            ASSERT(WTF::holds_alternative<std::nullptr_t>(keyframeLikeObject.baseProperties.composite));
     310
     311        for (auto& propertyAndValue : keyframeLikeObject.propertiesAndValues) {
     312            auto cssPropertyId = propertyAndValue.property;
     313            // When calling processKeyframeLikeObject() with the "allow lists" flag set to false,
     314            // there should only ever be a single value for a given property.
     315            ASSERT(propertyAndValue.values.size() == 1);
     316            auto stringValue = propertyAndValue.values[0];
     317            if (keyframeOutput.style->setProperty(cssPropertyId, stringValue))
     318                keyframeOutput.unparsedStyle.set(cssPropertyId, stringValue);
     319        }
     320
     321        parsedKeyframes.append(WTFMove(keyframeOutput));
     322
     323        return { };
     324    });
     325
     326    return { };
     327}
     328
    291329static inline ExceptionOr<void> processPropertyIndexedKeyframes(ExecState& state, Strong<JSObject>&& keyframesInput, Vector<KeyframeEffectReadOnly::ParsedKeyframe>& parsedKeyframes, Vector<String>& unusedEasings)
    292330{
    293331    // 1. Let property-indexed keyframe be the result of running the procedure to process a keyframe-like object passing object as the keyframe input.
    294     auto processKeyframeLikeObjectResult = processKeyframeLikeObject(state, WTFMove(keyframesInput));
     332    auto processKeyframeLikeObjectResult = processKeyframeLikeObject(state, WTFMove(keyframesInput), true);
    295333    if (processKeyframeLikeObjectResult.hasException())
    296334        return processKeyframeLikeObjectResult.releaseException();
  • trunk/Source/WebCore/animation/KeyframeEffectReadOnly.h

    r232946 r233729  
    5454        Variant<Vector<String>, String> easing = Vector<String>();
    5555        Variant<std::nullptr_t, Vector<std::optional<CompositeOperation>>, CompositeOperation> composite = Vector<std::optional<CompositeOperation>>();
     56    };
     57
     58    struct BaseKeyframe {
     59        std::optional<double> offset;
     60        String easing { "linear" };
     61        std::optional<CompositeOperation> composite;
    5662    };
    5763
  • trunk/Source/WebCore/animation/KeyframeEffectReadOnly.idl

    r228694 r233729  
    4545};
    4646
     47dictionary BaseKeyframe {
     48    double? offset = null;
     49    DOMString easing = "linear";
     50    CompositeOperation? composite = null;
     51};
     52
    4753[
    4854    JSGenerateToJSObject
Note: See TracChangeset for help on using the changeset viewer.