diff --git a/components/style/properties/declaration_block.rs b/components/style/properties/declaration_block.rs index 0d5ba518935..29277eef1d8 100644 --- a/components/style/properties/declaration_block.rs +++ b/components/style/properties/declaration_block.rs @@ -47,8 +47,12 @@ pub enum DeclarationPushMode { /// importance, the new declaration will be discarded. Otherwise, it will /// be appended to the end of the declaration block. Parsing, + /// In this mode, if there is an existing declaration of the same property, + /// the value is updated in-place. Otherwise it's appended. This is one + /// possible behavior of CSSOM. + Update, /// In this mode, the new declaration is always pushed to the end of the - /// declaration block. This is for CSSOM. + /// declaration block. This is another possible behavior of CSSOM. Append, } @@ -414,10 +418,49 @@ impl PropertyDeclarationBlock { } } + /// Returns whether the property is definitely new for this declaration + /// block. It returns true when the declaration is a non-custom longhand + /// and it doesn't exist in the block, and returns false otherwise. + #[inline] + fn is_definitely_new(&self, decl: &PropertyDeclaration) -> bool { + match decl.id() { + PropertyDeclarationId::Longhand(id) => !self.longhands.contains(id), + PropertyDeclarationId::Custom(..) => false, + } + } + + /// Returns whether calling extend with `DeclarationPushMode::Update` + /// will cause this declaration block to change. + pub fn will_change_in_update_mode( + &self, + source_declarations: &SourcePropertyDeclaration, + importance: Importance, + ) -> bool { + // XXX The type of parameter seems to be necessary because otherwise + // the compiler complains about `decl` not living long enough in the + // all_shorthand expression. Why? + let needs_update = |decl: &_| { + if self.is_definitely_new(decl) { + return true; + } + self.declarations.iter().enumerate() + .find(|&(_, ref slot)| slot.id() == decl.id()) + .map_or(true, |(i, slot)| { + let important = self.declarations_importance[i]; + *slot != *decl || important != importance.important() + }) + }; + source_declarations.declarations.iter().any(&needs_update) || + source_declarations.all_shorthand.declarations().any(|decl| needs_update(&decl)) + } + /// Adds or overrides the declaration for a given property in this block. /// /// See the documentation of `push` to see what impact `source` has when the /// property is already there. + /// + /// When calling with `DeclarationPushMode::Update`, this should not change + /// anything if `will_change_in_update_mode` returns false. pub fn extend( &mut self, mut drain: SourcePropertyDeclarationDrain, @@ -473,16 +516,7 @@ impl PropertyDeclarationBlock { importance: Importance, mode: DeclarationPushMode, ) -> bool { - let longhand_id = match declaration.id() { - PropertyDeclarationId::Longhand(id) => Some(id), - PropertyDeclarationId::Custom(..) => None, - }; - - let definitely_new = longhand_id.map_or(false, |id| { - !self.longhands.contains(id) - }); - - if !definitely_new { + if !self.is_definitely_new(&declaration) { let mut index_to_remove = None; for (i, slot) in self.declarations.iter_mut().enumerate() { if slot.id() != declaration.id() { @@ -514,6 +548,15 @@ impl PropertyDeclarationBlock { } } } + if matches!(mode, DeclarationPushMode::Update) { + let important = self.declarations_importance[i]; + if *slot == declaration && important == importance.important() { + return false; + } + *slot = declaration; + self.declarations_importance.set(i, importance.important()); + return true; + } index_to_remove = Some(i); break; @@ -528,7 +571,7 @@ impl PropertyDeclarationBlock { } } - if let Some(id) = longhand_id { + if let PropertyDeclarationId::Longhand(id) = declaration.id() { self.longhands.insert(id); } self.declarations.push(declaration);