diff --git a/components/style/gecko/generated/bindings.rs b/components/style/gecko/generated/bindings.rs index 3f1fe721076..3eb3c522913 100644 --- a/components/style/gecko/generated/bindings.rs +++ b/components/style/gecko/generated/bindings.rs @@ -2618,12 +2618,20 @@ extern "C" { RawServoAnimationValueBorrowed) -> f32; } +extern "C" { + pub fn Servo_AnimationValue_Opacity(arg1: f32) + -> RawServoAnimationValueStrong; +} extern "C" { pub fn Servo_AnimationValue_GetTransform(value: RawServoAnimationValueBorrowed, list: *mut RefPtr); } +extern "C" { + pub fn Servo_AnimationValue_Transform(list: *const nsCSSValueSharedList) + -> RawServoAnimationValueStrong; +} extern "C" { pub fn Servo_AnimationValue_DeepEqual(arg1: RawServoAnimationValueBorrowed, @@ -2768,9 +2776,22 @@ extern "C" { RawGeckoAnimationPropertySegmentBorrowed, computed_timing: RawGeckoComputedTimingBorrowed, - iteration_composite: + iter_composite: IterationCompositeOperation); } +extern "C" { + pub fn Servo_ComposeAnimationSegment(animation_segment: + RawGeckoAnimationPropertySegmentBorrowed, + underlying_value: + RawServoAnimationValueBorrowedOrNull, + last_value: + RawServoAnimationValueBorrowedOrNull, + iter_composite: + IterationCompositeOperation, + progress: f64, + current_iteration: u64) + -> RawServoAnimationValueStrong; +} extern "C" { pub fn Servo_DeclarationBlock_PropertyIsSet(declarations: RawServoDeclarationBlockBorrowed, diff --git a/components/style/gecko_bindings/sugar/ns_css_value.rs b/components/style/gecko_bindings/sugar/ns_css_value.rs index 8c1d83c8ffd..2a930b5cd1a 100644 --- a/components/style/gecko_bindings/sugar/ns_css_value.rs +++ b/components/style/gecko_bindings/sugar/ns_css_value.rs @@ -23,6 +23,12 @@ impl nsCSSValue { unsafe { mem::zeroed() } } + /// Returns true if this nsCSSValue is none. + #[inline] + pub fn is_none(&self) -> bool { + self.mUnit == nsCSSUnit::eCSSUnit_None + } + /// Returns this nsCSSValue value as an integer, unchecked in release /// builds. pub fn integer_unchecked(&self) -> i32 { diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index cb0866df0c4..16a55c72689 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -3081,6 +3081,13 @@ fn static_assert() { }; unsafe { + use gecko_bindings::structs::nsCSSKeyword; + use values::computed::Angle; + + let get_array_angle = || -> Angle { + bindings::Gecko_CSSValue_GetArrayItemConst(gecko_value, 1).get_angle() + }; + match transform_function { ${computed_operation_arm("Matrix", "matrix3d", ["number"] * 16)} ${computed_operation_arm("Skew", "skew", ["angle"] * 2)} @@ -3092,23 +3099,59 @@ fn static_assert() { ["list"] * 2 + ["percentage"])} ${computed_operation_arm("AccumulateMatrix", "accumulatematrix", ["list"] * 2 + ["percentage_to_integer"])} - _ => panic!("We shouldn't set any other transform function types"), + // FIXME: Bug 1391145 will introduce new types for these keywords. For now, we + // temporarily don't use |computed_operation_arm| because these are special cases + // for compositor animations when we use Gecko style backend on the main thread, + // and I don't want to add too many special cases in |computed_operation_arm|. + // + // Note: Gecko only converts translate and scale into the corresponding primitive + // functions, so we still need to handle the following functions. + nsCSSKeyword::eCSSKeyword_skewx => { + ComputedOperation::Skew(get_array_angle(), Angle::zero()) + }, + nsCSSKeyword::eCSSKeyword_skewy => { + ComputedOperation::Skew(Angle::zero(), get_array_angle()) + }, + nsCSSKeyword::eCSSKeyword_rotatex => { + ComputedOperation::Rotate(1.0, 0.0, 0.0, get_array_angle()) + }, + nsCSSKeyword::eCSSKeyword_rotatey => { + ComputedOperation::Rotate(0.0, 1.0, 0.0, get_array_angle()) + }, + nsCSSKeyword::eCSSKeyword_rotatez | nsCSSKeyword::eCSSKeyword_rotate => { + ComputedOperation::Rotate(0.0, 0.0, 1.0, get_array_angle()) + }, + _ => panic!("{:?} is not an acceptable transform function", transform_function), } } } pub fn clone_transform(&self) -> longhands::transform::computed_value::T { - use properties::longhands::transform::computed_value; - if self.gecko.mSpecifiedTransform.mRawPtr.is_null() { - return computed_value::T(None); + return longhands::transform::computed_value::T(None); } let list = unsafe { (*self.gecko.mSpecifiedTransform.to_safe().get()).mHead.as_ref() }; - let result = list.map(|list| { - list.into_iter() - .map(|value| Self::clone_single_transform_function(value)) - .collect() - }); - computed_value::T(result) + Self::clone_transform_from_list(list) + } + pub fn clone_transform_from_list(list: Option< &structs::root::nsCSSValueList>) + -> longhands::transform::computed_value::T { + let result = match list { + Some(list) => { + let vec: Vec<_> = list + .into_iter() + .filter_map(|value| { + // Handle none transform. + if value.is_none() { + None + } else { + Some(Self::clone_single_transform_function(value)) + } + }) + .collect(); + if !vec.is_empty() { Some(vec) } else { None } + }, + _ => None, + }; + longhands::transform::computed_value::T(result) } ${impl_transition_time_value('delay', 'Delay')} diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 901a06bfe14..4c841c71daa 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -67,6 +67,7 @@ use style::gecko_bindings::bindings::RawGeckoServoAnimationValueListBorrowed; use style::gecko_bindings::bindings::RawGeckoServoAnimationValueListBorrowedMut; use style::gecko_bindings::bindings::RawGeckoServoStyleRuleListBorrowedMut; use style::gecko_bindings::bindings::RawServoAnimationValueBorrowed; +use style::gecko_bindings::bindings::RawServoAnimationValueBorrowedOrNull; use style::gecko_bindings::bindings::RawServoAnimationValueMapBorrowedMut; use style::gecko_bindings::bindings::RawServoAnimationValueStrong; use style::gecko_bindings::bindings::RawServoAnimationValueTableBorrowed; @@ -411,6 +412,157 @@ pub extern "C" fn Servo_AnimationValues_ComputeDistance(from: RawServoAnimationV from_value.compute_squared_distance(to_value).map(|d| d.sqrt()).unwrap_or(-1.0) } +/// Compute one of the endpoints for the interpolation interval, compositing it with the +/// underlying value if needed. +/// An None returned value means, "Just use endpoint_value as-is." +/// It is the responsibility of the caller to ensure that |underlying_value| is provided +/// when it will be used. +fn composite_endpoint( + endpoint_value: Option<&RawOffsetArc>, + composite: CompositeOperation, + underlying_value: Option<&AnimationValue>, +) -> Option { + match endpoint_value { + Some(endpoint_value) => { + match composite { + CompositeOperation::Add => { + underlying_value + .expect("We should have an underlying_value") + .animate(endpoint_value, Procedure::Add).ok() + }, + CompositeOperation::Accumulate => { + underlying_value + .expect("We should have an underlying value") + .animate(endpoint_value, Procedure::Accumulate { count: 1 }) + .ok() + }, + _ => None, + } + }, + None => underlying_value.map(|v| v.clone()), + } +} + +/// Accumulate one of the endpoints of the animation interval. +/// A returned value of None means, "Just use endpoint_value as-is." +fn accumulate_endpoint( + endpoint_value: Option<&RawOffsetArc>, + composited_value: Option, + last_value: &AnimationValue, + current_iteration: u64 +) -> Option { + debug_assert!(endpoint_value.is_some() || composited_value.is_some(), + "Should have a suitable value to use"); + + let count = current_iteration; + match composited_value { + Some(endpoint) => { + last_value + .animate(&endpoint, Procedure::Accumulate { count }) + .ok() + .or(Some(endpoint)) + }, + None => { + last_value + .animate(endpoint_value.unwrap(), Procedure::Accumulate { count }) + .ok() + }, + } +} + +/// Compose the animation segment. We composite it with the underlying_value and last_value if +/// needed. +/// The caller is responsible for providing an underlying value and last value +/// in all situations where there are needed. +fn compose_animation_segment( + segment: RawGeckoAnimationPropertySegmentBorrowed, + underlying_value: Option<&AnimationValue>, + last_value: Option<&AnimationValue>, + iteration_composite: IterationCompositeOperation, + current_iteration: u64, + total_progress: f64, + segment_progress: f64, +) -> AnimationValue { + // Extract keyframe values. + let raw_from_value; + let keyframe_from_value = if !segment.mFromValue.mServo.mRawPtr.is_null() { + raw_from_value = unsafe { &*segment.mFromValue.mServo.mRawPtr }; + Some(AnimationValue::as_arc(&raw_from_value)) + } else { + None + }; + + let raw_to_value; + let keyframe_to_value = if !segment.mToValue.mServo.mRawPtr.is_null() { + raw_to_value = unsafe { &*segment.mToValue.mServo.mRawPtr }; + Some(AnimationValue::as_arc(&raw_to_value)) + } else { + None + }; + + let mut composited_from_value = composite_endpoint(keyframe_from_value, + segment.mFromComposite, + underlying_value); + let mut composited_to_value = composite_endpoint(keyframe_to_value, + segment.mToComposite, + underlying_value); + + debug_assert!(keyframe_from_value.is_some() || composited_from_value.is_some(), + "Should have a suitable from value to use"); + debug_assert!(keyframe_to_value.is_some() || composited_to_value.is_some(), + "Should have a suitable to value to use"); + + // Apply iteration composite behavior. + if iteration_composite == IterationCompositeOperation::Accumulate && current_iteration > 0 { + let last_value = last_value.unwrap_or_else(|| { + underlying_value.expect("Should have a valid underlying value") + }); + + composited_from_value = accumulate_endpoint(keyframe_from_value, + composited_from_value, + last_value, + current_iteration); + composited_to_value = accumulate_endpoint(keyframe_to_value, + composited_to_value, + last_value, + current_iteration); + } + + // Use the composited value if there is one, otherwise, use the original keyframe value. + let from = composited_from_value.as_ref().unwrap_or_else(|| keyframe_from_value.unwrap()); + let to = composited_to_value.as_ref().unwrap_or_else(|| keyframe_to_value.unwrap()); + + if segment.mToKey == segment.mFromKey { + return if total_progress < 0. { from.clone() } else { to.clone() }; + } + + match from.animate(to, Procedure::Interpolate { progress: segment_progress }) { + Ok(value) => value, + _ => if segment_progress < 0.5 { from.clone() } else { to.clone() }, + } +} + +#[no_mangle] +pub extern "C" fn Servo_ComposeAnimationSegment( + segment: RawGeckoAnimationPropertySegmentBorrowed, + underlying_value: RawServoAnimationValueBorrowedOrNull, + last_value: RawServoAnimationValueBorrowedOrNull, + iteration_composite: IterationCompositeOperation, + progress: f64, + current_iteration: u64 +) -> RawServoAnimationValueStrong { + let underlying_value = AnimationValue::arc_from_borrowed(&underlying_value).map(|v| &**v); + let last_value = AnimationValue::arc_from_borrowed(&last_value).map(|v| &**v); + let result = compose_animation_segment(segment, + underlying_value, + last_value, + iteration_composite, + current_iteration, + progress, + progress); + Arc::new(result).into_strong() +} + #[no_mangle] pub extern "C" fn Servo_AnimationCompose(raw_value_map: RawServoAnimationValueMapBorrowedMut, base_values: RawServoAnimationValueTableBorrowed, @@ -461,125 +613,31 @@ pub extern "C" fn Servo_AnimationCompose(raw_value_map: RawServoAnimationValueMa return; } - // Extract keyframe values. - let raw_from_value; - let keyframe_from_value = if !segment.mFromValue.mServo.mRawPtr.is_null() { - raw_from_value = unsafe { &*segment.mFromValue.mServo.mRawPtr }; - Some(AnimationValue::as_arc(&raw_from_value)) + let raw_last_value; + let last_value = if !last_segment.mToValue.mServo.mRawPtr.is_null() { + raw_last_value = unsafe { &*last_segment.mToValue.mServo.mRawPtr }; + Some(&**AnimationValue::as_arc(&raw_last_value)) } else { None }; - let raw_to_value; - let keyframe_to_value = if !segment.mToValue.mServo.mRawPtr.is_null() { - raw_to_value = unsafe { &*segment.mToValue.mServo.mRawPtr }; - Some(AnimationValue::as_arc(&raw_to_value)) - } else { - None - }; - - // Composite with underlying value. - // A return value of None means, "Just use keyframe_value as-is." - let composite_endpoint = |keyframe_value: Option<&RawOffsetArc>, - composite_op: CompositeOperation| -> Option { - match keyframe_value { - Some(keyframe_value) => { - match composite_op { - CompositeOperation::Add => { - debug_assert!(need_underlying_value, - "Should have detected we need an underlying value"); - underlying_value.as_ref().unwrap().animate(keyframe_value, Procedure::Add).ok() - }, - CompositeOperation::Accumulate => { - debug_assert!(need_underlying_value, - "Should have detected we need an underlying value"); - underlying_value - .as_ref() - .unwrap() - .animate(keyframe_value, Procedure::Accumulate { count: 1 }) - .ok() - }, - _ => None, - } - }, - None => { - debug_assert!(need_underlying_value, - "Should have detected we need an underlying value"); - underlying_value.clone() - }, - } - }; - let mut composited_from_value = composite_endpoint(keyframe_from_value, segment.mFromComposite); - let mut composited_to_value = composite_endpoint(keyframe_to_value, segment.mToComposite); - - debug_assert!(keyframe_from_value.is_some() || composited_from_value.is_some(), - "Should have a suitable from value to use"); - debug_assert!(keyframe_to_value.is_some() || composited_to_value.is_some(), - "Should have a suitable to value to use"); - - // Apply iteration composite behavior. - if iteration_composite == IterationCompositeOperation::Accumulate && - computed_timing.mCurrentIteration > 0 { - let raw_last_value; - let last_value = if !last_segment.mToValue.mServo.mRawPtr.is_null() { - raw_last_value = unsafe { &*last_segment.mToValue.mServo.mRawPtr }; - &*AnimationValue::as_arc(&raw_last_value) - } else { - debug_assert!(need_underlying_value, - "Should have detected we need an underlying value"); - underlying_value.as_ref().unwrap() - }; - - // As with composite_endpoint, a return value of None means, "Use keyframe_value as-is." - let apply_iteration_composite = |keyframe_value: Option<&RawOffsetArc>, - composited_value: Option| - -> Option { - let count = computed_timing.mCurrentIteration; - match composited_value { - Some(endpoint) => { - last_value - .animate(&endpoint, Procedure::Accumulate { count }) - .ok() - .or(Some(endpoint)) - }, - None => { - last_value - .animate(keyframe_value.unwrap(), Procedure::Accumulate { count }) - .ok() - }, - } - }; - - composited_from_value = apply_iteration_composite(keyframe_from_value, - composited_from_value); - composited_to_value = apply_iteration_composite(keyframe_to_value, - composited_to_value); - } - - // Use the composited value if there is one, otherwise, use the original keyframe value. - let from_value = composited_from_value.as_ref().unwrap_or_else(|| keyframe_from_value.unwrap()); - let to_value = composited_to_value.as_ref().unwrap_or_else(|| keyframe_to_value.unwrap()); - let progress = unsafe { Gecko_GetProgressFromComputedTiming(computed_timing) }; - if segment.mToKey == segment.mFromKey { - if progress < 0. { - value_map.insert(property, from_value.clone()); - } else { - value_map.insert(property, to_value.clone()); - } - return; - } - - let pos = unsafe { - Gecko_GetPositionInSegment(segment, progress, computed_timing.mBeforeFlag) - }; - if let Ok(value) = from_value.animate(to_value, Procedure::Interpolate { progress: pos }) { - value_map.insert(property, value); - } else if pos < 0.5 { - value_map.insert(property, from_value.clone()); + let position = if segment.mToKey == segment.mFromKey { + // Note: compose_animation_segment doesn't use this value + // if segment.mFromKey == segment.mToKey, so assigning |progress| directly is fine. + progress } else { - value_map.insert(property, to_value.clone()); - } + unsafe { Gecko_GetPositionInSegment(segment, progress, computed_timing.mBeforeFlag) } + }; + + let result = compose_animation_segment(segment, + underlying_value.as_ref(), + last_value, + iteration_composite, + computed_timing.mCurrentIteration, + progress, + position); + value_map.insert(property, result); } macro_rules! get_property_id_from_nscsspropertyid { @@ -634,9 +692,9 @@ pub extern "C" fn Servo_Shorthand_AnimationValues_Serialize(shorthand_property: } #[no_mangle] -pub extern "C" fn Servo_AnimationValue_GetOpacity(value: RawServoAnimationValueBorrowed) - -> f32 -{ +pub extern "C" fn Servo_AnimationValue_GetOpacity( + value: RawServoAnimationValueBorrowed +) -> f32 { let value = AnimationValue::as_arc(&value); if let AnimationValue::Opacity(opacity) = **value { opacity @@ -646,9 +704,17 @@ pub extern "C" fn Servo_AnimationValue_GetOpacity(value: RawServoAnimationValueB } #[no_mangle] -pub extern "C" fn Servo_AnimationValue_GetTransform(value: RawServoAnimationValueBorrowed, - list: *mut structs::RefPtr) -{ +pub extern "C" fn Servo_AnimationValue_Opacity( + opacity: f32 +) -> RawServoAnimationValueStrong { + Arc::new(AnimationValue::Opacity(opacity)).into_strong() +} + +#[no_mangle] +pub extern "C" fn Servo_AnimationValue_GetTransform( + value: RawServoAnimationValueBorrowed, + list: *mut structs::RefPtr +) { let value = AnimationValue::as_arc(&value); if let AnimationValue::Transform(ref servo_list) = **value { let list = unsafe { &mut *list }; @@ -665,6 +731,15 @@ pub extern "C" fn Servo_AnimationValue_GetTransform(value: RawServoAnimationValu } } +#[no_mangle] +pub extern "C" fn Servo_AnimationValue_Transform( + list: *const nsCSSValueSharedList +) -> RawServoAnimationValueStrong { + let list = unsafe { (&*list).mHead.as_ref() }; + let transform = style_structs::Box::clone_transform_from_list(list); + Arc::new(AnimationValue::Transform(transform)).into_strong() +} + #[no_mangle] pub extern "C" fn Servo_AnimationValue_DeepEqual(this: RawServoAnimationValueBorrowed, other: RawServoAnimationValueBorrowed)