Auto merge of #16963 - BorisChiou:stylo/animation/omta, r=emilio

stylo: Bug 1334036 - Enable animations running on compositor.

These are interdependent patches of Bug 1334036, which enables off-main thread animations. We add one FFI to get the property id set which overriding animations, so we can make sure the cascade result is correct for off-main thread animations.

---
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix [Bug 1334036](https://bugzilla.mozilla.org/show_bug.cgi?id=1334036)
- [X] These changes do not require tests because we support off-main thread animation only on Gecko, and there are enough test cases there.

<!-- 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/16963)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-05-20 07:24:21 -05:00 committed by GitHub
commit 5a012cc9b1
11 changed files with 1543 additions and 1267 deletions

View file

@ -486,7 +486,10 @@ impl<'le> TElement for ServoLayoutElement<'le> {
}
fn has_animations(&self) -> bool {
unreachable!("this should be only called on gecko");
// We use this function not only for Gecko but also for Servo to know if this element has
// animations, so we maybe try to get the important rules of this element. This is used for
// off-main thread animations, but we don't support it on Servo, so return false directly.
false
}
fn has_css_animations(&self) -> bool {

View file

@ -13,6 +13,7 @@ use properties::longhands::display::computed_value as display;
use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
use rule_tree::StrongRuleNode;
use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
use shared_lock::StylesheetGuards;
#[cfg(feature = "servo")] use std::collections::HashMap;
use std::fmt;
#[cfg(feature = "servo")] use std::hash::BuildHasherDefault;
@ -506,6 +507,24 @@ impl ElementData {
true
}
/// Return true if important rules are different.
/// We use this to make sure the cascade of off-main thread animations is correct.
/// Note: Ignore custom properties for now because we only support opacity and transform
/// properties for animations running on compositor. Actually, we only care about opacity
/// and transform for now, but it's fine to compare all properties and let the user
/// the check which properties do they want.
/// If it costs too much, get_properties_overriding_animations() should return a set
/// containing only opacity and transform properties.
pub fn important_rules_are_different(&self,
rules: &StrongRuleNode,
guards: &StylesheetGuards) -> bool {
debug_assert!(self.has_styles());
let (important_rules, _custom) =
self.styles().primary.rules.get_properties_overriding_animations(&guards);
let (other_important_rules, _custom) = rules.get_properties_overriding_animations(&guards);
important_rules != other_important_rules
}
/// Returns true if the Element has a RestyleData.
pub fn has_restyle(&self) -> bool {
self.restyle.is_some()

View file

@ -10,6 +10,7 @@ use gecko_bindings::structs::mozilla::css::URLValue;
use gecko_bindings::structs::mozilla::Side;
use gecko_bindings::structs::RawGeckoAnimationPropertySegment;
use gecko_bindings::structs::RawGeckoComputedTiming;
use gecko_bindings::structs::RawGeckoCSSPropertyIDList;
use gecko_bindings::structs::RawGeckoDocument;
use gecko_bindings::structs::RawGeckoElement;
use gecko_bindings::structs::RawGeckoKeyframeList;
@ -48,6 +49,7 @@ use gecko_bindings::structs::nsCSSCounterStyleRule;
use gecko_bindings::structs::nsCSSFontFaceRule;
use gecko_bindings::structs::nsCSSKeyword;
use gecko_bindings::structs::nsCSSPropertyID;
use gecko_bindings::structs::nsCSSPropertyIDSet;
use gecko_bindings::structs::nsCSSShadowArray;
use gecko_bindings::structs::nsCSSUnit;
use gecko_bindings::structs::nsCSSValue;
@ -316,6 +318,10 @@ pub type RawGeckoPresContextBorrowed<'a> = &'a RawGeckoPresContext;
pub type RawGeckoPresContextBorrowedOrNull<'a> = Option<&'a RawGeckoPresContext>;
pub type RawGeckoStyleAnimationListBorrowed<'a> = &'a RawGeckoStyleAnimationList;
pub type RawGeckoStyleAnimationListBorrowedOrNull<'a> = Option<&'a RawGeckoStyleAnimationList>;
pub type nsCSSPropertyIDSetBorrowed<'a> = &'a nsCSSPropertyIDSet;
pub type nsCSSPropertyIDSetBorrowedOrNull<'a> = Option<&'a nsCSSPropertyIDSet>;
pub type nsCSSPropertyIDSetBorrowedMut<'a> = &'a mut nsCSSPropertyIDSet;
pub type nsCSSPropertyIDSetBorrowedMutOrNull<'a> = Option<&'a mut nsCSSPropertyIDSet>;
pub type nsCSSValueBorrowed<'a> = &'a nsCSSValue;
pub type nsCSSValueBorrowedOrNull<'a> = Option<&'a nsCSSValue>;
pub type nsCSSValueBorrowedMut<'a> = &'a mut nsCSSValue;
@ -352,6 +358,10 @@ pub type RawGeckoServoStyleRuleListBorrowed<'a> = &'a RawGeckoServoStyleRuleList
pub type RawGeckoServoStyleRuleListBorrowedOrNull<'a> = Option<&'a RawGeckoServoStyleRuleList>;
pub type RawGeckoServoStyleRuleListBorrowedMut<'a> = &'a mut RawGeckoServoStyleRuleList;
pub type RawGeckoServoStyleRuleListBorrowedMutOrNull<'a> = Option<&'a mut RawGeckoServoStyleRuleList>;
pub type RawGeckoCSSPropertyIDListBorrowed<'a> = &'a RawGeckoCSSPropertyIDList;
pub type RawGeckoCSSPropertyIDListBorrowedOrNull<'a> = Option<&'a RawGeckoCSSPropertyIDList>;
pub type RawGeckoCSSPropertyIDListBorrowedMut<'a> = &'a mut RawGeckoCSSPropertyIDList;
pub type RawGeckoCSSPropertyIDListBorrowedMutOrNull<'a> = Option<&'a mut RawGeckoCSSPropertyIDList>;
extern "C" {
pub fn Gecko_EnsureTArrayCapacity(aArray: *mut ::std::os::raw::c_void,
@ -1177,6 +1187,9 @@ extern "C" {
extern "C" {
pub fn Gecko_NewCSSValueSharedList(len: u32) -> *mut nsCSSValueSharedList;
}
extern "C" {
pub fn Gecko_NewNoneTransform() -> *mut nsCSSValueSharedList;
}
extern "C" {
pub fn Gecko_CSSValue_GetArrayItem(css_value: nsCSSValueBorrowedMut,
index: i32) -> nsCSSValueBorrowedMut;
@ -1363,6 +1376,10 @@ extern "C" {
ident: *const u16,
set_slow_selector: *mut bool) -> bool;
}
extern "C" {
pub fn Gecko_AddPropertyToSet(arg1: nsCSSPropertyIDSetBorrowedMut,
arg2: nsCSSPropertyID);
}
extern "C" {
pub fn Gecko_Construct_Default_nsStyleFont(ptr: *mut nsStyleFont,
pres_context:
@ -2076,6 +2093,14 @@ extern "C" {
pub fn Servo_Property_IsDiscreteAnimatable(property: nsCSSPropertyID)
-> bool;
}
extern "C" {
pub fn Servo_GetProperties_Overriding_Animation(arg1:
RawGeckoElementBorrowed,
arg2:
RawGeckoCSSPropertyIDListBorrowed,
arg3:
nsCSSPropertyIDSetBorrowedMut);
}
extern "C" {
pub fn Servo_AnimationValues_Interpolate(from:
RawServoAnimationValueBorrowed,

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -379,6 +379,39 @@ pub enum StyleSharingResult {
StyleWasShared(usize),
}
/// The result status for match primary rules.
#[derive(Debug)]
pub struct RulesMatchedResult {
/// Indicate that the rule nodes are changed.
rule_nodes_changed: bool,
/// Indicate that there are any changes of important rules overriding animations.
important_rules_overriding_animation_changed: bool,
}
bitflags! {
/// Flags that represent the result of replace_rules.
pub flags RulesChanged: u8 {
/// Normal rules are changed.
const NORMAL_RULES_CHANGED = 0x01,
/// Important rules are changed.
const IMPORTANT_RULES_CHANGED = 0x02,
}
}
impl RulesChanged {
/// Return true if there are any normal rules changed.
#[inline]
pub fn normal_rules_changed(&self) -> bool {
self.contains(NORMAL_RULES_CHANGED)
}
/// Return true if there are any important rules changed.
#[inline]
pub fn important_rules_changed(&self) -> bool {
self.contains(IMPORTANT_RULES_CHANGED)
}
}
trait PrivateMatchMethods: TElement {
/// Returns the closest parent element that doesn't have a display: contents
/// style (and thus generates a box).
@ -536,7 +569,8 @@ trait PrivateMatchMethods: TElement {
/// setting them on the ElementData.
fn cascade_primary(&self,
context: &mut StyleContext<Self>,
data: &mut ElementData) {
data: &mut ElementData,
important_rules_changed: bool) {
// Collect some values.
let (mut styles, restyle) = data.styles_and_restyle_mut();
let mut primary_style = &mut styles.primary;
@ -551,7 +585,8 @@ trait PrivateMatchMethods: TElement {
self.process_animations(context,
&mut old_values,
&mut new_values,
primary_style);
primary_style,
important_rules_changed);
}
if let Some(old) = old_values {
@ -647,8 +682,9 @@ trait PrivateMatchMethods: TElement {
context: &mut StyleContext<Self>,
old_values: &mut Option<Arc<ComputedValues>>,
new_values: &mut Arc<ComputedValues>,
primary_style: &ComputedStyle) {
use context::{CSS_ANIMATIONS, CSS_TRANSITIONS, EFFECT_PROPERTIES};
primary_style: &ComputedStyle,
important_rules_changed: bool) {
use context::{CASCADE_RESULTS, CSS_ANIMATIONS, CSS_TRANSITIONS, EFFECT_PROPERTIES};
use context::UpdateAnimationsTasks;
let mut tasks = UpdateAnimationsTasks::empty();
@ -694,6 +730,9 @@ trait PrivateMatchMethods: TElement {
if self.has_animations() {
tasks.insert(EFFECT_PROPERTIES);
if important_rules_changed {
tasks.insert(CASCADE_RESULTS);
}
}
if !tasks.is_empty() {
@ -709,7 +748,8 @@ trait PrivateMatchMethods: TElement {
context: &mut StyleContext<Self>,
old_values: &mut Option<Arc<ComputedValues>>,
new_values: &mut Arc<ComputedValues>,
_primary_style: &ComputedStyle) {
_primary_style: &ComputedStyle,
_important_rules_changed: bool) {
use animation;
let possibly_expired_animations =
@ -881,17 +921,14 @@ pub trait MatchMethods : TElement {
{
// Perform selector matching for the primary style.
let mut relations = StyleRelations::empty();
let _rule_node_changed = self.match_primary(context,
data,
&mut relations);
let result = self.match_primary(context, data, &mut relations);
// Cascade properties and compute primary values.
self.cascade_primary(context, data);
self.cascade_primary(context, data, result.important_rules_overriding_animation_changed);
// Match and cascade eager pseudo-elements.
if !data.styles().is_display_none() {
let _pseudo_rule_nodes_changed =
self.match_pseudos(context, data);
let _pseudo_rule_nodes_changed = self.match_pseudos(context, data);
self.cascade_pseudos(context, data);
}
@ -925,20 +962,22 @@ pub trait MatchMethods : TElement {
/// Performs the cascade, without matching.
fn cascade_primary_and_pseudos(&self,
context: &mut StyleContext<Self>,
mut data: &mut ElementData)
mut data: &mut ElementData,
important_rules_changed: bool)
{
self.cascade_primary(context, &mut data);
self.cascade_primary(context, &mut data, important_rules_changed);
self.cascade_pseudos(context, &mut data);
}
/// Runs selector matching to (re)compute the primary rule node for this element.
///
/// Returns whether the primary rule node changed.
/// Returns RulesMatchedResult which indicates whether the primary rule node changed
/// and whether the change includes important rules.
fn match_primary(&self,
context: &mut StyleContext<Self>,
data: &mut ElementData,
relations: &mut StyleRelations)
-> bool
-> RulesMatchedResult
{
let implemented_pseudo = self.implemented_pseudo_element();
if let Some(ref pseudo) = implemented_pseudo {
@ -977,7 +1016,16 @@ pub trait MatchMethods : TElement {
}
}
return data.set_primary_rules(rules);
let important_rules_changed =
self.has_animations() &&
data.has_styles() &&
data.important_rules_are_different(&rules,
&context.shared.guards);
return RulesMatchedResult {
rule_nodes_changed: data.set_primary_rules(rules),
important_rules_overriding_animation_changed: important_rules_changed,
};
}
}
@ -1015,7 +1063,15 @@ pub trait MatchMethods : TElement {
&mut applicable_declarations,
&context.shared.guards);
return data.set_primary_rules(primary_rule_node);
let important_rules_changed = self.has_animations() &&
data.has_styles() &&
data.important_rules_are_different(&primary_rule_node,
&context.shared.guards);
RulesMatchedResult {
rule_nodes_changed: data.set_primary_rules(primary_rule_node),
important_rules_overriding_animation_changed: important_rules_changed,
}
}
/// Runs selector matching to (re)compute eager pseudo-element rule nodes
@ -1155,18 +1211,19 @@ pub trait MatchMethods : TElement {
}
/// Updates the rule nodes without re-running selector matching, using just
/// the rule tree. Returns true if the rule nodes changed.
/// the rule tree. Returns RulesChanged which indicates whether the rule nodes changed
/// and whether the important rules changed.
fn replace_rules(&self,
hint: RestyleHint,
context: &StyleContext<Self>,
data: &mut AtomicRefMut<ElementData>)
-> bool {
-> RulesChanged {
use properties::PropertyDeclarationBlock;
use shared_lock::Locked;
let element_styles = &mut data.styles_mut();
let primary_rules = &mut element_styles.primary.rules;
let mut rule_node_changed = false;
let mut result = RulesChanged::empty();
{
let mut replace_rule_node = |level: CascadeLevel,
@ -1176,7 +1233,11 @@ pub trait MatchMethods : TElement {
.update_rule_at_level(level, pdb, path, &context.shared.guards);
if let Some(n) = new_node {
*path = n;
rule_node_changed = true;
if level.is_important() {
result.insert(IMPORTANT_RULES_CHANGED);
} else {
result.insert(NORMAL_RULES_CHANGED);
}
}
};
@ -1224,7 +1285,7 @@ pub trait MatchMethods : TElement {
}
}
rule_node_changed
result
}
/// Attempts to share a style with another node. This method is unsafe

View file

@ -2241,7 +2241,7 @@ fn static_assert() {
% endfor
}
</%def>
pub fn convert_transform(input: Vec<longhands::transform::computed_value::ComputedOperation>,
pub fn convert_transform(input: &[longhands::transform::computed_value::ComputedOperation],
output: &mut structs::root::RefPtr<structs::root::nsCSSValueSharedList>) {
use gecko_bindings::structs::nsCSSKeyword::*;
use gecko_bindings::sugar::refptr::RefPtr;
@ -2261,7 +2261,7 @@ fn static_assert() {
let servo = iter.next().expect("Gecko_NewCSSValueSharedList should create a shared \
value list of the same length as the transform vector");
unsafe {
match servo {
match *servo {
${transform_function_arm("Matrix", "matrix3d", ["number"] * 16)}
${transform_function_arm("MatrixWithPercents", "matrix3d", ["number"] * 12 + ["lop"] * 2
+ ["length"] + ["number"])}
@ -2287,7 +2287,7 @@ fn static_assert() {
}
return;
};
Self::convert_transform(vec, &mut self.gecko.mSpecifiedTransform);
Self::convert_transform(&vec, &mut self.gecko.mSpecifiedTransform);
}
pub fn copy_transform_from(&mut self, other: &Self) {

View file

@ -250,7 +250,7 @@ pub mod animated_properties {
}
/// A set of longhand properties
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct LonghandIdSet {
storage: [u32; (${len(data.longhands)} - 1 + 32) / 32]
}
@ -954,7 +954,7 @@ impl PropertyId {
}
}
/// Returns a property id from Gecko's nsCSSPropertyID.
/// Returns an nsCSSPropertyID.
#[cfg(feature = "gecko")]
#[allow(non_upper_case_globals)]
pub fn to_nscsspropertyid(&self) -> Result<nsCSSPropertyID, ()> {

View file

@ -8,7 +8,7 @@
#[cfg(feature = "servo")]
use heapsize::HeapSizeOf;
use properties::{Importance, PropertyDeclarationBlock};
use properties::{Importance, LonghandIdSet, PropertyDeclarationBlock};
use shared_lock::{Locked, StylesheetGuards, SharedRwLockReadGuard};
use smallvec::SmallVec;
use std::io::{self, Write};
@ -1092,6 +1092,42 @@ impl StrongRuleNode {
.take_while(|node| node.cascade_level() >= CascadeLevel::SMILOverride)
.any(|node| node.cascade_level().is_animation())
}
/// Get a set of properties whose CascadeLevel are higher than Animations but not equal to
/// Transitions. If there are any custom properties, we set the boolean value of the returned
/// tuple to true.
pub fn get_properties_overriding_animations(&self, guards: &StylesheetGuards)
-> (LonghandIdSet, bool) {
use properties::PropertyDeclarationId;
// We want to iterate over cascade levels that override the animations level, i.e.
// !important levels and the transitions level. However, we actually want to skip the
// transitions level because although it is higher in the cascade than animations, when
// both transitions and animations are present for a given element and property, transitions
// are suppressed so that they don't actually override animations.
let iter = self.self_and_ancestors()
.skip_while(|node| node.cascade_level() == CascadeLevel::Transitions)
.take_while(|node| node.cascade_level() > CascadeLevel::Animations);
let mut result = (LonghandIdSet::new(), false);
for node in iter {
let style = node.style_source().unwrap();
for &(ref decl, important) in style.read(node.cascade_level().guard(guards))
.declarations()
.iter() {
// Although we are only iterating over cascade levels that override animations,
// in a given property declaration block we can have a mixture of !important and
// non-!important declarations but only the !important declarations actually
// override animations.
if important.important() {
match decl.id() {
PropertyDeclarationId::Longhand(id) => result.0.insert(id),
PropertyDeclarationId::Custom(_) => result.1 = true
}
}
}
}
result
}
}
/// An iterator over a rule node and its ancestors.

View file

@ -747,12 +747,12 @@ fn compute_style<E, D>(_traversal: &D,
element.match_and_cascade(context, &mut data, StyleSharingBehavior::Allow);
}
CascadeWithReplacements(hint) => {
let _rule_nodes_changed =
element.replace_rules(hint, context, &mut data);
element.cascade_primary_and_pseudos(context, &mut data);
let rules_changed = element.replace_rules(hint, context, &mut data);
element.cascade_primary_and_pseudos(context, &mut data,
rules_changed.important_rules_changed());
}
CascadeOnly => {
element.cascade_primary_and_pseudos(context, &mut data);
element.cascade_primary_and_pseudos(context, &mut data, false);
}
};
}

View file

@ -40,11 +40,14 @@ use style::gecko_bindings::bindings::{RawServoStyleSheetBorrowed, ServoComputedV
use style::gecko_bindings::bindings::{RawServoStyleSheetStrong, ServoComputedValuesStrong};
use style::gecko_bindings::bindings::{RawServoSupportsRule, RawServoSupportsRuleBorrowed};
use style::gecko_bindings::bindings::{ServoCssRulesBorrowed, ServoCssRulesStrong};
use style::gecko_bindings::bindings::{nsACString, nsAString};
use style::gecko_bindings::bindings::{nsACString, nsAString, nsCSSPropertyIDSetBorrowedMut};
use style::gecko_bindings::bindings::Gecko_AddPropertyToSet;
use style::gecko_bindings::bindings::Gecko_GetOrCreateFinalKeyframe;
use style::gecko_bindings::bindings::Gecko_GetOrCreateInitialKeyframe;
use style::gecko_bindings::bindings::Gecko_GetOrCreateKeyframeAtStart;
use style::gecko_bindings::bindings::Gecko_NewNoneTransform;
use style::gecko_bindings::bindings::RawGeckoAnimationPropertySegmentBorrowed;
use style::gecko_bindings::bindings::RawGeckoCSSPropertyIDListBorrowed;
use style::gecko_bindings::bindings::RawGeckoComputedKeyframeValuesListBorrowedMut;
use style::gecko_bindings::bindings::RawGeckoComputedTimingBorrowed;
use style::gecko_bindings::bindings::RawGeckoElementBorrowed;
@ -247,20 +250,29 @@ pub extern "C" fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed,
debug!("Servo_TraverseSubtree: {:?}", element);
let traversal_flags = match (root_behavior, restyle_behavior) {
(Root::Normal, Restyle::Normal) => TraversalFlags::empty(),
(Root::UnstyledChildrenOnly, Restyle::Normal) => UNSTYLED_CHILDREN_ONLY,
(Root::Normal, Restyle::Normal) |
(Root::Normal, Restyle::ForAnimationOnly)
=> TraversalFlags::empty(),
(Root::UnstyledChildrenOnly, Restyle::Normal) |
(Root::UnstyledChildrenOnly, Restyle::ForAnimationOnly)
=> UNSTYLED_CHILDREN_ONLY,
(Root::Normal, Restyle::ForReconstruct) => FOR_RECONSTRUCT,
_ => panic!("invalid combination of TraversalRootBehavior and TraversalRestyleBehavior"),
};
if element.has_animation_only_dirty_descendants() ||
element.has_animation_restyle_hints() {
let needs_animation_only_restyle = element.has_animation_only_dirty_descendants() ||
element.has_animation_restyle_hints();
if needs_animation_only_restyle {
traverse_subtree(element,
raw_data,
traversal_flags | ANIMATION_ONLY,
unsafe { &*snapshots });
}
if restyle_behavior == Restyle::ForAnimationOnly {
return needs_animation_only_restyle;
}
traverse_subtree(element,
raw_data,
traversal_flags,
@ -449,7 +461,15 @@ pub extern "C" fn Servo_AnimationValue_GetTransform(value: RawServoAnimationValu
{
let value = AnimationValue::as_arc(&value);
if let AnimationValue::Transform(ref servo_list) = **value {
style_structs::Box::convert_transform(servo_list.0.clone().unwrap(), unsafe { &mut *list });
let list = unsafe { &mut *list };
match servo_list.0 {
Some(ref servo_list) => {
style_structs::Box::convert_transform(servo_list, list);
},
None => unsafe {
list.set_move(RefPtr::from_addrefed(Gecko_NewNoneTransform()));
}
}
} else {
panic!("The AnimationValue should be transform");
}
@ -1319,6 +1339,38 @@ pub extern "C" fn Servo_ParseEasing(easing: *const nsAString,
}
}
#[no_mangle]
pub extern "C" fn Servo_GetProperties_Overriding_Animation(element: RawGeckoElementBorrowed,
list: RawGeckoCSSPropertyIDListBorrowed,
set: nsCSSPropertyIDSetBorrowedMut) {
let element = GeckoElement(element);
let element_data = match element.borrow_data() {
Some(data) => data,
None => return
};
let global_style_data = &*GLOBAL_STYLE_DATA;
let guard = global_style_data.shared_lock.read();
let guards = StylesheetGuards::same(&guard);
let (overridden, custom) =
element_data.styles().primary.rules.get_properties_overriding_animations(&guards);
for p in list.iter() {
match PropertyId::from_nscsspropertyid(*p) {
Ok(property) => {
if let PropertyId::Longhand(id) = property {
if overridden.contains(id) {
unsafe { Gecko_AddPropertyToSet(set, *p) };
}
}
},
Err(_) => {
if *p == nsCSSPropertyID::eCSSPropertyExtra_variable && custom {
unsafe { Gecko_AddPropertyToSet(set, *p) };
}
}
}
}
}
#[no_mangle]
pub extern "C" fn Servo_ParseStyleAttribute(data: *const nsACString,
raw_extra_data: *mut URLExtraData,