Auto merge of #17202 - BorisChiou:stylo/animation/mismatched_transform, r=Manishearth,birtles

stylo: Bug 1335998 - Handle interpolation and accumulation of mismatched transform lists

These are the interdependent patches of Bug 1335998. We want to do interpolation and accumulation for mismatched transform lists, so introduce ComputedOperation::InterpolateMatrix and ComputedOperation::Accumulation. Both arms store the from_list and to_list, and resolve them until we have the layout information. For the Servo part, we haven't implemented how to read the transform lists in layout/fragment.rs, but I think it would be easy. (related issue #13267)

---
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix [Bug 1335998](https://bugzilla.mozilla.org/show_bug.cgi?id=1335998)
- [X] There are tests for these changes

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17202)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-06-06 23:54:41 -07:00 committed by GitHub
commit f4a720483d
10 changed files with 4840 additions and 4085 deletions

View file

@ -2896,6 +2896,12 @@ impl Fragment {
Matrix4D::create_skew(Radians::new(theta_x.radians()), Matrix4D::create_skew(Radians::new(theta_x.radians()),
Radians::new(theta_y.radians())) Radians::new(theta_y.radians()))
} }
transform::ComputedOperation::InterpolateMatrix { .. } |
transform::ComputedOperation::AccumulateMatrix { .. } => {
// TODO: Convert InterpolateMatrix/AccmulateMatrix into a valid Matrix4D by
// the reference box.
Matrix4D::identity()
}
}; };
transform = transform.pre_mul(&matrix); transform = transform.pre_mul(&matrix);

View file

@ -1,6 +1,7 @@
/* automatically generated by rust-bindgen */ /* automatically generated by rust-bindgen */
pub use nsstring::{nsACString, nsAString, nsString, nsStringRepr}; pub use nsstring::{nsACString, nsAString, nsString, nsStringRepr};
use gecko_bindings::structs::nsStyleTransformMatrix;
use gecko_bindings::structs::nsTArray; use gecko_bindings::structs::nsTArray;
type nsACString_internal = nsACString; type nsACString_internal = nsACString;
type nsAString_internal = nsAString; type nsAString_internal = nsAString;
@ -209,6 +210,10 @@ use gecko_bindings::structs::ParsingMode;
use gecko_bindings::structs::InheritTarget; use gecko_bindings::structs::InheritTarget;
use gecko_bindings::structs::URLMatchingFunction; use gecko_bindings::structs::URLMatchingFunction;
use gecko_bindings::structs::StyleRuleInclusion; use gecko_bindings::structs::StyleRuleInclusion;
use gecko_bindings::structs::nsStyleTransformMatrix::MatrixTransformOperator;
unsafe impl Send for nsStyleTransformMatrix::MatrixTransformOperator {}
unsafe impl Sync for nsStyleTransformMatrix::MatrixTransformOperator {}
use gecko_bindings::structs::RawGeckoGfxMatrix4x4;
pub type nsTArrayBorrowed_uintptr_t<'a> = &'a mut ::gecko_bindings::structs::nsTArray<usize>; pub type nsTArrayBorrowed_uintptr_t<'a> = &'a mut ::gecko_bindings::structs::nsTArray<usize>;
pub type RawServoStyleSetOwned = ::gecko_bindings::sugar::ownership::Owned<RawServoStyleSet>; pub type RawServoStyleSetOwned = ::gecko_bindings::sugar::ownership::Owned<RawServoStyleSet>;
pub type RawServoStyleSetOwnedOrNull = ::gecko_bindings::sugar::ownership::OwnedOrNull<RawServoStyleSet>; pub type RawServoStyleSetOwnedOrNull = ::gecko_bindings::sugar::ownership::OwnedOrNull<RawServoStyleSet>;
@ -1310,6 +1315,10 @@ extern "C" {
pub fn Gecko_CSSValue_SetPairList(css_value: nsCSSValueBorrowedMut, pub fn Gecko_CSSValue_SetPairList(css_value: nsCSSValueBorrowedMut,
len: u32); len: u32);
} }
extern "C" {
pub fn Gecko_CSSValue_InitSharedList(css_value: nsCSSValueBorrowedMut,
len: u32);
}
extern "C" { extern "C" {
pub fn Gecko_CSSValue_Drop(css_value: nsCSSValueBorrowedMut); pub fn Gecko_CSSValue_Drop(css_value: nsCSSValueBorrowedMut);
} }
@ -2179,6 +2188,15 @@ extern "C" {
arg3: arg3:
nsCSSPropertyIDSetBorrowedMut); nsCSSPropertyIDSetBorrowedMut);
} }
extern "C" {
pub fn Servo_MatrixTransform_Operate(matrix_operator:
MatrixTransformOperator,
from: *const RawGeckoGfxMatrix4x4,
to: *const RawGeckoGfxMatrix4x4,
progress: f64,
result:
*mut RawGeckoGfxMatrix4x4);
}
extern "C" { extern "C" {
pub fn Servo_AnimationValues_Interpolate(from: pub fn Servo_AnimationValues_Interpolate(from:
RawServoAnimationValueBorrowed, RawServoAnimationValueBorrowed,

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -8,8 +8,9 @@ use app_units::Au;
use gecko_bindings::bindings; use gecko_bindings::bindings;
use gecko_bindings::structs; use gecko_bindings::structs;
use gecko_bindings::structs::{nsCSSValue, nsCSSUnit}; use gecko_bindings::structs::{nsCSSValue, nsCSSUnit};
use gecko_bindings::structs::{nsCSSValue_Array, nscolor}; use gecko_bindings::structs::{nsCSSValue_Array, nsCSSValueList, nscolor};
use gecko_string_cache::Atom; use gecko_string_cache::Atom;
use std::marker::PhantomData;
use std::mem; use std::mem;
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::slice; use std::slice;
@ -225,19 +226,17 @@ impl nsCSSValue {
/// Set to a list value /// Set to a list value
/// ///
/// This is only supported on the main thread. /// This is only supported on the main thread.
pub fn set_list<I>(&mut self, mut values: I) where I: ExactSizeIterator<Item=nsCSSValue> { pub fn set_list<I>(&mut self, values: I) where I: ExactSizeIterator<Item=nsCSSValue> {
debug_assert!(values.len() > 0, "Empty list is not supported"); debug_assert!(values.len() > 0, "Empty list is not supported");
unsafe { bindings::Gecko_CSSValue_SetList(self, values.len() as u32); } unsafe { bindings::Gecko_CSSValue_SetList(self, values.len() as u32); }
debug_assert_eq!(self.mUnit, nsCSSUnit::eCSSUnit_List); debug_assert_eq!(self.mUnit, nsCSSUnit::eCSSUnit_List);
let mut item_ptr = &mut unsafe { let list: &mut structs::nsCSSValueList = &mut unsafe {
self.mValue.mList.as_ref() // &*nsCSSValueList_heap self.mValue.mList.as_ref() // &*nsCSSValueList_heap
.as_mut().expect("List pointer should be non-null") .as_mut().expect("List pointer should be non-null")
}._base as *mut structs::nsCSSValueList; }._base;
while let Some(item) = unsafe { item_ptr.as_mut() } { for (item, new_value) in list.into_iter().zip(values) {
item.mValue = values.next().expect("Values shouldn't have been exhausted"); *item = new_value;
item_ptr = item.mNext;
} }
debug_assert!(values.next().is_none(), "Values should have been exhausted");
} }
/// Set to a pair list value /// Set to a pair list value
@ -260,6 +259,21 @@ impl nsCSSValue {
} }
debug_assert!(values.next().is_none(), "Values should have been exhausted"); debug_assert!(values.next().is_none(), "Values should have been exhausted");
} }
/// Set a shared list
pub fn set_shared_list<I>(&mut self, values: I) where I: ExactSizeIterator<Item=nsCSSValue> {
debug_assert!(values.len() > 0, "Empty list is not supported");
unsafe { bindings::Gecko_CSSValue_InitSharedList(self, values.len() as u32) };
debug_assert_eq!(self.mUnit, nsCSSUnit::eCSSUnit_SharedList);
let list = unsafe {
self.mValue.mSharedList.as_ref()
.as_mut().expect("List pointer should be non-null").mHead.as_mut()
};
debug_assert!(list.is_some(), "New created shared list shouldn't be null");
for (item, new_value) in list.unwrap().into_iter().zip(values) {
*item = new_value;
}
}
} }
impl Drop for nsCSSValue { impl Drop for nsCSSValue {
@ -268,6 +282,64 @@ impl Drop for nsCSSValue {
} }
} }
/// Iterator of nsCSSValueList.
#[allow(non_camel_case_types)]
pub struct nsCSSValueListIterator<'a> {
current: Option<&'a nsCSSValueList>,
}
impl<'a> Iterator for nsCSSValueListIterator<'a> {
type Item = &'a nsCSSValue;
fn next(&mut self) -> Option<Self::Item> {
match self.current {
Some(item) => {
self.current = unsafe { item.mNext.as_ref() };
Some(&item.mValue)
},
None => None
}
}
}
impl<'a> IntoIterator for &'a nsCSSValueList {
type Item = &'a nsCSSValue;
type IntoIter = nsCSSValueListIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
nsCSSValueListIterator { current: Some(self) }
}
}
/// Mutable Iterator of nsCSSValueList.
#[allow(non_camel_case_types)]
pub struct nsCSSValueListMutIterator<'a> {
current: *mut nsCSSValueList,
phantom: PhantomData<&'a mut nsCSSValue>,
}
impl<'a> Iterator for nsCSSValueListMutIterator<'a> {
type Item = &'a mut nsCSSValue;
fn next(&mut self) -> Option<Self::Item> {
match unsafe { self.current.as_mut() } {
Some(item) => {
self.current = item.mNext;
Some(&mut item.mValue)
},
None => None
}
}
}
impl<'a> IntoIterator for &'a mut nsCSSValueList {
type Item = &'a mut nsCSSValue;
type IntoIter = nsCSSValueListMutIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
nsCSSValueListMutIterator { current: self as *mut nsCSSValueList,
phantom: PhantomData }
}
}
impl nsCSSValue_Array { impl nsCSSValue_Array {
/// Return the length of this `nsCSSValue::Array` /// Return the length of this `nsCSSValue::Array`
#[inline] #[inline]

View file

@ -2286,30 +2286,44 @@ fn static_assert() {
single_patterns = ["m%s: %s" % (str(a / 4 + 1) + str(a % 4 + 1), b + str(a + 1)) for (a, b) single_patterns = ["m%s: %s" % (str(a / 4 + 1) + str(a % 4 + 1), b + str(a + 1)) for (a, b)
in enumerate(items)] in enumerate(items)]
if name == "Matrix": if name == "Matrix":
pattern = "ComputedMatrix { %s }" % ", ".join(single_patterns) pattern = "(ComputedMatrix { %s })" % ", ".join(single_patterns)
else: else:
pattern = "ComputedMatrixWithPercents { %s }" % ", ".join(single_patterns) pattern = "(ComputedMatrixWithPercents { %s })" % ", ".join(single_patterns)
elif keyword == "interpolatematrix":
pattern = " { from_list: ref list1, to_list: ref list2, progress: percentage3 }"
elif keyword == "accumulatematrix":
pattern = " { from_list: ref list1, to_list: ref list2, count: integer_to_percentage3 }"
else: else:
# Generate contents of pattern from items # Generate contents of pattern from items
pattern = ", ".join([b + str(a+1) for (a,b) in enumerate(items)]) pattern = "(%s)" % ", ".join([b + str(a+1) for (a,b) in enumerate(items)])
# First %s substituted with the call to GetArrayItem, the second # First %s substituted with the call to GetArrayItem, the second
# %s substituted with the corresponding variable # %s substituted with the corresponding variable
css_value_setters = { css_value_setters = {
"length" : "bindings::Gecko_CSSValue_SetAbsoluteLength(%s, %s.0)", "length" : "bindings::Gecko_CSSValue_SetAbsoluteLength(%s, %s.0)",
"percentage" : "bindings::Gecko_CSSValue_SetPercentage(%s, %s)", "percentage" : "bindings::Gecko_CSSValue_SetPercentage(%s, %s.0)",
# Note: This is an integer type, but we use it as a percentage value in Gecko, so
# need to cast it to f32.
"integer_to_percentage" : "bindings::Gecko_CSSValue_SetPercentage(%s, %s as f32)",
"lop" : "%s.set_lop(%s)", "lop" : "%s.set_lop(%s)",
"angle" : "%s.set_angle(%s)", "angle" : "%s.set_angle(%s)",
"number" : "bindings::Gecko_CSSValue_SetNumber(%s, %s)", "number" : "bindings::Gecko_CSSValue_SetNumber(%s, %s)",
# Note: We use nsCSSValueSharedList here, instead of nsCSSValueList_heap
# because this function is not called on the main thread and
# nsCSSValueList_heap is not thread safe.
"list" : "%s.set_shared_list(%s.0.as_ref().unwrap().into_iter().map(&convert_to_ns_css_value));",
} }
%> %>
longhands::transform::computed_value::ComputedOperation::${name}(${pattern}) => { longhands::transform::computed_value::ComputedOperation::${name}${pattern} => {
bindings::Gecko_CSSValue_SetFunction(gecko_value, ${len(items) + 1}); bindings::Gecko_CSSValue_SetFunction(gecko_value, ${len(items) + 1});
bindings::Gecko_CSSValue_SetKeyword( bindings::Gecko_CSSValue_SetKeyword(
bindings::Gecko_CSSValue_GetArrayItem(gecko_value, 0), bindings::Gecko_CSSValue_GetArrayItem(gecko_value, 0),
eCSSKeyword_${keyword} structs::nsCSSKeyword::eCSSKeyword_${keyword}
); );
% for index, item in enumerate(items): % for index, item in enumerate(items):
% if item == "list":
debug_assert!(${item}${index + 1}.0.is_some());
% endif
${css_value_setters[item] % ( ${css_value_setters[item] % (
"bindings::Gecko_CSSValue_GetArrayItem(gecko_value, %d)" % (index + 1), "bindings::Gecko_CSSValue_GetArrayItem(gecko_value, %d)" % (index + 1),
item + str(index + 1) item + str(index + 1)
@ -2317,40 +2331,50 @@ fn static_assert() {
% endfor % endfor
} }
</%def> </%def>
pub fn convert_transform(input: &[longhands::transform::computed_value::ComputedOperation], fn set_single_transform_function(servo_value: &longhands::transform::computed_value::ComputedOperation,
output: &mut structs::root::RefPtr<structs::root::nsCSSValueSharedList>) { gecko_value: &mut structs::nsCSSValue /* output */) {
use gecko_bindings::structs::nsCSSKeyword::*;
use gecko_bindings::sugar::refptr::RefPtr;
use properties::longhands::transform::computed_value::ComputedMatrix; use properties::longhands::transform::computed_value::ComputedMatrix;
use properties::longhands::transform::computed_value::ComputedMatrixWithPercents; use properties::longhands::transform::computed_value::ComputedMatrixWithPercents;
use properties::longhands::transform::computed_value::ComputedOperation;
let convert_to_ns_css_value = |item: &ComputedOperation| -> structs::nsCSSValue {
let mut value = structs::nsCSSValue::null();
Self::set_single_transform_function(item, &mut value);
value
};
unsafe {
match *servo_value {
${transform_function_arm("Matrix", "matrix3d", ["number"] * 16)}
${transform_function_arm("MatrixWithPercents", "matrix3d", ["number"] * 12 + ["lop"] * 2
+ ["length"] + ["number"])}
${transform_function_arm("Skew", "skew", ["angle"] * 2)}
${transform_function_arm("Translate", "translate3d", ["lop", "lop", "length"])}
${transform_function_arm("Scale", "scale3d", ["number"] * 3)}
${transform_function_arm("Rotate", "rotate3d", ["number"] * 3 + ["angle"])}
${transform_function_arm("Perspective", "perspective", ["length"])}
${transform_function_arm("InterpolateMatrix", "interpolatematrix",
["list"] * 2 + ["percentage"])}
${transform_function_arm("AccumulateMatrix", "accumulatematrix",
["list"] * 2 + ["integer_to_percentage"])}
}
}
}
pub fn convert_transform(input: &[longhands::transform::computed_value::ComputedOperation],
output: &mut structs::root::RefPtr<structs::root::nsCSSValueSharedList>) {
use gecko_bindings::sugar::refptr::RefPtr;
unsafe { output.clear() }; unsafe { output.clear() };
let list = unsafe { let list = unsafe {
RefPtr::from_addrefed(bindings::Gecko_NewCSSValueSharedList(input.len() as u32)) RefPtr::from_addrefed(bindings::Gecko_NewCSSValueSharedList(input.len() as u32))
}; };
let value_list = unsafe { list.mHead.as_mut() };
let mut cur = list.mHead; if let Some(value_list) = value_list {
let mut iter = input.into_iter(); for (gecko, servo) in value_list.into_iter().zip(input.into_iter()) {
while !cur.is_null() { Self::set_single_transform_function(servo, gecko);
let gecko_value = unsafe { &mut (*cur).mValue };
let servo = iter.next().expect("Gecko_NewCSSValueSharedList should create a shared \
value list of the same length as the transform vector");
unsafe {
match *servo {
${transform_function_arm("Matrix", "matrix3d", ["number"] * 16)}
${transform_function_arm("MatrixWithPercents", "matrix3d", ["number"] * 12 + ["lop"] * 2
+ ["length"] + ["number"])}
${transform_function_arm("Skew", "skew", ["angle"] * 2)}
${transform_function_arm("Translate", "translate3d", ["lop", "lop", "length"])}
${transform_function_arm("Scale", "scale3d", ["number"] * 3)}
${transform_function_arm("Rotate", "rotate3d", ["number"] * 3 + ["angle"])}
${transform_function_arm("Perspective", "perspective", ["length"])}
}
cur = (*cur).mNext;
} }
} }
debug_assert!(iter.next().is_none());
unsafe { output.set_move(list) }; unsafe { output.set_move(list) };
} }
@ -2378,60 +2402,94 @@ fn static_assert() {
"lop" : "%s.get_lop()", "lop" : "%s.get_lop()",
"angle" : "%s.get_angle()", "angle" : "%s.get_angle()",
"number" : "bindings::Gecko_CSSValue_GetNumber(%s)", "number" : "bindings::Gecko_CSSValue_GetNumber(%s)",
"percentage" : "Percentage(bindings::Gecko_CSSValue_GetPercentage(%s))",
"percentage_to_integer" : "bindings::Gecko_CSSValue_GetPercentage(%s) as i32",
"list" : "TransformList(Some(convert_shared_list_to_operations(%s)))",
} }
pre_symbols = "("
post_symbols = ")"
if keyword == "interpolatematrix" or keyword == "accumulatematrix":
# We generate this like: "ComputedOperation::InterpolateMatrix {", so the space is
# between "InterpolateMatrix"/"AccumulateMatrix" and '{'
pre_symbols = " {"
post_symbols = "}"
elif keyword == "matrix3d":
pre_symbols = "(ComputedMatrix {"
post_symbols = "})"
field_names = None
if keyword == "interpolatematrix":
field_names = ["from_list", "to_list", "progress"]
elif keyword == "accumulatematrix":
field_names = ["from_list", "to_list", "count"]
%> %>
eCSSKeyword_${keyword} => { structs::nsCSSKeyword::eCSSKeyword_${keyword} => {
ComputedOperation::${name}( ComputedOperation::${name}${pre_symbols}
% if keyword == "matrix3d":
ComputedMatrix {
% endif
% for index, item in enumerate(items): % for index, item in enumerate(items):
% if keyword == "matrix3d": % if keyword == "matrix3d":
m${index / 4 + 1}${index % 4 + 1}: m${index / 4 + 1}${index % 4 + 1}:
% elif keyword == "interpolatematrix" or keyword == "accumulatematrix":
${field_names[index]}:
% endif % endif
${css_value_getters[item] % ( ${css_value_getters[item] % (
"bindings::Gecko_CSSValue_GetArrayItemConst(gecko_value, %d)" % (index + 1) "bindings::Gecko_CSSValue_GetArrayItemConst(gecko_value, %d)" % (index + 1)
)}, )},
% endfor % endfor
% if keyword == "matrix3d": ${post_symbols}
}
% endif
)
}, },
</%def> </%def>
pub fn clone_transform(&self) -> longhands::transform::computed_value::T { fn clone_single_transform_function(gecko_value: &structs::nsCSSValue)
use app_units::Au; -> longhands::transform::computed_value::ComputedOperation {
use gecko_bindings::structs::nsCSSKeyword::*;
use properties::longhands::transform::computed_value;
use properties::longhands::transform::computed_value::ComputedMatrix; use properties::longhands::transform::computed_value::ComputedMatrix;
use properties::longhands::transform::computed_value::ComputedOperation; use properties::longhands::transform::computed_value::ComputedOperation;
use properties::longhands::transform::computed_value::T as TransformList;
use values::computed::Percentage;
let convert_shared_list_to_operations = |value: &structs::nsCSSValue|
-> Vec<ComputedOperation> {
debug_assert!(value.mUnit == structs::nsCSSUnit::eCSSUnit_SharedList);
let value_list = unsafe {
value.mValue.mSharedList.as_ref()
.as_mut().expect("List pointer should be non-null").mHead.as_ref()
};
debug_assert!(value_list.is_some(), "An empty shared list is not allowed");
value_list.unwrap().into_iter()
.map(|item| Self::clone_single_transform_function(item))
.collect()
};
let transform_function = unsafe {
bindings::Gecko_CSSValue_GetKeyword(bindings::Gecko_CSSValue_GetArrayItemConst(gecko_value, 0))
};
unsafe {
match transform_function {
${computed_operation_arm("Matrix", "matrix3d", ["number"] * 16)}
${computed_operation_arm("Skew", "skew", ["angle"] * 2)}
${computed_operation_arm("Translate", "translate3d", ["lop", "lop", "length"])}
${computed_operation_arm("Scale", "scale3d", ["number"] * 3)}
${computed_operation_arm("Rotate", "rotate3d", ["number"] * 3 + ["angle"])}
${computed_operation_arm("Perspective", "perspective", ["length"])}
${computed_operation_arm("InterpolateMatrix", "interpolatematrix",
["list"] * 2 + ["percentage"])}
${computed_operation_arm("AccumulateMatrix", "accumulatematrix",
["list"] * 2 + ["percentage_to_integer"])}
_ => panic!("We shouldn't set any other transform function types"),
}
}
}
pub fn clone_transform(&self) -> longhands::transform::computed_value::T {
use properties::longhands::transform::computed_value;
if self.gecko.mSpecifiedTransform.mRawPtr.is_null() { if self.gecko.mSpecifiedTransform.mRawPtr.is_null() {
return computed_value::T(None); return computed_value::T(None);
} }
let list = unsafe { (*self.gecko.mSpecifiedTransform.to_safe().get()).mHead.as_ref() };
let mut result = vec![]; let result = list.map(|list| {
let mut cur = unsafe { (*self.gecko.mSpecifiedTransform.to_safe().get()).mHead }; list.into_iter()
while !cur.is_null() { .map(|value| Self::clone_single_transform_function(value))
let gecko_value = unsafe { &(*cur).mValue }; .collect()
let transform_function = unsafe { });
bindings::Gecko_CSSValue_GetKeyword(bindings::Gecko_CSSValue_GetArrayItemConst(gecko_value, 0)) computed_value::T(result)
};
let servo = unsafe {
match transform_function {
${computed_operation_arm("Matrix", "matrix3d", ["number"] * 16)}
${computed_operation_arm("Skew", "skew", ["angle"] * 2)}
${computed_operation_arm("Translate", "translate3d", ["lop", "lop", "length"])}
${computed_operation_arm("Scale", "scale3d", ["number"] * 3)}
${computed_operation_arm("Rotate", "rotate3d", ["number"] * 3 + ["angle"])}
${computed_operation_arm("Perspective", "perspective", ["length"])}
_ => panic!("We shouldn't set any other transform function types"),
}
};
result.push(servo);
unsafe { cur = (&*cur).mNext };
}
computed_value::T(Some(result))
} }
${impl_transition_time_value('delay', 'Delay')} ${impl_transition_time_value('delay', 'Delay')}

View file

@ -10,6 +10,7 @@ use app_units::Au;
use cssparser::{Color as CSSParserColor, Parser, RGBA, serialize_identifier}; use cssparser::{Color as CSSParserColor, Parser, RGBA, serialize_identifier};
use euclid::{Point2D, Size2D}; use euclid::{Point2D, Size2D};
#[cfg(feature = "gecko")] use gecko_bindings::bindings::RawServoAnimationValueMap; #[cfg(feature = "gecko")] use gecko_bindings::bindings::RawServoAnimationValueMap;
#[cfg(feature = "gecko")] use gecko_bindings::structs::RawGeckoGfxMatrix4x4;
#[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID; #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
#[cfg(feature = "gecko")] use gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI}; #[cfg(feature = "gecko")] use gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI};
#[cfg(feature = "gecko")] use gecko_string_cache::Atom; #[cfg(feature = "gecko")] use gecko_string_cache::Atom;
@ -637,7 +638,31 @@ impl Animatable for AnimationValue {
% endif % endif
% endfor % endfor
_ => { _ => {
panic!("Expected weighted addition of computed values of the same \ panic!("Expected addition of computed values of the same \
property, got: {:?}, {:?}", self, other);
}
}
}
fn accumulate(&self, other: &Self, count: u64) -> Result<Self, ()> {
match (self, other) {
% for prop in data.longhands:
% if prop.animatable:
% if prop.animation_value_type == "discrete":
(&AnimationValue::${prop.camel_case}(_),
&AnimationValue::${prop.camel_case}(_)) => {
Err(())
}
% else:
(&AnimationValue::${prop.camel_case}(ref from),
&AnimationValue::${prop.camel_case}(ref to)) => {
from.accumulate(to, count).map(AnimationValue::${prop.camel_case})
}
% endif
% endif
% endfor
_ => {
panic!("Expected accumulation of computed values of the same \
property, got: {:?}, {:?}", self, other); property, got: {:?}, {:?}", self, other);
} }
} }
@ -1510,11 +1535,21 @@ fn build_identity_transform_list(list: &[TransformOperation]) -> Vec<TransformOp
TransformOperation::Rotate(..) => { TransformOperation::Rotate(..) => {
result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle::zero())); result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle::zero()));
} }
TransformOperation::Perspective(..) => { TransformOperation::Perspective(..) |
TransformOperation::AccumulateMatrix { .. } => {
// Perspective: We convert a perspective function into an equivalent
// ComputedMatrix, and then decompose/interpolate/recompose these matrices.
// AccumulateMatrix: We do interpolation on AccumulateMatrix by reading it as a
// ComputedMatrix (with layout information), and then do matrix interpolation.
//
// Therefore, we use an identity matrix to represent the identity transform list.
// http://dev.w3.org/csswg/css-transforms/#identity-transform-function // http://dev.w3.org/csswg/css-transforms/#identity-transform-function
let identity = ComputedMatrix::identity(); let identity = ComputedMatrix::identity();
result.push(TransformOperation::Matrix(identity)); result.push(TransformOperation::Matrix(identity));
} }
TransformOperation::InterpolateMatrix { .. } => {
panic!("Building the identity matrix for InterpolateMatrix is not supported");
}
} }
} }
@ -1616,8 +1651,13 @@ fn add_weighted_transform_lists(from_list: &[TransformOperation],
} }
} }
} else { } else {
// TODO(gw): Implement matrix decomposition and interpolation use values::specified::Percentage;
result.extend_from_slice(from_list); let from_transform_list = TransformList(Some(from_list.to_vec()));
let to_transform_list = TransformList(Some(to_list.to_vec()));
result.push(
TransformOperation::InterpolateMatrix { from_list: from_transform_list,
to_list: to_transform_list,
progress: Percentage(other_portion as f32) });
} }
TransformList(Some(result)) TransformList(Some(result))
@ -1889,6 +1929,28 @@ impl From<MatrixDecomposed2D> for ComputedMatrix {
} }
} }
#[cfg(feature = "gecko")]
impl<'a> From< &'a RawGeckoGfxMatrix4x4> for ComputedMatrix {
fn from(m: &'a RawGeckoGfxMatrix4x4) -> ComputedMatrix {
ComputedMatrix {
m11: m[0], m12: m[1], m13: m[2], m14: m[3],
m21: m[4], m22: m[5], m23: m[6], m24: m[7],
m31: m[8], m32: m[9], m33: m[10], m34: m[11],
m41: m[12], m42: m[13], m43: m[14], m44: m[15],
}
}
}
#[cfg(feature = "gecko")]
impl From<ComputedMatrix> for RawGeckoGfxMatrix4x4 {
fn from(matrix: ComputedMatrix) -> RawGeckoGfxMatrix4x4 {
[ matrix.m11, matrix.m12, matrix.m13, matrix.m14,
matrix.m21, matrix.m22, matrix.m23, matrix.m24,
matrix.m31, matrix.m32, matrix.m33, matrix.m34,
matrix.m41, matrix.m42, matrix.m43, matrix.m44 ]
}
}
/// A 3d translation. /// A 3d translation.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@ -2462,6 +2524,41 @@ impl Animatable for TransformList {
} }
} }
#[inline]
fn accumulate(&self, other: &Self, count: u64) -> Result<Self, ()> {
match (&self.0, &other.0) {
(&Some(ref from_list), &Some(ref to_list)) => {
if can_interpolate_list(from_list, to_list) {
Ok(add_weighted_transform_lists(from_list, &to_list, count as f64, 1.0))
} else {
use std::i32;
let result = vec![TransformOperation::AccumulateMatrix {
from_list: self.clone(),
to_list: other.clone(),
count: cmp::min(count, i32::MAX as u64) as i32
}];
Ok(TransformList(Some(result)))
}
}
(&Some(ref from_list), &None) => {
Ok(add_weighted_transform_lists(from_list, from_list, count as f64, 0.0))
}
(&None, &Some(_)) => {
// If |self| is 'none' then we are calculating:
//
// none * |count| + |other|
// = none + |other|
// = |other|
//
// Hence the result is just |other|.
Ok(other.clone())
}
_ => {
Ok(TransformList(None))
}
}
}
#[inline] #[inline]
fn get_zero_value(&self) -> Option<Self> { Some(TransformList(None)) } fn get_zero_value(&self) -> Option<Self> { Some(TransformList(None)) }
} }

View file

@ -688,7 +688,7 @@ ${helpers.predefined_type("scroll-snap-coordinate",
use app_units::Au; use app_units::Au;
use values::computed::{LengthOrPercentageOrNumber as ComputedLoPoNumber, LengthOrNumber as ComputedLoN}; use values::computed::{LengthOrPercentageOrNumber as ComputedLoPoNumber, LengthOrNumber as ComputedLoN};
use values::computed::{LengthOrPercentage as ComputedLoP, Length as ComputedLength}; use values::computed::{LengthOrPercentage as ComputedLoP, Length as ComputedLength};
use values::specified::{Angle, Length, LengthOrPercentage}; use values::specified::{Angle, Integer, Length, LengthOrPercentage, Percentage};
use values::specified::{LengthOrNumber, LengthOrPercentageOrNumber as LoPoNumber, Number}; use values::specified::{LengthOrNumber, LengthOrPercentageOrNumber as LoPoNumber, Number};
use style_traits::ToCss; use style_traits::ToCss;
use style_traits::values::Css; use style_traits::values::Css;
@ -699,7 +699,7 @@ ${helpers.predefined_type("scroll-snap-coordinate",
use app_units::Au; use app_units::Au;
use values::CSSFloat; use values::CSSFloat;
use values::computed; use values::computed;
use values::computed::{Length, LengthOrPercentage}; use values::computed::{Length, LengthOrPercentage, Percentage};
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@ -756,6 +756,24 @@ ${helpers.predefined_type("scroll-snap-coordinate",
Scale(CSSFloat, CSSFloat, CSSFloat), Scale(CSSFloat, CSSFloat, CSSFloat),
Rotate(CSSFloat, CSSFloat, CSSFloat, computed::Angle), Rotate(CSSFloat, CSSFloat, CSSFloat, computed::Angle),
Perspective(computed::Length), Perspective(computed::Length),
// For mismatched transform lists.
// A vector of |ComputedOperation| could contain an |InterpolateMatrix| and other
// |ComputedOperation|s, and multiple nested |InterpolateMatrix|s is acceptable.
// e.g.
// [ InterpolateMatrix { from_list: [ InterpolateMatrix { ... },
// Scale(...) ],
// to_list: [ AccumulateMatrix { from_list: ...,
// to_list: [ InterpolateMatrix,
// ... ],
// count: ... } ],
// progress: ... } ]
InterpolateMatrix { from_list: T,
to_list: T,
progress: Percentage },
// For accumulate operation of mismatched transform lists.
AccumulateMatrix { from_list: T,
to_list: T,
count: computed::Integer },
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -835,6 +853,14 @@ ${helpers.predefined_type("scroll-snap-coordinate",
/// ///
/// The value must be greater than or equal to zero. /// The value must be greater than or equal to zero.
Perspective(specified::Length), Perspective(specified::Length),
/// A intermediate type for interpolation of mismatched transform lists.
InterpolateMatrix { from_list: SpecifiedValue,
to_list: SpecifiedValue,
progress: Percentage },
/// A intermediate type for accumulation of mismatched transform lists.
AccumulateMatrix { from_list: SpecifiedValue,
to_list: SpecifiedValue,
count: Integer },
} }
impl ToCss for computed_value::T { impl ToCss for computed_value::T {
@ -899,6 +925,7 @@ ${helpers.predefined_type("scroll-snap-coordinate",
dest, "rotate3d({}, {}, {}, {})", dest, "rotate3d({}, {}, {}, {})",
Css(x), Css(y), Css(z), Css(theta)), Css(x), Css(y), Css(z), Css(theta)),
Perspective(ref length) => write!(dest, "perspective({})", Css(length)), Perspective(ref length) => write!(dest, "perspective({})", Css(length)),
_ => unreachable!(),
} }
} }
} }
@ -1440,6 +1467,20 @@ ${helpers.predefined_type("scroll-snap-coordinate",
Perspective(ref d) => { Perspective(ref d) => {
result.push(computed_value::ComputedOperation::Perspective(d.to_computed_value(context))); result.push(computed_value::ComputedOperation::Perspective(d.to_computed_value(context)));
} }
InterpolateMatrix { ref from_list, ref to_list, progress } => {
result.push(computed_value::ComputedOperation::InterpolateMatrix {
from_list: from_list.to_computed_value(context),
to_list: to_list.to_computed_value(context),
progress: progress
});
}
AccumulateMatrix { ref from_list, ref to_list, count } => {
result.push(computed_value::ComputedOperation::AccumulateMatrix {
from_list: from_list.to_computed_value(context),
to_list: to_list.to_computed_value(context),
count: count.value()
});
}
}; };
} }
@ -1523,6 +1564,24 @@ ${helpers.predefined_type("scroll-snap-coordinate",
ToComputedValue::from_computed_value(d) ToComputedValue::from_computed_value(d)
)); ));
} }
computed_value::ComputedOperation::InterpolateMatrix { ref from_list,
ref to_list,
progress } => {
result.push(SpecifiedOperation::InterpolateMatrix {
from_list: SpecifiedValue::from_computed_value(from_list),
to_list: SpecifiedValue::from_computed_value(to_list),
progress: progress
});
}
computed_value::ComputedOperation::AccumulateMatrix { ref from_list,
ref to_list,
count } => {
result.push(SpecifiedOperation::AccumulateMatrix {
from_list: SpecifiedValue::from_computed_value(from_list),
to_list: SpecifiedValue::from_computed_value(to_list),
count: Integer::new(count)
});
}
}; };
} }
result result

View file

@ -72,12 +72,14 @@ use style::gecko_bindings::structs::{nsCSSFontFaceRule, nsCSSCounterStyleRule};
use style::gecko_bindings::structs::{nsRestyleHint, nsChangeHint, PropertyValuePair}; use style::gecko_bindings::structs::{nsRestyleHint, nsChangeHint, PropertyValuePair};
use style::gecko_bindings::structs::IterationCompositeOperation; use style::gecko_bindings::structs::IterationCompositeOperation;
use style::gecko_bindings::structs::MallocSizeOf; use style::gecko_bindings::structs::MallocSizeOf;
use style::gecko_bindings::structs::RawGeckoGfxMatrix4x4;
use style::gecko_bindings::structs::RawGeckoPresContextOwned; use style::gecko_bindings::structs::RawGeckoPresContextOwned;
use style::gecko_bindings::structs::ServoElementSnapshotTable; use style::gecko_bindings::structs::ServoElementSnapshotTable;
use style::gecko_bindings::structs::StyleRuleInclusion; use style::gecko_bindings::structs::StyleRuleInclusion;
use style::gecko_bindings::structs::URLExtraData; use style::gecko_bindings::structs::URLExtraData;
use style::gecko_bindings::structs::nsCSSValueSharedList; use style::gecko_bindings::structs::nsCSSValueSharedList;
use style::gecko_bindings::structs::nsCompatibility; use style::gecko_bindings::structs::nsCompatibility;
use style::gecko_bindings::structs::nsStyleTransformMatrix::MatrixTransformOperator;
use style::gecko_bindings::structs::nsresult; use style::gecko_bindings::structs::nsresult;
use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasFFI, HasArcFFI, HasBoxFFI}; use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasFFI, HasArcFFI, HasBoxFFI};
use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong}; use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong};
@ -1594,6 +1596,28 @@ pub extern "C" fn Servo_GetProperties_Overriding_Animation(element: RawGeckoElem
} }
} }
#[no_mangle]
pub extern "C" fn Servo_MatrixTransform_Operate(matrix_operator: MatrixTransformOperator,
from: *const RawGeckoGfxMatrix4x4,
to: *const RawGeckoGfxMatrix4x4,
progress: f64,
output: *mut RawGeckoGfxMatrix4x4) {
use self::MatrixTransformOperator::{Accumulate, Interpolate};
use style::properties::longhands::transform::computed_value::ComputedMatrix;
let from = ComputedMatrix::from(unsafe { from.as_ref() }.expect("not a valid 'from' matrix"));
let to = ComputedMatrix::from(unsafe { to.as_ref() }.expect("not a valid 'to' matrix"));
let result = match matrix_operator {
Interpolate => from.interpolate(&to, progress),
Accumulate => from.accumulate(&to, progress as u64),
};
let output = unsafe { output.as_mut() }.expect("not a valid 'output' matrix");
if let Ok(result) = result {
*output = result.into();
};
}
#[no_mangle] #[no_mangle]
pub extern "C" fn Servo_ParseStyleAttribute(data: *const nsACString, pub extern "C" fn Servo_ParseStyleAttribute(data: *const nsACString,
raw_extra_data: *mut URLExtraData, raw_extra_data: *mut URLExtraData,

View file

@ -2,8 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use app_units::Au;
use cssparser::RGBA; use cssparser::RGBA;
use style::properties::animated_properties::{Animatable, IntermediateRGBA}; use style::properties::animated_properties::{Animatable, IntermediateRGBA};
use style::properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
use style::properties::longhands::transform::computed_value::T as TransformList;
fn interpolate_rgba(from: RGBA, to: RGBA, progress: f64) -> RGBA { fn interpolate_rgba(from: RGBA, to: RGBA, progress: f64) -> RGBA {
let from: IntermediateRGBA = from.into(); let from: IntermediateRGBA = from.into();
@ -11,6 +14,7 @@ fn interpolate_rgba(from: RGBA, to: RGBA, progress: f64) -> RGBA {
from.interpolate(&to, progress).unwrap().into() from.interpolate(&to, progress).unwrap().into()
} }
// Color
#[test] #[test]
fn test_rgba_color_interepolation_preserves_transparent() { fn test_rgba_color_interepolation_preserves_transparent() {
assert_eq!(interpolate_rgba(RGBA::transparent(), assert_eq!(interpolate_rgba(RGBA::transparent(),
@ -54,3 +58,95 @@ fn test_rgba_color_interepolation_out_of_range_clamped_2() {
RGBA::from_floats(0.0, 1.0, 0.0, 0.2), 1.5), RGBA::from_floats(0.0, 1.0, 0.0, 0.2), 1.5),
RGBA::from_floats(0.0, 0.0, 0.0, 0.0)); RGBA::from_floats(0.0, 0.0, 0.0, 0.0));
} }
// Transform
#[test]
fn test_transform_interpolation_on_translate() {
use style::values::computed::{CalcLengthOrPercentage, LengthOrPercentage};
let from = TransformList(Some(vec![
TransformOperation::Translate(LengthOrPercentage::Length(Au(0)),
LengthOrPercentage::Length(Au(100)),
Au(25))]));
let to = TransformList(Some(vec![
TransformOperation::Translate(LengthOrPercentage::Length(Au(100)),
LengthOrPercentage::Length(Au(0)),
Au(75))]));
assert_eq!(from.interpolate(&to, 0.5).unwrap(),
TransformList(Some(vec![
TransformOperation::Translate(LengthOrPercentage::Length(Au(50)),
LengthOrPercentage::Length(Au(50)),
Au(50))])));
let from = TransformList(Some(vec![
TransformOperation::Translate(LengthOrPercentage::Percentage(0.5),
LengthOrPercentage::Percentage(1.0),
Au(25))]));
let to = TransformList(Some(vec![
TransformOperation::Translate(LengthOrPercentage::Length(Au(100)),
LengthOrPercentage::Length(Au(50)),
Au(75))]));
assert_eq!(from.interpolate(&to, 0.5).unwrap(),
TransformList(Some(vec![
TransformOperation::Translate(LengthOrPercentage::Calc(
// calc(50px + 25%)
CalcLengthOrPercentage::new(Au(50),
Some(0.25))),
LengthOrPercentage::Calc(
// calc(25px + 50%)
CalcLengthOrPercentage::new(Au(25),
Some(0.5))),
Au(50))])));
}
#[test]
fn test_transform_interpolation_on_scale() {
let from = TransformList(Some(vec![TransformOperation::Scale(1.0, 2.0, 1.0)]));
let to = TransformList(Some(vec![TransformOperation::Scale(2.0, 4.0, 2.0)]));
assert_eq!(from.interpolate(&to, 0.5).unwrap(),
TransformList(Some(vec![TransformOperation::Scale(1.5, 3.0, 1.5)])));
}
#[test]
fn test_transform_interpolation_on_rotate() {
use style::values::computed::Angle;
let from = TransformList(Some(vec![TransformOperation::Rotate(0.0, 0.0, 1.0,
Angle::from_radians(0.0))]));
let to = TransformList(Some(vec![TransformOperation::Rotate(0.0, 0.0, 1.0,
Angle::from_radians(100.0))]));
assert_eq!(from.interpolate(&to, 0.5).unwrap(),
TransformList(Some(vec![TransformOperation::Rotate(0.0, 0.0, 1.0,
Angle::from_radians(50.0))])));
}
#[test]
fn test_transform_interpolation_on_skew() {
use style::values::computed::Angle;
let from = TransformList(Some(vec![TransformOperation::Skew(Angle::from_radians(0.0),
Angle::from_radians(100.0))]));
let to = TransformList(Some(vec![TransformOperation::Skew(Angle::from_radians(100.0),
Angle::from_radians(0.0))]));
assert_eq!(from.interpolate(&to, 0.5).unwrap(),
TransformList(Some(vec![TransformOperation::Skew(Angle::from_radians(50.0),
Angle::from_radians(50.0))])));
}
#[test]
fn test_transform_interpolation_on_mismatched_lists() {
use style::values::computed::{Angle, LengthOrPercentage, Percentage};
let from = TransformList(Some(vec![TransformOperation::Rotate(0.0, 0.0, 1.0,
Angle::from_radians(100.0))]));
let to = TransformList(Some(vec![
TransformOperation::Translate(LengthOrPercentage::Length(Au(100)),
LengthOrPercentage::Length(Au(0)),
Au(0))]));
assert_eq!(from.interpolate(&to, 0.5).unwrap(),
TransformList(Some(vec![TransformOperation::InterpolateMatrix {
from_list: from.clone(),
to_list: to.clone(),
progress: Percentage(0.5)
}])));
}