void KeyframeEffectModelBase::ensureKeyframeGroups() const { if (m_keyframeGroups) return; m_keyframeGroups = adoptPtrWillBeNoop(new KeyframeGroupMap); const KeyframeVector keyframes = normalizedKeyframes(getFrames()); for (KeyframeVector::const_iterator keyframeIter = keyframes.begin(); keyframeIter != keyframes.end(); ++keyframeIter) { const Keyframe* keyframe = keyframeIter->get(); PropertySet keyframeProperties = keyframe->properties(); for (PropertySet::const_iterator propertyIter = keyframeProperties.begin(); propertyIter != keyframeProperties.end(); ++propertyIter) { CSSPropertyID property = *propertyIter; ASSERT_WITH_MESSAGE(!isExpandedShorthand(property), "Web Animations: Encountered shorthand CSS property (%d) in normalized keyframes.", property); KeyframeGroupMap::iterator groupIter = m_keyframeGroups->find(property); PropertySpecificKeyframeGroup* group; if (groupIter == m_keyframeGroups->end()) group = m_keyframeGroups->add(property, adoptPtrWillBeNoop(new PropertySpecificKeyframeGroup)).storedValue->value.get(); else group = groupIter->value.get(); group->appendKeyframe(keyframe->createPropertySpecificKeyframe(property)); } } // Add synthetic keyframes. for (KeyframeGroupMap::iterator iter = m_keyframeGroups->begin(); iter != m_keyframeGroups->end(); ++iter) { iter->value->addSyntheticKeyframeIfRequired(this); iter->value->removeRedundantKeyframes(); } }
void CompositorAnimationsImpl::getAnimationOnCompositor(const Timing& timing, const KeyframeEffectModel& effect, Vector<OwnPtr<blink::WebAnimation> >& animations) { ASSERT(animations.isEmpty()); CompositorTiming compositorTiming; bool timingValid = convertTimingForCompositor(timing, compositorTiming); ASSERT_UNUSED(timingValid, timingValid); RefPtr<TimingFunction> timingFunction = timing.timingFunction; if (compositorTiming.reverse) timingFunction = CompositorAnimationsTimingFunctionReverser::reverse(timingFunction.get()); PropertySet properties = effect.properties(); ASSERT(!properties.isEmpty()); for (PropertySet::iterator it = properties.begin(); it != properties.end(); ++it) { KeyframeVector values; getKeyframeValuesForProperty(&effect, *it, compositorTiming.scaledDuration, compositorTiming.reverse, values); blink::WebAnimation::TargetProperty targetProperty; OwnPtr<blink::WebAnimationCurve> curve; switch (*it) { case CSSPropertyOpacity: { targetProperty = blink::WebAnimation::TargetPropertyOpacity; blink::WebFloatAnimationCurve* floatCurve = blink::Platform::current()->compositorSupport()->createFloatAnimationCurve(); addKeyframesToCurve(*floatCurve, values, *timingFunction.get()); curve = adoptPtr(floatCurve); break; } case CSSPropertyWebkitFilter: { targetProperty = blink::WebAnimation::TargetPropertyFilter; blink::WebFilterAnimationCurve* filterCurve = blink::Platform::current()->compositorSupport()->createFilterAnimationCurve(); addKeyframesToCurve(*filterCurve, values, *timingFunction); curve = adoptPtr(filterCurve); break; } case CSSPropertyWebkitTransform: { targetProperty = blink::WebAnimation::TargetPropertyTransform; blink::WebTransformAnimationCurve* transformCurve = blink::Platform::current()->compositorSupport()->createTransformAnimationCurve(); addKeyframesToCurve(*transformCurve, values, *timingFunction.get()); curve = adoptPtr(transformCurve); break; } default: ASSERT_NOT_REACHED(); continue; } ASSERT(curve.get()); OwnPtr<blink::WebAnimation> animation = adoptPtr(blink::Platform::current()->compositorSupport()->createAnimation(*curve, targetProperty)); animation->setIterations(compositorTiming.adjustedIterationCount); animation->setTimeOffset(compositorTiming.scaledTimeOffset); animation->setAlternatesDirection(compositorTiming.alternate); animations.append(animation.release()); } ASSERT(!animations.isEmpty()); }
PropertySet KeyframeEffectModelBase::properties() const { PropertySet result; if (!m_keyframes.size()) { return result; } result = m_keyframes[0]->properties(); for (size_t i = 1; i < m_keyframes.size(); i++) { PropertySet extras = m_keyframes[i]->properties(); for (PropertySet::const_iterator it = extras.begin(); it != extras.end(); ++it) { result.add(*it); } } return result; }
static void resolveKeyframes(StyleResolver* resolver, Element* element, const Element& parentElement, const RenderStyle& style, RenderStyle* parentStyle, const AtomicString& name, TimingFunction* defaultTimingFunction, Vector<std::pair<KeyframeEffectModel::KeyframeVector, RefPtr<TimingFunction> > >& keyframesAndTimingFunctions) { ASSERT(RuntimeEnabledFeatures::webAnimationsCSSEnabled()); // When the element is null, use its parent for scoping purposes. const Element* elementForScoping = element ? element : &parentElement; const StyleRuleKeyframes* keyframesRule = CSSAnimations::matchScopedKeyframesRule(resolver, elementForScoping, name.impl()); if (!keyframesRule) return; const Vector<RefPtr<StyleKeyframe> >& styleKeyframes = keyframesRule->keyframes(); if (styleKeyframes.isEmpty()) return; // Construct and populate the style for each keyframe PropertySet specifiedProperties; KeyframeEffectModel::KeyframeVector keyframes; HashMap<double, RefPtr<TimingFunction> > perKeyframeTimingFunctions; for (size_t i = 0; i < styleKeyframes.size(); ++i) { const StyleKeyframe* styleKeyframe = styleKeyframes[i].get(); // It's OK to pass a null element here. RefPtr<RenderStyle> keyframeStyle = resolver->styleForKeyframe(element, style, parentStyle, styleKeyframe, name); RefPtr<Keyframe> keyframe = Keyframe::create(); const Vector<double>& offsets = styleKeyframe->keys(); ASSERT(!offsets.isEmpty()); keyframe->setOffset(offsets[0]); TimingFunction* timingFunction = defaultTimingFunction; const StylePropertySet* properties = styleKeyframe->properties(); for (unsigned j = 0; j < properties->propertyCount(); j++) { CSSPropertyID property = properties->propertyAt(j).id(); specifiedProperties.add(property); if (property == CSSPropertyWebkitAnimationTimingFunction || property == CSSPropertyAnimationTimingFunction) timingFunction = KeyframeValue::timingFunction(*keyframeStyle); else if (CSSAnimations::isAnimatableProperty(property)) keyframe->setPropertyValue(property, CSSAnimatableValueFactory::create(property, *keyframeStyle).get()); } keyframes.append(keyframe); // The last keyframe specified at a given offset is used. perKeyframeTimingFunctions.set(offsets[0], timingFunction); for (size_t j = 1; j < offsets.size(); ++j) { keyframes.append(keyframe->cloneWithOffset(offsets[j])); perKeyframeTimingFunctions.set(offsets[j], timingFunction); } } ASSERT(!keyframes.isEmpty()); if (!perKeyframeTimingFunctions.contains(0)) perKeyframeTimingFunctions.set(0, defaultTimingFunction); for (PropertySet::const_iterator iter = specifiedProperties.begin(); iter != specifiedProperties.end(); ++iter) { const CSSPropertyID property = *iter; ASSERT(property != CSSPropertyInvalid); blink::Platform::current()->histogramSparse("WebCore.Animation.CSSProperties", UseCounter::mapCSSPropertyIdToCSSSampleIdForHistogram(property)); } // Remove duplicate keyframes. In CSS the last keyframe at a given offset takes priority. std::stable_sort(keyframes.begin(), keyframes.end(), Keyframe::compareOffsets); size_t targetIndex = 0; for (size_t i = 1; i < keyframes.size(); i++) { if (keyframes[i]->offset() != keyframes[targetIndex]->offset()) targetIndex++; if (targetIndex != i) keyframes[targetIndex] = keyframes[i]; } keyframes.shrink(targetIndex + 1); // Add 0% and 100% keyframes if absent. RefPtr<Keyframe> startKeyframe = keyframes[0]; if (startKeyframe->offset()) { startKeyframe = Keyframe::create(); startKeyframe->setOffset(0); keyframes.prepend(startKeyframe); } RefPtr<Keyframe> endKeyframe = keyframes[keyframes.size() - 1]; if (endKeyframe->offset() != 1) { endKeyframe = Keyframe::create(); endKeyframe->setOffset(1); keyframes.append(endKeyframe); } ASSERT(keyframes.size() >= 2); ASSERT(!keyframes.first()->offset()); ASSERT(keyframes.last()->offset() == 1); // Snapshot current property values for 0% and 100% if missing. PropertySet allProperties; size_t numKeyframes = keyframes.size(); for (size_t i = 0; i < numKeyframes; i++) { const PropertySet& keyframeProperties = keyframes[i]->properties(); for (PropertySet::const_iterator iter = keyframeProperties.begin(); iter != keyframeProperties.end(); ++iter) allProperties.add(*iter); } const PropertySet& startKeyframeProperties = startKeyframe->properties(); const PropertySet& endKeyframeProperties = endKeyframe->properties(); bool missingStartValues = startKeyframeProperties.size() < allProperties.size(); bool missingEndValues = endKeyframeProperties.size() < allProperties.size(); if (missingStartValues || missingEndValues) { for (PropertySet::const_iterator iter = allProperties.begin(); iter != allProperties.end(); ++iter) { const CSSPropertyID property = *iter; bool startNeedsValue = missingStartValues && !startKeyframeProperties.contains(property); bool endNeedsValue = missingEndValues && !endKeyframeProperties.contains(property); if (!startNeedsValue && !endNeedsValue) continue; RefPtr<AnimatableValue> snapshotValue = CSSAnimatableValueFactory::create(property, style); if (startNeedsValue) startKeyframe->setPropertyValue(property, snapshotValue.get()); if (endNeedsValue) endKeyframe->setPropertyValue(property, snapshotValue.get()); } } ASSERT(startKeyframe->properties().size() == allProperties.size()); ASSERT(endKeyframe->properties().size() == allProperties.size()); // Determine how many keyframes specify each property. Note that this must // be done after we've filled in end keyframes. typedef HashCountedSet<CSSPropertyID> PropertyCountedSet; PropertyCountedSet propertyCounts; for (size_t i = 0; i < numKeyframes; ++i) { const PropertySet& properties = keyframes[i]->properties(); for (PropertySet::const_iterator iter = properties.begin(); iter != properties.end(); ++iter) propertyCounts.add(*iter); } // Split keyframes into groups, where each group contains only keyframes // which specify all properties used in that group. Each group is animated // in a separate animation, to allow per-keyframe timing functions to be // applied correctly. for (PropertyCountedSet::const_iterator iter = propertyCounts.begin(); iter != propertyCounts.end(); ++iter) { const CSSPropertyID property = iter->key; const size_t count = iter->value; ASSERT(count <= numKeyframes); if (count == numKeyframes) continue; KeyframeEffectModel::KeyframeVector splitOutKeyframes; for (size_t i = 0; i < numKeyframes; i++) { Keyframe* keyframe = keyframes[i].get(); if (!keyframe->properties().contains(property)) { ASSERT(i && i != numKeyframes - 1); continue; } RefPtr<Keyframe> clonedKeyframe = Keyframe::create(); clonedKeyframe->setOffset(keyframe->offset()); clonedKeyframe->setComposite(keyframe->composite()); clonedKeyframe->setPropertyValue(property, keyframe->propertyValue(property)); splitOutKeyframes.append(clonedKeyframe); // Note that it's OK if this keyframe ends up having no // properties. This can only happen when none of the properties // are specified in all keyframes, in which case we won't animate // anything with these keyframes. keyframe->clearPropertyValue(property); } ASSERT(!splitOutKeyframes.first()->offset()); ASSERT(splitOutKeyframes.last()->offset() == 1); #ifndef NDEBUG for (size_t j = 0; j < splitOutKeyframes.size(); ++j) ASSERT(splitOutKeyframes[j]->properties().size() == 1); #endif keyframesAndTimingFunctions.append(std::make_pair(splitOutKeyframes, generateTimingFunction(splitOutKeyframes, perKeyframeTimingFunctions))); } unsigned numPropertiesSpecifiedInAllKeyframes = keyframes.first()->properties().size(); #ifndef NDEBUG for (size_t i = 1; i < numKeyframes; ++i) ASSERT(keyframes[i]->properties().size() == numPropertiesSpecifiedInAllKeyframes); #endif // If the animation specifies any keyframes, we always provide at least one // vector of resolved keyframes, even if no properties are animated. if (numPropertiesSpecifiedInAllKeyframes || keyframesAndTimingFunctions.isEmpty()) keyframesAndTimingFunctions.append(std::make_pair(keyframes, generateTimingFunction(keyframes, perKeyframeTimingFunctions))); }
bool CompositorAnimations::isCandidateForAnimationOnCompositor(const Timing& timing, const AnimationEffect& effect) { const KeyframeEffectModel& keyframeEffect = *toKeyframeEffectModel(&effect); // Are the keyframes convertible? const KeyframeEffectModel::KeyframeVector frames = keyframeEffect.getFrames(); for (size_t i = 0; i < frames.size(); ++i) { // Only replace mode can be accelerated if (frames[i]->composite() != AnimationEffect::CompositeReplace) return false; // Check all the properties can be accelerated const PropertySet properties = frames[i]->properties(); // FIXME: properties creates a whole new PropertySet! if (properties.isEmpty()) return false; for (PropertySet::const_iterator it = properties.begin(); it != properties.end(); ++it) { switch (*it) { case CSSPropertyOpacity: continue; case CSSPropertyWebkitTransform: if (toAnimatableTransform(frames[i]->propertyValue(CSSPropertyWebkitTransform))->transformOperations().dependsOnBoxSize()) return false; continue; case CSSPropertyWebkitFilter: { const FilterOperations& operations = toAnimatableFilterOperations(frames[i]->propertyValue(CSSPropertyWebkitFilter))->operations(); if (operations.hasFilterThatMovesPixels()) return false; for (size_t i = 0; i < operations.size(); i++) { const FilterOperation& op = *operations.at(i); if (op.type() == FilterOperation::VALIDATED_CUSTOM || op.type() == FilterOperation::CUSTOM) return false; } continue; } default: return false; } } } // Is the timing object convertible? CompositorAnimationsImpl::CompositorTiming out; if (!CompositorAnimationsImpl::convertTimingForCompositor(timing, out)) return false; // Is the timing function convertible? switch (timing.timingFunction->type()) { case TimingFunction::LinearFunction: break; case TimingFunction::CubicBezierFunction: // Can have a cubic if we don't have to split it (IE only have two frames). if (frames.size() != 2) return false; ASSERT(frames[0]->offset() == 0.0 && frames[1]->offset() == 1.0); break; case TimingFunction::StepsFunction: return false; case TimingFunction::ChainedFunction: { // Currently we only support chained segments in the form the CSS code // generates. These chained segments are only one level deep and have // one timing function per frame. const ChainedTimingFunction* chained = static_cast<const ChainedTimingFunction*>(timing.timingFunction.get()); if (!chained->m_segments.size()) return false; if (frames.size() != chained->m_segments.size() + 1) return false; for (size_t timeIndex = 0; timeIndex < chained->m_segments.size(); timeIndex++) { const ChainedTimingFunction::Segment& segment = chained->m_segments[timeIndex]; if (frames[timeIndex]->offset() != segment.m_min || frames[timeIndex + 1]->offset() != segment.m_max) return false; switch (segment.m_timingFunction->type()) { case TimingFunction::LinearFunction: case TimingFunction::CubicBezierFunction: continue; case TimingFunction::StepsFunction: case TimingFunction::ChainedFunction: default: return false; } } break; } default: ASSERT_NOT_REACHED(); return false; } return true; }