Skip to content

Commit

Permalink
style: Add new algorithm for setting property to be used in later com…
Browse files Browse the repository at this point in the history
…mit.

Bug: 1473180
Reviewed-by: emilio
MozReview-Commit-ID: CdM8hDB6rFj
  • Loading branch information
upsuper authored and emilio committed Jul 24, 2018
1 parent d245f8b commit b14b2be
Show file tree
Hide file tree
Showing 2 changed files with 219 additions and 11 deletions.
207 changes: 203 additions & 4 deletions components/style/properties/declaration_block.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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<DeclarationUpdate>,
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 {
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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<UpdateOrRemoval> =
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]
Expand Down
23 changes: 16 additions & 7 deletions components/style/properties/properties.mako.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -1605,6 +1606,15 @@ impl<'a> PropertyDeclarationId<'a> {
}
}
}

/// Returns longhand id if it is, None otherwise.
#[inline]
pub fn as_longhand(&self) -> Option<LonghandId> {
match *self {
PropertyDeclarationId::Longhand(id) => Some(id),
_ => None,
}
}
}

/// Servo's representation of a CSS property, that is, either a longhand, a
Expand Down Expand Up @@ -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> =
[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<T> = ArrayVec<SubpropertiesArray<T>>;

/// 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<SourcePropertyDeclarationArray>,
declarations: SubpropertiesVec<PropertyDeclaration>,

/// Stored separately to keep MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL smaller.
/// Stored separately to keep SubpropertiesVec smaller.
all_shorthand: AllShorthand,
}

Expand Down Expand Up @@ -2318,7 +2327,7 @@ impl SourcePropertyDeclaration {

/// Return type of SourcePropertyDeclaration::drain
pub struct SourcePropertyDeclarationDrain<'a> {
declarations: ::arrayvec::Drain<'a, SourcePropertyDeclarationArray>,
declarations: ArrayVecDrain<'a, SubpropertiesArray<PropertyDeclaration>>,
all_shorthand: AllShorthand,
}

Expand Down

0 comments on commit b14b2be

Please sign in to comment.