From b14b2beaad7a9acc1315df0af30b4e1d162c7d72 Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Thu, 19 Jul 2018 10:11:04 +1000 Subject: [PATCH] style: Add new algorithm for setting property to be used in later commit. Bug: 1473180 Reviewed-by: emilio MozReview-Commit-ID: CdM8hDB6rFj --- .../style/properties/declaration_block.rs | 207 +++++++++++++++++- .../style/properties/properties.mako.rs | 23 +- 2 files changed, 219 insertions(+), 11 deletions(-) diff --git a/components/style/properties/declaration_block.rs b/components/style/properties/declaration_block.rs index 7f6fc15bde9..4d775e85337 100644 --- a/components/style/properties/declaration_block.rs +++ b/components/style/properties/declaration_block.rs @@ -11,6 +11,7 @@ use cssparser::{DeclarationListParser, parse_important, ParserInput, CowRcStr}; use cssparser::{Parser, AtRuleParser, DeclarationParser, Delimiter, ParseErrorKind}; use custom_properties::CustomPropertiesBuilder; use error_reporting::{ParseErrorReporter, ContextualParseError}; +use itertools::Itertools; use parser::ParserContext; use properties::animated_properties::{AnimationValue, AnimationValueMap}; use shared_lock::Locked; @@ -39,6 +40,30 @@ impl AnimationRules { } } +/// An enum describes how a declaration should update +/// the declaration block. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum DeclarationUpdate { + /// The given declaration doesn't update anything. + None, + /// The given declaration is new, and should be append directly. + Append, + /// The given declaration can be updated in-place at the given position. + UpdateInPlace { pos: usize }, + /// The given declaration cannot be updated in-place, and an existing + /// one needs to be removed at the given position. + AppendAndRemove { pos: usize }, +} + +/// A struct describes how a declaration block should be updated by +/// a `SourcePropertyDeclaration`. +#[derive(Default)] +pub struct SourcePropertyDeclarationUpdate { + updates: SubpropertiesVec, + new_count: usize, + any_removal: bool, +} + /// Enum for how a given declaration should be pushed into a declaration block. #[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] pub enum DeclarationPushMode { @@ -440,10 +465,7 @@ impl PropertyDeclarationBlock { /// 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, - } + decl.id().as_longhand().map_or(false, |id| !self.longhands.contains(id)) } /// Returns whether calling extend with `DeclarationPushMode::Update` @@ -596,6 +618,183 @@ impl PropertyDeclarationBlock { true } + /// Prepares updating this declaration block with the given + /// `SourcePropertyDeclaration` and importance, and returns whether + /// there is something to update. + pub fn prepare_for_update( + &self, + source_declarations: &SourcePropertyDeclaration, + importance: Importance, + updates: &mut SourcePropertyDeclarationUpdate, + ) -> bool { + debug_assert!(updates.updates.is_empty()); + // Check whether we are updating for an all shorthand change. + if !matches!(source_declarations.all_shorthand, AllShorthand::NotSet) { + debug_assert!(source_declarations.declarations.is_empty()); + return source_declarations.all_shorthand.declarations().any(|decl| { + self.is_definitely_new(&decl) || + self.declarations.iter().enumerate() + .find(|&(_, ref d)| d.id() == decl.id()) + .map_or(true, |(i, d)| { + let important = self.declarations_importance[i]; + *d != decl || important != importance.important() + }) + }); + } + // Fill `updates` with update information. + let mut any_update = false; + let new_count = &mut updates.new_count; + let any_removal = &mut updates.any_removal; + let updates = &mut updates.updates; + updates.extend(source_declarations.declarations.iter().map(|declaration| { + if self.is_definitely_new(declaration) { + return DeclarationUpdate::Append; + } + let longhand_id = declaration.id().as_longhand(); + if let Some(longhand_id) = longhand_id { + if let Some(logical_group) = longhand_id.logical_group() { + let mut needs_append = false; + for (pos, decl) in self.declarations.iter().enumerate().rev() { + let id = match decl.id().as_longhand() { + Some(id) => id, + None => continue, + }; + if id == longhand_id { + if needs_append { + return DeclarationUpdate::AppendAndRemove { pos }; + } + let important = self.declarations_importance[pos]; + if decl == declaration && important == importance.important() { + return DeclarationUpdate::None; + } + return DeclarationUpdate::UpdateInPlace { pos }; + } + if !needs_append && + id.logical_group() == Some(logical_group) && + id.is_logical() != longhand_id.is_logical() { + needs_append = true; + } + } + unreachable!("Longhand should be found in loop above"); + } + } + self.declarations.iter().enumerate() + .find(|&(_, ref decl)| decl.id() == declaration.id()) + .map_or(DeclarationUpdate::Append, |(pos, decl)| { + let important = self.declarations_importance[pos]; + if decl == declaration && important == importance.important() { + DeclarationUpdate::None + } else { + DeclarationUpdate::UpdateInPlace { pos } + } + }) + }).inspect(|update| { + if matches!(update, DeclarationUpdate::None) { + return; + } + any_update = true; + match update { + DeclarationUpdate::Append => { + *new_count += 1; + } + DeclarationUpdate::AppendAndRemove { .. } => { + *any_removal = true; + } + _ => {} + } + })); + any_update + } + + /// Update this declaration block with the given data. + pub fn update( + &mut self, + drain: SourcePropertyDeclarationDrain, + importance: Importance, + updates: &mut SourcePropertyDeclarationUpdate, + ) { + let important = importance.important(); + if !matches!(drain.all_shorthand, AllShorthand::NotSet) { + debug_assert!(updates.updates.is_empty()); + for decl in drain.all_shorthand.declarations() { + if self.is_definitely_new(&decl) { + let longhand_id = decl.id().as_longhand().unwrap(); + self.declarations.push(decl); + self.declarations_importance.push(important); + self.longhands.insert(longhand_id); + } else { + let (idx, slot) = self.declarations.iter_mut() + .enumerate().find(|&(_, ref d)| d.id() == decl.id()).unwrap(); + *slot = decl; + self.declarations_importance.set(idx, important); + } + } + return; + } + + self.declarations.reserve(updates.new_count); + if updates.any_removal { + // Prepare for removal and fixing update positions. + struct UpdateOrRemoval<'a> { + item: &'a mut DeclarationUpdate, + pos: usize, + remove: bool, + } + let mut updates_and_removals: SubpropertiesVec = + updates.updates.iter_mut().filter_map(|item| { + let (pos, remove) = match *item { + DeclarationUpdate::UpdateInPlace { pos } => (pos, false), + DeclarationUpdate::AppendAndRemove { pos } => (pos, true), + _ => return None, + }; + Some(UpdateOrRemoval { item, pos, remove }) + }).collect(); + // Execute removals. It's important to do it in reverse index order, + // so that removing doesn't invalidate following positions. + updates_and_removals.sort_unstable_by_key(|update| update.pos); + updates_and_removals.iter().rev() + .filter(|update| update.remove) + .for_each(|update| { + self.declarations.remove(update.pos); + self.declarations_importance.remove(update.pos); + }); + // Fixup pos field for updates. + let mut removed_count = 0; + for update in updates_and_removals.iter_mut() { + if update.remove { + removed_count += 1; + continue; + } + debug_assert_eq!( + *update.item, + DeclarationUpdate::UpdateInPlace { pos: update.pos } + ); + *update.item = DeclarationUpdate::UpdateInPlace { + pos: update.pos - removed_count + }; + } + } + // Execute updates and appends. + for (decl, update) in drain.declarations.zip_eq(updates.updates.iter()) { + match *update { + DeclarationUpdate::None => {}, + DeclarationUpdate::Append | + DeclarationUpdate::AppendAndRemove { .. } => { + if let Some(id) = decl.id().as_longhand() { + self.longhands.insert(id); + } + self.declarations.push(decl); + self.declarations_importance.push(important); + } + DeclarationUpdate::UpdateInPlace { pos } => { + self.declarations[pos] = decl; + self.declarations_importance.set(pos, important); + } + } + } + updates.updates.clear(); + } + /// Returns the first declaration that would be removed by removing /// `property`. #[inline] diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 6a94fbd6a64..c67d26a4cda 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -12,6 +12,7 @@ #[cfg(feature = "servo")] use app_units::Au; +use arrayvec::{ArrayVec, Drain as ArrayVecDrain}; use dom::TElement; use custom_properties::CustomPropertiesBuilder; use servo_arc::{Arc, UniqueArc}; @@ -1605,6 +1606,15 @@ impl<'a> PropertyDeclarationId<'a> { } } } + + /// Returns longhand id if it is, None otherwise. + #[inline] + pub fn as_longhand(&self) -> Option { + match *self { + PropertyDeclarationId::Longhand(id) => Some(id), + _ => None, + } + } } /// Servo's representation of a CSS property, that is, either a longhand, a @@ -2266,19 +2276,18 @@ impl PropertyDeclaration { } } -const MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL: usize = - ${max(len(s.sub_properties) for s in data.shorthands_except_all())}; +type SubpropertiesArray = + [T; ${max(len(s.sub_properties) for s in data.shorthands_except_all())}]; -type SourcePropertyDeclarationArray = - [PropertyDeclaration; MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL]; +type SubpropertiesVec = ArrayVec>; /// A stack-allocated vector of `PropertyDeclaration` /// large enough to parse one CSS `key: value` declaration. /// (Shorthands expand to multiple `PropertyDeclaration`s.) pub struct SourcePropertyDeclaration { - declarations: ::arrayvec::ArrayVec, + declarations: SubpropertiesVec, - /// Stored separately to keep MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL smaller. + /// Stored separately to keep SubpropertiesVec smaller. all_shorthand: AllShorthand, } @@ -2318,7 +2327,7 @@ impl SourcePropertyDeclaration { /// Return type of SourcePropertyDeclaration::drain pub struct SourcePropertyDeclarationDrain<'a> { - declarations: ::arrayvec::Drain<'a, SourcePropertyDeclarationArray>, + declarations: ArrayVecDrain<'a, SubpropertiesArray>, all_shorthand: AllShorthand, }