Auto merge of #21237 - emilio:gecko-sync, r=emilio

style: Sync changes from mozilla-central.

See individual commits for details.

https://bugzilla.mozilla.org/show_bug.cgi?id=1477883

<!-- 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/21237)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2018-07-24 07:00:31 -04:00 committed by GitHub
commit ba6123583d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 873 additions and 497 deletions

View file

@ -17,7 +17,7 @@ use dom_struct::dom_struct;
use servo_arc::Arc;
use servo_url::ServoUrl;
use style::attr::AttrValue;
use style::properties::{DeclarationPushMode, Importance, PropertyDeclarationBlock, PropertyId, LonghandId, ShorthandId};
use style::properties::{Importance, PropertyDeclarationBlock, PropertyId, LonghandId, ShorthandId};
use style::properties::{parse_one_declaration_into, parse_style_attribute, SourcePropertyDeclaration};
use style::selector_parser::PseudoElement;
use style::shared_lock::Locked;
@ -285,8 +285,14 @@ impl CSSStyleDeclaration {
let quirks_mode = window.Document().quirks_mode();
let mut declarations = SourcePropertyDeclaration::new();
let result = parse_one_declaration_into(
&mut declarations, id, &value, &self.owner.base_url(),
window.css_error_reporter(), ParsingMode::DEFAULT, quirks_mode);
&mut declarations,
id,
&value,
&self.owner.base_url(),
window.css_error_reporter(),
ParsingMode::DEFAULT,
quirks_mode,
);
// Step 6
match result {
@ -297,13 +303,17 @@ impl CSSStyleDeclaration {
}
}
let mut updates = Default::default();
*changed =
pdb.prepare_for_update(&declarations, importance, &mut updates);
if !*changed {
return Ok(());
}
// Step 7
// Step 8
*changed = pdb.extend(
declarations.drain(),
importance,
DeclarationPushMode::Update,
);
pdb.update(declarations.drain(), importance, &mut updates);
Ok(())
})

View file

@ -418,10 +418,6 @@ where
match combinator {
Combinator::NextSibling | Combinator::LaterSibling => element.prev_sibling_element(),
Combinator::Child | Combinator::Descendant => {
if element.blocks_ancestor_combinators() {
return None;
}
match element.parent_element() {
Some(e) => return Some(e),
None => {},

View file

@ -129,11 +129,4 @@ pub trait Element: Sized + Clone + Debug {
fn ignores_nth_child_selectors(&self) -> bool {
false
}
/// Return true if we want to stop lookup ancestor of the current
/// element while matching complex selectors with descendant/child
/// combinator.
fn blocks_ancestor_combinators(&self) -> bool {
false
}
}

View file

@ -349,15 +349,10 @@ impl<T: ?Sized> Arc<T> {
#[inline]
pub fn is_unique(&self) -> bool {
// We can use Relaxed here, but the justification is a bit subtle.
// See the extensive discussion in [1] for why this needs to be Acquire.
//
// The reason to use Acquire would be to synchronize with other threads
// that are modifying the refcount with Release, i.e. to ensure that
// their writes to memory guarded by this refcount are flushed. However,
// we know that threads only modify the contents of the Arc when they
// observe the refcount to be 1, and no other thread could observe that
// because we're holding one strong reference here.
self.inner().count.load(Relaxed) == 1
// [1] https://github.com/servo/servo/issues/21186
self.inner().count.load(Acquire) == 1
}
}

View file

@ -9,7 +9,7 @@ use bezier::Bezier;
use context::SharedStyleContext;
use dom::{OpaqueNode, TElement};
use font_metrics::FontMetricsProvider;
use properties::{self, CascadeFlags, ComputedValues, LonghandId};
use properties::{self, CascadeMode, ComputedValues, LonghandId};
use properties::animated_properties::AnimatedProperty;
use properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection;
use properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
@ -504,9 +504,8 @@ where
Some(previous_style),
Some(previous_style),
Some(previous_style),
/* visited_style = */ None,
font_metrics_provider,
CascadeFlags::empty(),
CascadeMode::Unvisited { visited_rules: None },
context.quirks_mode(),
/* rule_cache = */ None,
&mut Default::default(),

View file

@ -28,5 +28,6 @@ predefined! {
f.write(' "%s",\n' % name)
f.write('}\n')
if __name__ == "__main__":
main(os.path.join(os.path.dirname(__file__), "predefined.rs"))

View file

@ -10,7 +10,7 @@
use cssparser::ToCss;
use gecko_bindings::structs::{self, CSSPseudoElementType};
use properties::{CascadeFlags, ComputedValues, PropertyFlags};
use properties::{ComputedValues, PropertyFlags};
use properties::longhands::display::computed_value::T as Display;
use selector_parser::{NonTSPseudoClass, PseudoElementCascadeType, SelectorImpl};
use std::fmt;
@ -55,14 +55,12 @@ impl PseudoElement {
PseudoElementCascadeType::Lazy
}
/// The CascadeFlags needed to cascade this pseudo-element.
/// Whether cascading this pseudo-element makes it inherit all properties,
/// even reset ones.
///
/// This is only needed to support the broken INHERIT_ALL pseudo mode for
/// Servo.
/// This is used in Servo for anonymous boxes, though it's likely broken.
#[inline]
pub fn cascade_flags(&self) -> CascadeFlags {
CascadeFlags::empty()
}
pub fn inherits_all(&self) -> bool { false }
/// Whether the pseudo-element should inherit from the default computed
/// values instead of from the parent element.

View file

@ -21,7 +21,7 @@ PRELUDE = """
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Autogenerated file created by components/style/gecko/binding_tools/regen_atoms.py, DO NOT EDIT DIRECTLY */
"""[1:]
"""[1:] # NOQA: E501
def gnu_symbolify(source, ident):
@ -57,7 +57,7 @@ class CSSPseudoElementsAtomSource:
class CSSAnonBoxesAtomSource:
PATTERN = re.compile('^(CSS_ANON_BOX|CSS_NON_INHERITING_ANON_BOX|CSS_WRAPPER_ANON_BOX)\(([^,]*),[^"]*"([^"]*)"\)',
PATTERN = re.compile('^(CSS_ANON_BOX|CSS_NON_INHERITING_ANON_BOX|CSS_WRAPPER_ANON_BOX)\(([^,]*),[^"]*"([^"]*)"\)', # NOQA: E501
re.MULTILINE)
FILE = "include/nsCSSAnonBoxList.h"
CLASS = "nsCSSAnonBoxes"

View file

@ -856,13 +856,12 @@ impl<'le> GeckoElement<'le> {
/// Returns true if this node is the shadow root of an use-element shadow tree.
#[inline]
fn is_root_of_use_element_shadow_tree(&self) -> bool {
if !self.is_root_of_anonymous_subtree() {
if !self.as_node().is_in_shadow_tree() {
return false;
}
match self.parent_element() {
match self.containing_shadow_host() {
Some(e) => {
e.local_name() == &*local_name!("use") &&
e.is_svg_element()
e.is_svg_element() && e.local_name() == &*local_name!("use")
},
None => false,
}
@ -882,6 +881,7 @@ impl<'le> GeckoElement<'le> {
.expect("AnimationValue not found in ElementTransitions");
let property = end_value.id();
debug_assert!(!property.is_logical());
map.insert(property, end_value.clone_arc());
}
map
@ -896,6 +896,7 @@ impl<'le> GeckoElement<'le> {
existing_transitions: &FnvHashMap<LonghandId, Arc<AnimationValue>>,
) -> bool {
use values::animated::{Animate, Procedure};
debug_assert!(!longhand_id.is_logical());
// If there is an existing transition, update only if the end value
// differs.
@ -1587,25 +1588,24 @@ impl<'le> TElement for GeckoElement<'le> {
fn might_need_transitions_update(
&self,
old_values: Option<&ComputedValues>,
new_values: &ComputedValues,
old_style: Option<&ComputedValues>,
new_style: &ComputedValues,
) -> bool {
use properties::longhands::display::computed_value::T as Display;
let old_values = match old_values {
let old_style = match old_style {
Some(v) => v,
None => return false,
};
let new_box_style = new_values.get_box();
let transition_not_running = !self.has_css_transitions() &&
new_box_style.transition_property_count() == 1 &&
new_box_style.transition_combined_duration_at(0) <= 0.0f32;
let new_display_style = new_box_style.clone_display();
let old_display_style = old_values.get_box().clone_display();
let new_box_style = new_style.get_box();
if !self.has_css_transitions() && !new_box_style.specifies_transitions() {
return false;
}
new_box_style.transition_property_count() > 0 && !transition_not_running &&
(new_display_style != Display::None && old_display_style != Display::None)
if new_box_style.clone_display().is_none() || old_style.clone_display().is_none() {
return false;
}
return true;
}
// Detect if there are any changes that require us to update transitions.
@ -1657,6 +1657,8 @@ impl<'le> TElement for GeckoElement<'le> {
let transition_property: TransitionProperty = property.into();
let mut property_check_helper = |property: LonghandId| -> bool {
let property =
property.to_physical(after_change_style.writing_mode);
transitions_to_keep.insert(property);
self.needs_transitions_update_per_property(
property,
@ -2329,14 +2331,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
fn ignores_nth_child_selectors(&self) -> bool {
self.is_root_of_anonymous_subtree()
}
#[inline]
fn blocks_ancestor_combinators(&self) -> bool {
// If this element is the shadow root of an use-element shadow tree,
// according to the spec, we should not match rules cross the shadow
// DOM boundary.
self.is_root_of_use_element_shadow_tree()
}
}
/// A few helpers to help with attribute selectors and snapshotting.

View file

@ -367,8 +367,4 @@ where
.assigned_slot()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn blocks_ancestor_combinators(&self) -> bool {
self.element.blocks_ancestor_combinators()
}
}

View file

@ -233,18 +233,23 @@ trait PrivateMatchMethods: TElement {
fn needs_animations_update(
&self,
context: &mut StyleContext<Self>,
old_values: Option<&ComputedValues>,
new_values: &ComputedValues,
old_style: Option<&ComputedValues>,
new_style: &ComputedValues,
) -> bool {
let new_box_style = new_values.get_box();
let has_new_animation_style = new_box_style.specifies_animations();
let new_box_style = new_style.get_box();
let new_style_specifies_animations = new_box_style.specifies_animations();
let old = match old_values {
let old_style = match old_style {
Some(old) => old,
None => return has_new_animation_style,
None => return new_style_specifies_animations,
};
let old_box_style = old.get_box();
let has_animations = self.has_css_animations();
if !new_style_specifies_animations && !has_animations {
return false;
}
let old_box_style = old_style.get_box();
let keyframes_could_have_changed = context
.shared
@ -258,7 +263,7 @@ trait PrivateMatchMethods: TElement {
//
// TODO: We should check which @keyframes were added/changed/deleted and
// update only animations corresponding to those @keyframes.
if keyframes_could_have_changed && (has_new_animation_style || self.has_css_animations()) {
if keyframes_could_have_changed {
return true;
}
@ -272,19 +277,27 @@ trait PrivateMatchMethods: TElement {
// If we were display: none, we may need to trigger animations.
if old_display == Display::None && new_display != Display::None {
return has_new_animation_style;
return new_style_specifies_animations;
}
// If we are becoming display: none, we may need to stop animations.
if old_display != Display::None && new_display == Display::None {
return self.has_css_animations();
return has_animations;
}
// We might need to update animations if writing-mode or direction
// changed, and any of the animations contained logical properties.
//
// We may want to be more granular, but it's probably not worth it.
if new_style.writing_mode != old_style.writing_mode {
return has_animations;
}
false
}
/// Create a SequentialTask for resolving descendants in a SMIL display property
/// animation if the display property changed from none.
/// Create a SequentialTask for resolving descendants in a SMIL display
/// property animation if the display property changed from none.
#[cfg(feature = "gecko")]
fn handle_display_change_for_smil_if_needed(
&self,

View file

@ -49,7 +49,8 @@ STYLE_STRUCT_LIST = [
def main():
usage = "Usage: %s [ servo | gecko ] [ style-crate | geckolib <template> | html ]" % sys.argv[0]
usage = ("Usage: %s [ servo | gecko ] [ style-crate | geckolib <template> | html ]" %
sys.argv[0])
if len(sys.argv) < 3:
abort(usage)
product = sys.argv[1]
@ -122,7 +123,7 @@ def render(filename, **context):
# Uncomment to debug generated Python code:
# write("/tmp", "mako_%s.py" % os.path.basename(filename), template.code)
return template.render(**context).encode("utf8")
except:
except Exception:
# Uncomment to see a traceback in generated Python code:
# raise
abort(exceptions.text_error_template().render().encode("utf8"))

View file

@ -56,13 +56,10 @@ bitflags! {
/// Whether the child explicitly inherits any reset property.
const INHERITS_RESET_STYLE = 1 << 8;
/// A flag to mark a style which is a visited style.
const IS_STYLE_IF_VISITED = 1 << 9;
/// Whether the style or any of the ancestors has a multicol style.
///
/// Only used in Servo.
const CAN_BE_FRAGMENTED = 1 << 10;
const CAN_BE_FRAGMENTED = 1 << 9;
}
}
@ -70,7 +67,6 @@ impl ComputedValueFlags {
/// Flags that are unconditionally propagated to descendants.
#[inline]
fn inherited_flags() -> Self {
ComputedValueFlags::IS_STYLE_IF_VISITED |
ComputedValueFlags::IS_RELEVANT_LINK_VISITED |
ComputedValueFlags::CAN_BE_FRAGMENTED |
ComputedValueFlags::IS_IN_PSEUDO_ELEMENT_SUBTREE |

View file

@ -75,7 +75,8 @@ class Keyword(object):
self.name = name
self.values = values.split()
if gecko_constant_prefix and gecko_enum_prefix:
raise TypeError("Only one of gecko_constant_prefix and gecko_enum_prefix can be specified")
raise TypeError("Only one of gecko_constant_prefix and gecko_enum_prefix "
"can be specified")
self.gecko_constant_prefix = gecko_constant_prefix or \
"NS_STYLE_" + self.name.upper().replace("-", "_")
self.gecko_enum_prefix = gecko_enum_prefix
@ -116,7 +117,8 @@ class Keyword(object):
raise Exception("Bad product: " + product)
def gecko_constant(self, value):
moz_stripped = value.replace("-moz-", '') if self.gecko_strip_moz_prefix else value.replace("-moz-", 'moz-')
moz_stripped = (value.replace("-moz-", '')
if self.gecko_strip_moz_prefix else value.replace("-moz-", 'moz-'))
mapped = self.consts_map.get(value)
if self.gecko_enum_prefix:
parts = moz_stripped.replace('-', '_').split('_')
@ -166,8 +168,9 @@ class Longhand(object):
enabled_in="content", need_index=False,
gecko_ffi_name=None,
allowed_in_keyframe_block=True, cast_type='u8',
logical=False, alias=None, extra_prefixes=None, boxed=False,
flags=None, allowed_in_page_rule=False, allow_quirks=False, ignored_when_colors_disabled=False,
logical=False, logical_group=None, alias=None, extra_prefixes=None, boxed=False,
flags=None, allowed_in_page_rule=False, allow_quirks=False,
ignored_when_colors_disabled=False,
vector=False, servo_restyle_damage="repaint"):
self.name = name
if not spec:
@ -193,6 +196,9 @@ class Longhand(object):
self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case
self.cast_type = cast_type
self.logical = arg_to_bool(logical)
self.logical_group = logical_group
if self.logical:
assert logical_group, "Property " + name + " must have a logical group"
self.alias = parse_property_aliases(alias)
self.extra_prefixes = parse_property_aliases(extra_prefixes)
self.boxed = arg_to_bool(boxed)
@ -220,12 +226,6 @@ class Longhand(object):
and animation_value_type != "discrete"
self.is_animatable_with_computed_value = animation_value_type == "ComputedValue" \
or animation_value_type == "discrete"
if self.logical:
# Logical properties will be animatable (i.e. the animation type is
# discrete). For now, it is still non-animatable.
self.animatable = False
self.transitionable = False
self.animation_value_type = None
# See compute_damage for the various values this can take
self.servo_restyle_damage = servo_restyle_damage
@ -234,6 +234,20 @@ class Longhand(object):
def type():
return "longhand"
# For a given logical property return all the physical
# property names corresponding to it.
def all_physical_mapped_properties(self):
assert self.logical
logical_side = None
for s in LOGICAL_SIDES + LOGICAL_SIZES:
if s in self.name:
assert not logical_side
logical_side = s
assert logical_side
physical = PHYSICAL_SIDES if logical_side in LOGICAL_SIDES else PHYSICAL_SIZES
return [self.name.replace(logical_side, physical_side).replace("inset-", "")
for physical_side in physical]
def experimental(self, product):
if product == "gecko":
return bool(self.gecko_pref)

View file

@ -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,21 +40,28 @@ impl AnimationRules {
}
}
/// Enum for how a given declaration should be pushed into a declaration block.
#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
pub enum DeclarationPushMode {
/// Mode used when declarations were obtained from CSS parsing.
/// If there is an existing declaration of the same property with a higher
/// 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 another possible behavior of CSSOM.
/// 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,
}
/// A declaration [importance][importance].
@ -131,12 +139,15 @@ impl<'a> DoubleEndedIterator for DeclarationImportanceIterator<'a> {
}
/// Iterator over `PropertyDeclaration` for Importance::Normal.
///
/// TODO(emilio): This should be replaced by `impl Trait`, returning a
/// filter()ed iterator when available instead, and all the boilerplate below
/// should go.
pub struct NormalDeclarationIterator<'a>(DeclarationImportanceIterator<'a>);
impl<'a> NormalDeclarationIterator<'a> {
/// Constructor.
#[inline]
pub fn new(declarations: &'a [PropertyDeclaration], important: &'a SmallBitVec) -> Self {
fn new(declarations: &'a [PropertyDeclaration], important: &'a SmallBitVec) -> Self {
NormalDeclarationIterator(
DeclarationImportanceIterator::new(declarations, important)
)
@ -146,6 +157,7 @@ impl<'a> NormalDeclarationIterator<'a> {
impl<'a> Iterator for NormalDeclarationIterator<'a> {
type Item = &'a PropertyDeclaration;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
loop {
let (decl, importance) = self.0.iter.next()?;
@ -155,11 +167,24 @@ impl<'a> Iterator for NormalDeclarationIterator<'a> {
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.iter.size_hint()
}
}
impl<'a> DoubleEndedIterator for NormalDeclarationIterator<'a> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
loop {
let (decl, importance) = self.0.iter.next_back()?;
if !importance {
return Some(decl);
}
}
}
}
/// Iterator for AnimationValue to be generated from PropertyDeclarationBlock.
pub struct AnimationValueIterator<'a, 'cx, 'cx_a:'cx> {
iter: NormalDeclarationIterator<'a>,
@ -423,98 +448,47 @@ 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,
}
}
/// 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))
decl.id().as_longhand().map_or(false, |id| !self.longhands.contains(id))
}
/// 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,
importance: Importance,
mode: DeclarationPushMode,
) -> bool {
match mode {
DeclarationPushMode::Parsing => {
let all_shorthand_len = match drain.all_shorthand {
AllShorthand::NotSet => 0,
AllShorthand::CSSWideKeyword(_) |
AllShorthand::WithVariables(_) => shorthands::ALL_SHORTHAND_MAX_LEN,
};
let push_calls_count =
drain.declarations.len() + all_shorthand_len;
let all_shorthand_len = match drain.all_shorthand {
AllShorthand::NotSet => 0,
AllShorthand::CSSWideKeyword(_) |
AllShorthand::WithVariables(_) => shorthands::ALL_SHORTHAND_MAX_LEN,
};
let push_calls_count =
drain.declarations.len() + all_shorthand_len;
// With deduplication the actual length increase may be less than this.
self.declarations.reserve(push_calls_count);
}
_ => {
// Don't bother reserving space, since it's usually the case
// that CSSOM just overrides properties and we don't need to use
// more memory. See bug 1424346 for an example where this
// matters.
//
// TODO: Would it be worth to call reserve() just if it's empty?
}
}
// With deduplication the actual length increase may be less than this.
self.declarations.reserve(push_calls_count);
let mut changed = false;
for decl in &mut drain.declarations {
changed |= self.push(
decl,
importance,
mode,
);
changed |= self.push(decl, importance);
}
drain.all_shorthand.declarations().fold(changed, |changed, decl| {
changed | self.push(decl, importance, mode)
changed | self.push(decl, importance)
})
}
/// Adds or overrides the declaration for a given property in this block.
///
/// Depending on the value of `mode`, this has a different behavior in the
/// presence of another declaration with the same ID in the declaration
/// block.
///
/// Returns whether the declaration has changed.
///
/// This is only used for parsing and internal use.
pub fn push(
&mut self,
declaration: PropertyDeclaration,
importance: Importance,
mode: DeclarationPushMode,
) -> bool {
if !self.is_definitely_new(&declaration) {
let mut index_to_remove = None;
@ -523,40 +497,29 @@ impl PropertyDeclarationBlock {
continue;
}
if matches!(mode, DeclarationPushMode::Parsing) {
let important = self.declarations_importance[i];
let important = self.declarations_importance[i];
// For declarations from parsing, non-important declarations
// shouldn't override existing important one.
if important && !importance.important() {
return false;
}
// For declarations from parsing, non-important declarations
// shouldn't override existing important one.
if important && !importance.important() {
return false;
}
// As a compatibility hack, specially on Android,
// don't allow to override a prefixed webkit display
// value with an unprefixed version from parsing
// code.
//
// TODO(emilio): Unship.
if let PropertyDeclaration::Display(old_display) = *slot {
use properties::longhands::display::computed_value::T as display;
// As a compatibility hack, specially on Android,
// don't allow to override a prefixed webkit display
// value with an unprefixed version from parsing
// code.
//
// TODO(emilio): Unship.
if let PropertyDeclaration::Display(old_display) = *slot {
use properties::longhands::display::computed_value::T as display;
if let PropertyDeclaration::Display(new_display) = declaration {
if display::should_ignore_parsed_value(old_display, new_display) {
return false;
}
if let PropertyDeclaration::Display(new_display) = declaration {
if display::should_ignore_parsed_value(old_display, new_display) {
return false;
}
}
}
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;
@ -579,6 +542,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]
@ -1225,7 +1365,6 @@ pub fn parse_property_declaration_list(
block.extend(
iter.parser.declarations.drain(),
importance,
DeclarationPushMode::Parsing,
);
}
Err((error, slice)) => {

View file

@ -5379,21 +5379,21 @@ clip-path
#[allow(unused_unsafe)]
pub fn set_column_count(&mut self, v: longhands::column_count::computed_value::T) {
use gecko_bindings::structs::{NS_STYLE_COLUMN_COUNT_AUTO, nsStyleColumn_kMaxColumnCount};
use gecko_bindings::structs::{nsStyleColumn_kColumnCountAuto, nsStyleColumn_kMaxColumnCount};
self.gecko.mColumnCount = match v {
ColumnCount::Integer(integer) => {
cmp::min(integer.0 as u32, unsafe { nsStyleColumn_kMaxColumnCount })
},
ColumnCount::Auto => NS_STYLE_COLUMN_COUNT_AUTO
ColumnCount::Auto => nsStyleColumn_kColumnCountAuto
};
}
${impl_simple_copy('column_count', 'mColumnCount')}
pub fn clone_column_count(&self) -> longhands::column_count::computed_value::T {
use gecko_bindings::structs::{NS_STYLE_COLUMN_COUNT_AUTO, nsStyleColumn_kMaxColumnCount};
if self.gecko.mColumnCount != NS_STYLE_COLUMN_COUNT_AUTO {
use gecko_bindings::structs::{nsStyleColumn_kColumnCountAuto, nsStyleColumn_kMaxColumnCount};
if self.gecko.mColumnCount != nsStyleColumn_kColumnCountAuto {
debug_assert!(self.gecko.mColumnCount >= 1 &&
self.gecko.mColumnCount <= nsStyleColumn_kMaxColumnCount);
ColumnCount::Integer((self.gecko.mColumnCount as i32).into())

View file

@ -5,7 +5,7 @@
<%namespace name="helpers" file="/helpers.mako.rs" />
<%
from data import to_idl_name, SYSTEM_FONT_LONGHANDS
from data import to_idl_name, SYSTEM_FONT_LONGHANDS, to_camel_case
from itertools import groupby
%>
@ -114,7 +114,7 @@ pub fn nscsspropertyid_is_transitionable(property: nsCSSPropertyID) -> bool {
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub enum AnimatedProperty {
% for prop in data.longhands:
% if prop.animatable:
% if prop.animatable and not prop.logical:
<%
value_type = "longhands::{}::computed_value::T".format(prop.ident)
if not prop.is_animatable_with_computed_value:
@ -131,7 +131,7 @@ impl AnimatedProperty {
pub fn name(&self) -> &'static str {
match *self {
% for prop in data.longhands:
% if prop.animatable:
% if prop.animatable and not prop.logical:
AnimatedProperty::${prop.camel_case}(..) => "${prop.name}",
% endif
% endfor
@ -143,7 +143,7 @@ impl AnimatedProperty {
pub fn does_animate(&self) -> bool {
match *self {
% for prop in data.longhands:
% if prop.animatable:
% if prop.animatable and not prop.logical:
AnimatedProperty::${prop.camel_case}(ref from, ref to) => from != to,
% endif
% endfor
@ -154,7 +154,7 @@ impl AnimatedProperty {
pub fn has_the_same_end_value_as(&self, other: &Self) -> bool {
match (self, other) {
% for prop in data.longhands:
% if prop.animatable:
% if prop.animatable and not prop.logical:
(&AnimatedProperty::${prop.camel_case}(_, ref this_end_value),
&AnimatedProperty::${prop.camel_case}(_, ref other_end_value)) => {
this_end_value == other_end_value
@ -173,7 +173,7 @@ impl AnimatedProperty {
{
match *self {
% for prop in data.longhands:
% if prop.animatable:
% if prop.animatable and not prop.logical:
AnimatedProperty::${prop.camel_case}(ref from, ref to) => {
// https://drafts.csswg.org/web-animations/#discrete-animation-type
% if prop.animation_value_type == "discrete":
@ -203,9 +203,12 @@ impl AnimatedProperty {
old_style: &ComputedValues,
new_style: &ComputedValues,
) -> Option<AnimatedProperty> {
// FIXME(emilio): Handle the case where old_style and new_style's
// writing mode differ.
let property = property.to_physical(new_style.writing_mode);
Some(match property {
% for prop in data.longhands:
% if prop.animatable:
% if prop.animatable and not prop.logical:
LonghandId::${prop.camel_case} => {
let old_computed = old_style.clone_${prop.ident}();
let new_computed = new_style.clone_${prop.ident}();
@ -254,11 +257,10 @@ unsafe impl HasSimpleFFI for AnimationValueMap {}
#[repr(u16)]
pub enum AnimationValue {
% for prop in data.longhands:
% if prop.animatable:
/// `${prop.name}`
% if prop.animatable and not prop.logical:
${prop.camel_case}(${prop.animated_type()}),
% else:
/// `${prop.name}` (not animatable)
${prop.camel_case}(Void),
% endif
% endfor
@ -267,8 +269,11 @@ pub enum AnimationValue {
<%
animated = []
unanimated = []
animated_with_logical = []
for prop in data.longhands:
if prop.animatable:
animated_with_logical.append(prop)
if prop.animatable and not prop.logical:
animated.append(prop)
else:
unanimated.append(prop)
@ -370,7 +375,7 @@ impl AnimationValue {
let id = unsafe { *(self as *const _ as *const LonghandId) };
debug_assert_eq!(id, match *self {
% for prop in data.longhands:
% if prop.animatable:
% if prop.animatable and not prop.logical:
AnimationValue::${prop.camel_case}(..) => LonghandId::${prop.camel_case},
% else:
AnimationValue::${prop.camel_case}(void) => void::unreachable(void),
@ -444,17 +449,18 @@ impl AnimationValue {
%>
let animatable = match *decl {
% for (specified_ty, ty, boxed, to_animated, inherit, system), props in groupby(animated, key=keyfunc):
% for (specified_ty, ty, boxed, to_animated, inherit, system), props in groupby(animated_with_logical, key=keyfunc):
${" |\n".join("PropertyDeclaration::{}(ref value)".format(prop.camel_case) for prop in props)} => {
let decl_repr = unsafe {
&*(decl as *const _ as *const PropertyDeclarationVariantRepr<${specified_ty}>)
};
let longhand_id = unsafe {
*(&decl_repr.tag as *const u16 as *const LonghandId)
};
% if inherit:
context.for_non_inherited_property = None;
% else:
context.for_non_inherited_property = unsafe {
Some(*(&decl_repr.tag as *const u16 as *const LonghandId))
};
context.for_non_inherited_property = Some(longhand_id);
% endif
% if system:
if let Some(sf) = value.get_system() {
@ -475,7 +481,7 @@ impl AnimationValue {
ptr::write(
&mut out as *mut _ as *mut AnimationValueVariantRepr<${ty}>,
AnimationValueVariantRepr {
tag: decl_repr.tag,
tag: longhand_id.to_physical(context.builder.writing_mode) as u16,
value,
},
);
@ -492,24 +498,40 @@ impl AnimationValue {
LonghandId::${prop.camel_case} => {
let style_struct = match declaration.keyword {
% if not prop.style_struct.inherited:
CSSWideKeyword::Unset |
CSSWideKeyword::Unset |
% endif
CSSWideKeyword::Initial => {
initial.get_${prop.style_struct.name_lower}()
},
% if prop.style_struct.inherited:
CSSWideKeyword::Unset |
CSSWideKeyword::Unset |
% endif
CSSWideKeyword::Inherit => {
context.builder
.get_parent_${prop.style_struct.name_lower}()
},
};
let computed = style_struct.clone_${prop.ident}();
let computed = style_struct
% if prop.logical:
.clone_${prop.ident}(context.builder.writing_mode);
% else:
.clone_${prop.ident}();
% endif
% if not prop.is_animatable_with_computed_value:
let computed = computed.to_animated_value();
% endif
AnimationValue::${prop.camel_case}(computed)
% if prop.logical:
let wm = context.builder.writing_mode;
<%helpers:logical_setter_helper name="${prop.name}">
<%def name="inner(physical_ident)">
AnimationValue::${to_camel_case(physical_ident)}(computed)
</%def>
</%helpers:logical_setter_helper>
% else:
AnimationValue::${prop.camel_case}(computed)
% endif
},
% endif
% endfor
@ -548,9 +570,10 @@ impl AnimationValue {
property: LonghandId,
style: &ComputedValues,
) -> Option<Self> {
let property = property.to_physical(style.writing_mode);
Some(match property {
% for prop in data.longhands:
% if prop.animatable:
% if prop.animatable and not prop.logical:
LonghandId::${prop.camel_case} => {
let computed = style.clone_${prop.ident}();
AnimationValue::${prop.camel_case}(
@ -656,7 +679,7 @@ impl ToAnimatedZero for AnimationValue {
fn to_animated_zero(&self) -> Result<Self, ()> {
match *self {
% for prop in data.longhands:
% if prop.animatable and prop.animation_value_type != "discrete":
% if prop.animatable and not prop.logical and prop.animation_value_type != "discrete":
AnimationValue::${prop.camel_case}(ref base) => {
Ok(AnimationValue::${prop.camel_case}(base.to_animated_zero()?))
},
@ -2924,43 +2947,67 @@ impl ToAnimatedZero for AnimatedFilter {
}
}
/// A comparator to sort PropertyIds such that longhands are sorted before shorthands,
/// shorthands with fewer components are sorted before shorthands with more components,
/// and otherwise shorthands are sorted by IDL name as defined by [Web Animations][property-order].
/// The category a property falls into for ordering purposes.
///
/// https://drafts.csswg.org/web-animations/#calculating-computed-keyframes
///
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
enum PropertyCategory {
Custom,
PhysicalLonghand,
LogicalLonghand,
Shorthand,
}
impl PropertyCategory {
fn of(id: &PropertyId) -> Self {
match *id {
PropertyId::Shorthand(..) |
PropertyId::ShorthandAlias(..) => PropertyCategory::Shorthand,
PropertyId::Longhand(id) |
PropertyId::LonghandAlias(id, ..) => {
if id.is_logical() {
PropertyCategory::LogicalLonghand
} else {
PropertyCategory::PhysicalLonghand
}
}
PropertyId::Custom(..) => PropertyCategory::Custom,
}
}
}
/// A comparator to sort PropertyIds such that physical longhands are sorted
/// before logical longhands and shorthands, shorthands with fewer components
/// are sorted before shorthands with more components, and otherwise shorthands
/// are sorted by IDL name as defined by [Web Animations][property-order].
///
/// Using this allows us to prioritize values specified by longhands (or smaller
/// shorthand subsets) when longhands and shorthands are both specified on the one keyframe.
///
/// Example orderings that result from this:
///
/// margin-left, margin
///
/// and:
///
/// border-top-color, border-color, border-top, border
/// shorthand subsets) when longhands and shorthands are both specified on the
/// one keyframe.
///
/// [property-order] https://drafts.csswg.org/web-animations/#calculating-computed-keyframes
pub fn compare_property_priority(a: &PropertyId, b: &PropertyId) -> cmp::Ordering {
match (a.as_shorthand(), b.as_shorthand()) {
// Within shorthands, sort by the number of subproperties, then by IDL name.
(Ok(a), Ok(b)) => {
let subprop_count_a = a.longhands().count();
let subprop_count_b = b.longhands().count();
subprop_count_a
.cmp(&subprop_count_b)
.then_with(|| {
get_idl_name_sort_order(a).cmp(&get_idl_name_sort_order(b))
})
},
let a_category = PropertyCategory::of(a);
let b_category = PropertyCategory::of(b);
// Longhands go before shorthands.
(Ok(_), Err(_)) => cmp::Ordering::Greater,
(Err(_), Ok(_)) => cmp::Ordering::Less,
// Both are longhands or custom properties in which case they don't overlap and should
// sort equally.
_ => cmp::Ordering::Equal,
if a_category != b_category {
return a_category.cmp(&b_category);
}
if a_category == PropertyCategory::Shorthand {
let a = a.as_shorthand().unwrap();
let b = b.as_shorthand().unwrap();
// Within shorthands, sort by the number of subproperties, then by IDL
// name.
let subprop_count_a = a.longhands().count();
let subprop_count_b = b.longhands().count();
return subprop_count_a.cmp(&subprop_count_b).then_with(|| {
get_idl_name_sort_order(a).cmp(&get_idl_name_sort_order(b))
});
}
cmp::Ordering::Equal
}
fn get_idl_name_sort_order(shorthand: ShorthandId) -> u32 {

View file

@ -27,6 +27,7 @@
spec=maybe_logical_spec(side, "color"),
animation_value_type="AnimatedColor",
logical=is_logical,
logical_group="border-color",
allow_quirks=not is_logical,
flags="APPLIES_TO_FIRST_LETTER",
ignored_when_colors_disabled=True,
@ -40,6 +41,7 @@
flags="APPLIES_TO_FIRST_LETTER",
animation_value_type="discrete" if not is_logical else "none",
logical=is_logical,
logical_group="border-style",
needs_context=False,
)}
@ -52,6 +54,7 @@
spec=maybe_logical_spec(side, "width"),
animation_value_type="NonNegativeLength",
logical=is_logical,
logical_group="border-width",
flags="APPLIES_TO_FIRST_LETTER GETCS_NEEDS_LAYOUT_FLUSH",
allow_quirks=not is_logical,
servo_restyle_damage="reflow rebuild_and_reflow_inline"

View file

@ -33,6 +33,7 @@ ${helpers.predefined_type(
${helpers.single_keyword("column-fill", "balance auto", extra_prefixes="moz",
products="gecko", animation_value_type="discrete",
gecko_enum_prefix="StyleColumnFill",
spec="https://drafts.csswg.org/css-multicol/#propdef-column-fill")}
${helpers.predefined_type("column-rule-width",
@ -60,6 +61,7 @@ ${helpers.predefined_type(
${helpers.single_keyword("column-span", "none all",
products="gecko", animation_value_type="discrete",
gecko_enum_prefix="StyleColumnSpan",
gecko_pref="layout.css.column-span.enabled",
spec="https://drafts.csswg.org/css-multicol/#propdef-column-span",
extra_prefixes="moz:layout.css.column-span.enabled")}

View file

@ -20,6 +20,7 @@
allow_quirks=not side[1],
animation_value_type="ComputedValue",
logical=side[1],
logical_group="margin",
spec=spec,
flags="APPLIES_TO_FIRST_LETTER GETCS_NEEDS_LAYOUT_FLUSH",
allowed_in_page_rule=True,

View file

@ -21,6 +21,7 @@
alias=maybe_moz_logical_alias(product, side, "-moz-padding-%s"),
animation_value_type="NonNegativeLengthOrPercentage",
logical=side[1],
logical_group="padding",
spec=spec,
flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_PLACEHOLDER GETCS_NEEDS_LAYOUT_FLUSH",
allow_quirks=not side[1],

View file

@ -18,7 +18,8 @@
flags="GETCS_NEEDS_LAYOUT_FLUSH",
animation_value_type="ComputedValue",
allow_quirks=True,
servo_restyle_damage="reflow_out_of_flow"
servo_restyle_damage="reflow_out_of_flow",
logical_group="inset",
)}
% endfor
// inset-* logical properties, map to "top" / "left" / "bottom" / "right"
@ -32,6 +33,7 @@
alias="offset-%s:layout.css.offset-logical-properties.enabled" % side,
animation_value_type="ComputedValue",
logical=True,
logical_group="inset",
)}
% endfor
@ -221,6 +223,7 @@ ${helpers.predefined_type(
"computed::MozLength::auto()",
parse_function,
logical=logical,
logical_group="size",
allow_quirks=not logical,
spec=spec % size,
animation_value_type="MozLength",
@ -234,6 +237,7 @@ ${helpers.predefined_type(
"computed::MozLength::auto()",
parse_function,
logical=logical,
logical_group="min-size",
allow_quirks=not logical,
spec=spec % size,
animation_value_type="MozLength",
@ -245,6 +249,7 @@ ${helpers.predefined_type(
"computed::MaxLength::none()",
parse_function,
logical=logical,
logical_group="max-size",
allow_quirks=not logical,
spec=spec % size,
animation_value_type="MaxLength",
@ -252,32 +257,41 @@ ${helpers.predefined_type(
)}
% else:
// servo versions (no keyword support)
${helpers.predefined_type(size,
"LengthOrPercentageOrAuto",
"computed::LengthOrPercentageOrAuto::Auto",
"parse_non_negative",
spec=spec % size,
allow_quirks=not logical,
animation_value_type="ComputedValue", logical = logical,
servo_restyle_damage = "reflow")}
${helpers.predefined_type("min-%s" % size,
"LengthOrPercentage",
"computed::LengthOrPercentage::Length(computed::Length::new(0.))",
"parse_non_negative",
spec=spec % ("min-%s" % size),
animation_value_type="ComputedValue",
logical=logical,
allow_quirks=not logical,
servo_restyle_damage = "reflow")}
${helpers.predefined_type("max-%s" % size,
"LengthOrPercentageOrNone",
"computed::LengthOrPercentageOrNone::None",
"parse_non_negative",
spec=spec % ("min-%s" % size),
animation_value_type="ComputedValue",
logical=logical,
allow_quirks=not logical,
servo_restyle_damage = "reflow")}
${helpers.predefined_type(
size,
"LengthOrPercentageOrAuto",
"computed::LengthOrPercentageOrAuto::Auto",
"parse_non_negative",
spec=spec % size,
logical_group="size",
allow_quirks=not logical,
animation_value_type="ComputedValue", logical = logical,
servo_restyle_damage = "reflow",
)}
${helpers.predefined_type(
"min-%s" % size,
"LengthOrPercentage",
"computed::LengthOrPercentage::Length(computed::Length::new(0.))",
"parse_non_negative",
spec=spec % ("min-%s" % size),
logical_group="min-size",
animation_value_type="ComputedValue",
logical=logical,
allow_quirks=not logical,
servo_restyle_damage = "reflow",
)}
${helpers.predefined_type(
"max-%s" % size,
"LengthOrPercentageOrNone",
"computed::LengthOrPercentageOrNone::None",
"parse_non_negative",
spec=spec % ("max-%s" % size),
logical_group="max-size",
animation_value_type="ComputedValue",
logical=logical,
allow_quirks=not logical,
servo_restyle_damage = "reflow",
)}
% endif
% endfor

View file

@ -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};
@ -56,6 +57,7 @@ use style_adjuster::StyleAdjuster;
pub use self::declaration_block::*;
<%!
from collections import defaultdict
from data import Method, Keyword, to_rust_ident, to_camel_case, SYSTEM_FONT_LONGHANDS
import os.path
%>
@ -841,6 +843,29 @@ bitflags! {
}
}
<%
logical_groups = defaultdict(list)
for prop in data.longhands:
if prop.logical_group:
logical_groups[prop.logical_group].append(prop)
for group, props in logical_groups.iteritems():
logical_count = sum(1 for p in props if p.logical)
if logical_count * 2 != len(props):
raise RuntimeError("Logical group {} has ".format(group) +
"unbalanced logical / physical properties")
%>
/// A group for properties which may override each other
/// via logical resolution.
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum LogicalGroup {
% for group in logical_groups.iterkeys():
/// ${group}
${to_camel_case(group)},
% endfor
}
/// An identifier for a given longhand property.
#[derive(Clone, Copy, Eq, Hash, MallocSizeOf, PartialEq)]
#[repr(u16)]
@ -993,9 +1018,15 @@ impl LonghandId {
match *self {
% for property in data.longhands:
% if property.logical:
<% logical_group = property.logical_group %>
LonghandId::${property.camel_case} => {
<%helpers:logical_setter_helper name="${property.name}">
<%def name="inner(physical_ident)">
<%
physical_name = physical_ident.replace("_", "-")
physical_property = data.longhands_by_name[physical_name]
assert logical_group == physical_property.logical_group
%>
LonghandId::${to_camel_case(physical_ident)}
</%def>
</%helpers:logical_setter_helper>
@ -1006,6 +1037,20 @@ impl LonghandId {
}
}
/// Return the logical group of this longhand property.
pub fn logical_group(&self) -> Option<LogicalGroup> {
const LOGICAL_GROUPS: [Option<LogicalGroup>; ${len(data.longhands)}] = [
% for prop in data.longhands:
% if prop.logical_group:
Some(LogicalGroup::${to_camel_case(prop.logical_group)}),
% else:
None,
% endif
% endfor
];
LOGICAL_GROUPS[*self as usize]
}
/// Returns PropertyFlags for given longhand property.
pub fn flags(&self) -> PropertyFlags {
match *self {
@ -1497,7 +1542,7 @@ impl UnparsedValue {
/// An identifier for a given property declaration, which can be either a
/// longhand or a custom property.
#[derive(Clone, Copy, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub enum PropertyDeclarationId<'a> {
/// A longhand.
@ -1561,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
@ -1903,7 +1957,7 @@ impl PropertyDeclaration {
}
_ => {}
}
// This is just fine because PropertyDeclarationId and LonghandId
// This is just fine because PropertyDeclaration and LonghandId
// have corresponding discriminants.
let id = unsafe { *(self as *const _ as *const LonghandId) };
debug_assert_eq!(id, match *self {
@ -1915,6 +1969,69 @@ impl PropertyDeclaration {
PropertyDeclarationId::Longhand(id)
}
/// Given a declaration, convert it into a declaration for a corresponding
/// physical property.
#[inline]
pub fn to_physical(&self, wm: WritingMode) -> Self {
match *self {
PropertyDeclaration::WithVariables(VariableDeclaration {
id,
ref value,
}) => {
return PropertyDeclaration::WithVariables(VariableDeclaration {
id: id.to_physical(wm),
value: value.clone(),
})
}
PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration {
id,
keyword,
}) => {
return PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration {
id: id.to_physical(wm),
keyword,
})
}
PropertyDeclaration::Custom(..) => return self.clone(),
% for prop in data.longhands:
PropertyDeclaration::${prop.camel_case}(..) => {},
% endfor
}
let mut ret = self.clone();
% for prop in data.longhands:
% if prop.logical:
% for physical_property in prop.all_physical_mapped_properties():
% if data.longhands_by_name[physical_property].specified_type() != prop.specified_type():
<% raise "Logical property %s should share specified value with physical property %s" % \
(prop.name, physical_property) %>
% endif
% endfor
% endif
% endfor
unsafe {
let longhand_id = *(&mut ret as *mut _ as *mut LonghandId);
debug_assert_eq!(
PropertyDeclarationId::Longhand(longhand_id),
ret.id()
);
// This is just fine because PropertyDeclaration and LonghandId
// have corresponding discriminants.
*(&mut ret as *mut _ as *mut LonghandId) = longhand_id.to_physical(wm);
debug_assert_eq!(
PropertyDeclarationId::Longhand(longhand_id.to_physical(wm)),
ret.id()
);
}
ret
}
fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option< &str> {
match *self {
PropertyDeclaration::WithVariables(ref declaration) => {
@ -2160,19 +2277,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,
}
@ -2212,7 +2328,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,
}
@ -2630,11 +2746,6 @@ impl ComputedValues {
self.get_box().clone_display().is_contents()
}
/// Whether we're a visited style.
pub fn is_style_if_visited(&self) -> bool {
self.flags.contains(ComputedValueFlags::IS_STYLE_IF_VISITED)
}
/// Gets a reference to the rule node. Panic if no rule node exists.
pub fn rules(&self) -> &StrongRuleNode {
self.rules.as_ref().unwrap()
@ -3163,10 +3274,8 @@ impl<'a> StyleBuilder<'a> {
parent_style: Option<<&'a ComputedValues>,
parent_style_ignoring_first_line: Option<<&'a ComputedValues>,
pseudo: Option<<&'a PseudoElement>,
cascade_flags: CascadeFlags,
rules: Option<StrongRuleNode>,
custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
visited_style: Option<Arc<ComputedValues>>,
) -> Self {
debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
#[cfg(feature = "gecko")]
@ -3177,20 +3286,17 @@ impl<'a> StyleBuilder<'a> {
let reset_style = device.default_computed_values();
let inherited_style = parent_style.unwrap_or(reset_style);
let inherited_style_ignoring_first_line = parent_style_ignoring_first_line.unwrap_or(reset_style);
// FIXME(bz): INHERIT_ALL seems like a fundamentally broken idea. I'm
// FIXME(bz): inherits_all seems like a fundamentally broken idea. I'm
// 99% sure it should give incorrect behavior for table anonymous box
// backgrounds, for example. This code doesn't attempt to make it play
// nice with inherited_style_ignoring_first_line.
let reset_style = if cascade_flags.contains(CascadeFlags::INHERIT_ALL) {
let reset_style = if pseudo.map_or(false, |p| p.inherits_all()) {
inherited_style
} else {
reset_style
};
let mut flags = inherited_style.flags.inherited();
if cascade_flags.contains(CascadeFlags::VISITED_DEPENDENT_ONLY) {
flags.insert(ComputedValueFlags::IS_STYLE_IF_VISITED);
}
let flags = inherited_style.flags.inherited();
StyleBuilder {
device,
@ -3203,7 +3309,7 @@ impl<'a> StyleBuilder<'a> {
custom_properties,
writing_mode: inherited_style.writing_mode,
flags,
visited_style,
visited_style: None,
% for style_struct in data.active_style_structs():
% if style_struct.inherited:
${style_struct.ident}: StyleStructRef::Borrowed(inherited_style.${style_struct.name_lower}_arc()),
@ -3214,11 +3320,6 @@ impl<'a> StyleBuilder<'a> {
}
}
/// Whether we're a visited style.
pub fn is_style_if_visited(&self) -> bool {
self.flags.contains(ComputedValueFlags::IS_STYLE_IF_VISITED)
}
/// NOTE(emilio): This is done so we can compute relative units with respect
/// to the parent style, but all the early properties / writing-mode / etc
/// are already set to the right ones on the kid.
@ -3368,16 +3469,16 @@ impl<'a> StyleBuilder<'a> {
).build()
})
});
Self::new(
let mut ret = Self::new(
device,
parent,
parent,
pseudo,
CascadeFlags::empty(),
/* rules = */ None,
parent.and_then(|p| p.custom_properties().cloned()),
visited_style,
)
);
ret.visited_style = visited_style;
ret
}
/// Returns whether we have a visited style.
@ -3588,18 +3689,6 @@ static CASCADE_PROPERTY: [CascadePropertyFn; ${len(data.longhands)}] = [
% endfor
];
bitflags! {
/// A set of flags to tweak the behavior of the `cascade` function.
pub struct CascadeFlags: u8 {
/// Whether to inherit all styles from the parent. If this flag is not
/// present, non-inherited styles are reset to their initial values.
const INHERIT_ALL = 1;
/// Whether to only cascade properties that are visited dependent.
const VISITED_DEPENDENT_ONLY = 1 << 1;
}
}
/// Performs the CSS cascade, computing new styles for an element from its parent style.
///
/// The arguments are:
@ -3622,9 +3711,43 @@ pub fn cascade<E>(
parent_style: Option<<&ComputedValues>,
parent_style_ignoring_first_line: Option<<&ComputedValues>,
layout_parent_style: Option<<&ComputedValues>,
visited_style: Option<Arc<ComputedValues>>,
visited_rules: Option<<&StrongRuleNode>,
font_metrics_provider: &FontMetricsProvider,
flags: CascadeFlags,
quirks_mode: QuirksMode,
rule_cache: Option<<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues>
where
E: TElement,
{
cascade_rules(
device,
pseudo,
rule_node,
guards,
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
font_metrics_provider,
CascadeMode::Unvisited { visited_rules },
quirks_mode,
rule_cache,
rule_cache_conditions,
element,
)
}
fn cascade_rules<E>(
device: &Device,
pseudo: Option<<&PseudoElement>,
rule_node: &StrongRuleNode,
guards: &StylesheetGuards,
parent_style: Option<<&ComputedValues>,
parent_style_ignoring_first_line: Option<<&ComputedValues>,
layout_parent_style: Option<<&ComputedValues>,
font_metrics_provider: &FontMetricsProvider,
cascade_mode: CascadeMode,
quirks_mode: QuirksMode,
rule_cache: Option<<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
@ -3635,33 +3758,29 @@ where
{
debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
let empty = SmallBitVec::new();
let property_restriction = pseudo.and_then(|p| p.property_restriction());
let restriction = pseudo.and_then(|p| p.property_restriction());
let iter_declarations = || {
rule_node.self_and_ancestors().flat_map(|node| {
let cascade_level = node.cascade_level();
let source = node.style_source();
let declarations = if source.is_some() {
source.as_ref().unwrap().read(cascade_level.guard(guards)).declaration_importance_iter()
} else {
// The root node has no style source.
DeclarationImportanceIterator::new(&[], &empty)
};
let node_importance = node.importance();
let declarations = match node.style_source() {
Some(source) => {
source.read(cascade_level.guard(guards)).declaration_importance_iter()
}
None => DeclarationImportanceIterator::new(&[], &empty),
};
declarations
// Yield declarations later in source order (with more precedence) first.
.rev()
.filter_map(move |(declaration, declaration_importance)| {
if let Some(property_restriction) = property_restriction {
if let Some(restriction) = restriction {
// declaration.id() is either a longhand or a custom
// property. Custom properties are always allowed, but
// longhands are only allowed if they have our
// property_restriction flag set.
// restriction flag set.
if let PropertyDeclarationId::Longhand(id) = declaration.id() {
if !id.flags().contains(property_restriction) {
if !id.flags().contains(restriction) {
return None
}
}
@ -3685,9 +3804,8 @@ where
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
visited_style,
font_metrics_provider,
flags,
cascade_mode,
quirks_mode,
rule_cache,
rule_cache_conditions,
@ -3695,6 +3813,22 @@ where
)
}
/// Whether we're cascading for visited or unvisited styles.
#[derive(Clone, Copy)]
pub enum CascadeMode<'a> {
/// We're cascading for unvisited styles.
Unvisited {
/// The visited rules that should match the visited style.
visited_rules: Option<<&'a StrongRuleNode>,
},
/// We're cascading for visited styles.
Visited {
/// The writing mode of our unvisited style, needed to correctly resolve
/// logical properties..
writing_mode: WritingMode,
},
}
/// NOTE: This function expects the declaration with more priority to appear
/// first.
pub fn apply_declarations<'a, E, F, I>(
@ -3706,9 +3840,8 @@ pub fn apply_declarations<'a, E, F, I>(
parent_style: Option<<&ComputedValues>,
parent_style_ignoring_first_line: Option<<&ComputedValues>,
layout_parent_style: Option<<&ComputedValues>,
visited_style: Option<Arc<ComputedValues>>,
font_metrics_provider: &FontMetricsProvider,
flags: CascadeFlags,
cascade_mode: CascadeMode,
quirks_mode: QuirksMode,
rule_cache: Option<<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
@ -3726,16 +3859,8 @@ where
::std::ptr::eq(parent_style.unwrap(),
parent_style_ignoring_first_line.unwrap()) ||
parent_style.unwrap().pseudo() == Some(PseudoElement::FirstLine));
let (inherited_style, layout_parent_style) = match parent_style {
Some(parent_style) => {
(parent_style,
layout_parent_style.unwrap_or(parent_style))
},
None => {
(device.default_computed_values(),
device.default_computed_values())
}
};
let inherited_style =
parent_style.unwrap_or(device.default_computed_values());
let custom_properties = {
let mut builder =
@ -3760,10 +3885,8 @@ where
parent_style,
parent_style_ignoring_first_line,
pseudo,
flags,
Some(rules.clone()),
custom_properties,
visited_style,
),
cached_system_font: None,
in_media_query: false,
@ -3825,7 +3948,7 @@ where
// Only a few properties are allowed to depend on the visited state
// of links. When cascading visited styles, we can save time by
// only processing these properties.
if flags.contains(CascadeFlags::VISITED_DEPENDENT_ONLY) &&
if matches!(cascade_mode, CascadeMode::Visited { .. }) &&
!physical_longhand_id.is_visited_dependent() {
continue
}
@ -3890,9 +4013,47 @@ where
(CASCADE_PROPERTY[discriminant])(&*declaration, &mut context);
}
% if category_to_cascade_now == "early":
let writing_mode =
WritingMode::new(context.builder.get_inherited_box());
let writing_mode = match cascade_mode {
CascadeMode::Unvisited { .. } => {
WritingMode::new(context.builder.get_inherited_box())
}
CascadeMode::Visited { writing_mode } => writing_mode,
};
context.builder.writing_mode = writing_mode;
if let CascadeMode::Unvisited { visited_rules: Some(visited_rules) } = cascade_mode {
let is_link = pseudo.is_none() && element.unwrap().is_link();
macro_rules! visited_parent {
($parent:expr) => {
if is_link {
$parent
} else {
$parent.map(|p| p.visited_style().unwrap_or(p))
}
}
}
// We could call apply_declarations directly, but that'd cause
// another instantiation of this function which is not great.
context.builder.visited_style = Some(cascade_rules(
device,
pseudo,
visited_rules,
guards,
visited_parent!(parent_style),
visited_parent!(parent_style_ignoring_first_line),
visited_parent!(layout_parent_style),
font_metrics_provider,
CascadeMode::Visited { writing_mode },
quirks_mode,
// The rule cache doesn't care about caching :visited
// styles, we cache the unvisited style instead. We still do
// need to set the caching dependencies properly if present
// though, so the cache conditions need to match.
/* rule_cache = */ None,
&mut *context.rule_cache_conditions.borrow_mut(),
element,
));
}
let mut _skip_font_family = false;
@ -4035,11 +4196,12 @@ where
builder.clear_modified_reset();
StyleAdjuster::new(&mut builder).adjust(
layout_parent_style,
element,
flags,
);
if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
StyleAdjuster::new(&mut builder).adjust(
layout_parent_style.unwrap_or(inherited_style),
element,
);
}
if builder.modified_reset() || !apply_reset {
// If we adjusted any reset structs, we can't cache this ComputedValues.
@ -4048,8 +4210,7 @@ where
// back again. (Aside from being wasted effort, it will be wrong, since
// context.rule_cache_conditions won't be set appropriately if we
// didn't compute those reset properties.)
context.rule_cache_conditions.borrow_mut()
.set_uncacheable();
context.rule_cache_conditions.borrow_mut().set_uncacheable();
}
builder.build()

View file

@ -124,11 +124,6 @@ impl RuleCache {
guards: &StylesheetGuards,
builder_with_early_props: &StyleBuilder,
) -> Option<&ComputedValues> {
if builder_with_early_props.is_style_if_visited() {
// FIXME(emilio): We can probably do better, does it matter much?
return None;
}
// A pseudo-element with property restrictions can result in different
// computed values if it's also used for a non-pseudo.
if builder_with_early_props
@ -166,11 +161,6 @@ impl RuleCache {
return false;
}
if style.is_style_if_visited() {
// FIXME(emilio): We can probably do better, does it matter much?
return false;
}
// A pseudo-element with property restrictions can result in different
// computed values if it's also used for a non-pseudo.
if pseudo.and_then(|p| p.property_restriction()).is_some() {

View file

@ -14,7 +14,7 @@ use element_state::{DocumentState, ElementState};
use fnv::FnvHashMap;
use invalidation::element::document_state::InvalidationMatchingData;
use invalidation::element::element_wrapper::ElementSnapshot;
use properties::{CascadeFlags, ComputedValues, PropertyFlags};
use properties::{ComputedValues, PropertyFlags};
use properties::longhands::display::computed_value::T as Display;
use selector_parser::{AttrValue as SelectorAttrValue, PseudoElementCascadeType, SelectorParser};
use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
@ -223,19 +223,19 @@ impl PseudoElement {
/// properties... Also, I guess it just could do all: inherit on the
/// stylesheet, though chances are that'd be kinda slow if we don't cache
/// them...
pub fn cascade_flags(&self) -> CascadeFlags {
pub fn inherits_all(&self) -> bool {
match *self {
PseudoElement::After |
PseudoElement::Before |
PseudoElement::Selection |
PseudoElement::DetailsContent |
PseudoElement::DetailsSummary => CascadeFlags::empty(),
PseudoElement::DetailsSummary |
// Anonymous table flows shouldn't inherit their parents properties in order
// to avoid doubling up styles such as transformations.
PseudoElement::ServoAnonymousTableCell |
PseudoElement::ServoAnonymousTableRow |
PseudoElement::ServoText |
PseudoElement::ServoInputText => CascadeFlags::empty(),
PseudoElement::ServoInputText => false,
// For tables, we do want style to inherit, because TableWrapper is
// responsible for handling clipping and scrolling, while Table is
@ -248,7 +248,7 @@ impl PseudoElement {
PseudoElement::ServoTableWrapper |
PseudoElement::ServoAnonymousBlock |
PseudoElement::ServoInlineBlockWrapper |
PseudoElement::ServoInlineAbsolute => CascadeFlags::INHERIT_ALL,
PseudoElement::ServoInlineAbsolute => true,
}
}

View file

@ -7,7 +7,7 @@
use app_units::Au;
use dom::TElement;
use properties::{self, CascadeFlags, ComputedValues, StyleBuilder};
use properties::{self, ComputedValues, StyleBuilder};
use properties::computed_value_flags::ComputedValueFlags;
use properties::longhands::display::computed_value::T as Display;
use properties::longhands::float::computed_value::T as Float;
@ -681,7 +681,6 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
&mut self,
layout_parent_style: &ComputedValues,
element: Option<E>,
flags: CascadeFlags,
) where
E: TElement,
{
@ -705,15 +704,6 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
// "Should always have an element around for non-pseudo styles"
// );
// Don't adjust visited styles, visited-dependent properties aren't
// affected by these adjustments and it'd be just wasted work anyway.
//
// It also doesn't make much sense to adjust them, since we don't
// cascade most properties anyway, and they wouldn't be looked up.
if flags.contains(CascadeFlags::VISITED_DEPENDENT_ONLY) {
return;
}
self.adjust_for_visited(element);
#[cfg(feature = "gecko")]
{

View file

@ -71,9 +71,19 @@ impl DeepCloneWithLock for DocumentRule {
}
}
/// A URL matching function for a `@document` rule's condition.
/// The kind of media document that the rule will match.
#[derive(Clone, Copy, Debug, Parse, PartialEq, ToCss)]
#[allow(missing_docs)]
pub enum MediaDocumentKind {
All,
Plugin,
Image,
Video,
}
/// A matching function for a `@document` rule's condition.
#[derive(Clone, Debug, ToCss)]
pub enum UrlMatchingFunction {
pub enum DocumentMatchingFunction {
/// Exact URL matching function. It evaluates to true whenever the
/// URL of the document being styled is exactly the URL given.
Url(CssUrl),
@ -97,6 +107,9 @@ pub enum UrlMatchingFunction {
/// of the document being styled.
#[css(function)]
Regexp(String),
/// Matching function for a media document.
#[css(function)]
MediaDocument(MediaDocumentKind)
}
macro_rules! parse_quoted_or_unquoted_string {
@ -116,60 +129,74 @@ macro_rules! parse_quoted_or_unquoted_string {
};
}
impl UrlMatchingFunction {
impl DocumentMatchingFunction {
/// Parse a URL matching function for a`@document` rule's condition.
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if input
.try(|input| input.expect_function_matching("url-prefix"))
.is_ok()
{
return parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::UrlPrefix);
if let Ok(url) = input.try(|input| CssUrl::parse(context, input)) {
return Ok(DocumentMatchingFunction::Url(url))
}
if input
.try(|input| input.expect_function_matching("domain"))
.is_ok()
{
return parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::Domain);
}
if input
.try(|input| input.expect_function_matching("regexp"))
.is_ok()
{
return input.parse_nested_block(|input| {
Ok(UrlMatchingFunction::Regexp(
input.expect_string()?.as_ref().to_owned(),
let location = input.current_source_location();
let function = input.expect_function()?.clone();
match_ignore_ascii_case! { &function,
"url-prefix" => {
parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::UrlPrefix)
}
"domain" => {
parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::Domain)
}
"regexp" => {
input.parse_nested_block(|input| {
Ok(DocumentMatchingFunction::Regexp(
input.expect_string()?.as_ref().to_owned(),
))
})
}
"media-document" => {
input.parse_nested_block(|input| {
let kind = MediaDocumentKind::parse(input)?;
Ok(DocumentMatchingFunction::MediaDocument(kind))
})
}
_ => {
Err(location.new_custom_error(
StyleParseErrorKind::UnexpectedFunction(function.clone())
))
});
}
}
let url = CssUrl::parse(context, input)?;
Ok(UrlMatchingFunction::Url(url))
}
#[cfg(feature = "gecko")]
/// Evaluate a URL matching function.
pub fn evaluate(&self, device: &Device) -> bool {
use gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
use gecko_bindings::structs::URLMatchingFunction as GeckoUrlMatchingFunction;
use gecko_bindings::structs::DocumentMatchingFunction as GeckoDocumentMatchingFunction;
use nsstring::nsCStr;
let func = match *self {
UrlMatchingFunction::Url(_) => GeckoUrlMatchingFunction::eURL,
UrlMatchingFunction::UrlPrefix(_) => GeckoUrlMatchingFunction::eURLPrefix,
UrlMatchingFunction::Domain(_) => GeckoUrlMatchingFunction::eDomain,
UrlMatchingFunction::Regexp(_) => GeckoUrlMatchingFunction::eRegExp,
DocumentMatchingFunction::Url(_) => GeckoDocumentMatchingFunction::URL,
DocumentMatchingFunction::UrlPrefix(_) => GeckoDocumentMatchingFunction::URLPrefix,
DocumentMatchingFunction::Domain(_) => GeckoDocumentMatchingFunction::Domain,
DocumentMatchingFunction::Regexp(_) => GeckoDocumentMatchingFunction::RegExp,
DocumentMatchingFunction::MediaDocument(_) => GeckoDocumentMatchingFunction::MediaDocument,
};
let pattern = nsCStr::from(match *self {
UrlMatchingFunction::Url(ref url) => url.as_str(),
UrlMatchingFunction::UrlPrefix(ref pat) |
UrlMatchingFunction::Domain(ref pat) |
UrlMatchingFunction::Regexp(ref pat) => pat,
DocumentMatchingFunction::Url(ref url) => url.as_str(),
DocumentMatchingFunction::UrlPrefix(ref pat) |
DocumentMatchingFunction::Domain(ref pat) |
DocumentMatchingFunction::Regexp(ref pat) => pat,
DocumentMatchingFunction::MediaDocument(kind) => {
match kind {
MediaDocumentKind::All => "all",
MediaDocumentKind::Image => "image",
MediaDocumentKind::Plugin => "plugin",
MediaDocumentKind::Video => "video",
}
}
});
unsafe { Gecko_DocumentRule_UseForPresentation(device.pres_context(), &*pattern, func) }
}
@ -190,7 +217,7 @@ impl UrlMatchingFunction {
/// one of those functions evaluates to true.
#[css(comma)]
#[derive(Clone, Debug, ToCss)]
pub struct DocumentCondition(#[css(iterable)] Vec<UrlMatchingFunction>);
pub struct DocumentCondition(#[css(iterable)] Vec<DocumentMatchingFunction>);
impl DocumentCondition {
/// Parse a document condition.
@ -199,7 +226,7 @@ impl DocumentCondition {
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let conditions =
input.parse_comma_separated(|input| UrlMatchingFunction::parse(context, input))?;
input.parse_comma_separated(|input| DocumentMatchingFunction::parse(context, input))?;
let condition = DocumentCondition(conditions);
if !condition.allowed_in(context) {
@ -252,7 +279,7 @@ impl DocumentCondition {
// NOTE(emilio): This technically allows url-prefix("") too, but...
match self.0[0] {
UrlMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(),
DocumentMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(),
_ => false,
}
}

View file

@ -8,7 +8,7 @@ use cssparser::{AtRuleParser, CowRcStr, Parser, ParserInput, QualifiedRuleParser
use cssparser::{parse_one_rule, DeclarationListParser, DeclarationParser, SourceLocation, Token};
use error_reporting::ContextualParseError;
use parser::ParserContext;
use properties::{DeclarationPushMode, Importance, PropertyDeclaration};
use properties::{Importance, PropertyDeclaration};
use properties::{LonghandId, PropertyDeclarationBlock, PropertyId};
use properties::{PropertyDeclarationId, SourcePropertyDeclaration};
use properties::LonghandIdSet;
@ -554,7 +554,6 @@ impl<'a, 'i> QualifiedRuleParser<'i> for KeyframeListParser<'a> {
block.extend(
iter.parser.declarations.drain(),
Importance::Normal,
DeclarationPushMode::Parsing,
);
},
Err((error, slice)) => {

View file

@ -20,7 +20,7 @@ use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps};
#[cfg(feature = "gecko")]
use malloc_size_of::MallocUnconditionalShallowSizeOf;
use media_queries::Device;
use properties::{self, CascadeFlags, ComputedValues};
use properties::{self, CascadeMode, ComputedValues};
use properties::{AnimationRules, PropertyDeclarationBlock};
use rule_cache::{RuleCache, RuleCacheConditions};
use rule_tree::{CascadeLevel, RuleTree, ShadowCascadeOrder, StrongRuleNode, StyleSource};
@ -845,55 +845,18 @@ impl Stylist {
{
debug_assert!(pseudo.is_some() || element.is_some(), "Huh?");
let cascade_flags = pseudo.map_or(CascadeFlags::empty(), |p| p.cascade_flags());
// We need to compute visited values if we have visited rules or if our
// parent has visited values.
let mut visited_values = None;
if inputs.visited_rules.is_some() || parent_style.and_then(|s| s.visited_style()).is_some()
{
// At this point inputs may have visited rules, or rules.
let rule_node = match inputs.visited_rules.as_ref() {
Some(rules) => rules,
None => inputs.rules.as_ref().unwrap_or(self.rule_tree.root()),
};
let inherited_style;
let inherited_style_ignoring_first_line;
let layout_parent_style_for_visited;
if pseudo.is_none() && element.unwrap().is_link() {
// We just want to use our parent style as our parent.
inherited_style = parent_style;
inherited_style_ignoring_first_line = parent_style_ignoring_first_line;
layout_parent_style_for_visited = layout_parent_style;
} else {
// We want to use the visited bits (if any) from our parent
// style as our parent.
inherited_style = parent_style
.map(|parent_style| parent_style.visited_style().unwrap_or(parent_style));
inherited_style_ignoring_first_line = parent_style_ignoring_first_line
.map(|parent_style| parent_style.visited_style().unwrap_or(parent_style));
layout_parent_style_for_visited = layout_parent_style
.map(|parent_style| parent_style.visited_style().unwrap_or(parent_style));
let visited_rules = match inputs.visited_rules.as_ref() {
Some(rules) => Some(rules),
None => {
if parent_style.and_then(|s| s.visited_style()).is_some() {
Some(inputs.rules.as_ref().unwrap_or(self.rule_tree.root()))
} else {
None
}
}
visited_values = Some(properties::cascade::<E>(
&self.device,
pseudo,
rule_node,
guards,
inherited_style,
inherited_style_ignoring_first_line,
layout_parent_style_for_visited,
None,
font_metrics,
cascade_flags | CascadeFlags::VISITED_DEPENDENT_ONLY,
self.quirks_mode,
rule_cache,
rule_cache_conditions,
element,
));
}
};
// Read the comment on `precomputed_values_for_pseudo` to see why it's
// difficult to assert that display: contents nodes never arrive here
@ -909,9 +872,8 @@ impl Stylist {
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
visited_values,
visited_rules,
font_metrics,
cascade_flags,
self.quirks_mode,
rule_cache,
rule_cache_conditions,
@ -1299,8 +1261,9 @@ impl Stylist {
if let Some(containing_shadow) = rule_hash_target.containing_shadow() {
let cascade_data = containing_shadow.style_data();
let host = containing_shadow.host();
if let Some(map) = cascade_data.normal_rules(pseudo_element) {
context.with_shadow_host(Some(containing_shadow.host()), |context| {
context.with_shadow_host(Some(host), |context| {
map.get_all_matching_rules(
element,
rule_hash_target,
@ -1314,7 +1277,26 @@ impl Stylist {
shadow_cascade_order += 1;
}
match_document_author_rules = false;
// NOTE(emilio): Hack so <svg:use> matches document rules as
// expected.
//
// This is not a problem for invalidation and that kind of stuff
// because they still don't match rules based on elements
// outside of the shadow tree, and because the <svg:use> subtree
// is immutable and recreated each time the source tree changes.
//
// See: https://github.com/w3c/svgwg/issues/504
//
// Note that we always resolve URLs against the document, so we
// can't get into a nested shadow situation here.
//
// See: https://github.com/w3c/svgwg/issues/505
//
let host_is_svg_use =
host.is_svg_element() &&
host.local_name() == &*local_name!("use");
match_document_author_rules = host_is_svg_use;
}
}
@ -1581,9 +1563,8 @@ impl Stylist {
Some(parent_style),
Some(parent_style),
Some(parent_style),
None,
&metrics,
CascadeFlags::empty(),
CascadeMode::Unvisited { visited_rules: None },
self.quirks_mode,
/* rule_cache = */ None,
&mut Default::default(),

View file

@ -569,7 +569,7 @@ pub fn get_normalized_vector_and_angle<T: Zero>(
// rotation to not be applied, so we use identity matrix (i.e. rotate3d(0, 0, 1, 0)).
(0., 0., 1., T::zero())
} else {
let vector = vector.normalize();
let vector = vector.robust_normalize();
(vector.x, vector.y, vector.z, angle)
}
}

View file

@ -30,6 +30,17 @@ fn moz_display_values_enabled(context: &ParserContext) -> bool {
}
}
#[cfg(feature = "gecko")]
fn moz_box_display_values_enabled(context: &ParserContext) -> bool {
use gecko_bindings::structs;
use stylesheets::Origin;
context.stylesheet_origin == Origin::UserAgent ||
context.chrome_rules_enabled() ||
unsafe {
structs::StaticPrefs_sVarCache_layout_css_xul_box_display_values_content_enabled
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq,
SpecifiedValueInfo, ToComputedValue, ToCss)]
@ -80,8 +91,10 @@ pub enum Display {
#[cfg(feature = "gecko")]
WebkitInlineBox,
#[cfg(feature = "gecko")]
#[parse(condition = "moz_box_display_values_enabled")]
MozBox,
#[cfg(feature = "gecko")]
#[parse(condition = "moz_box_display_values_enabled")]
MozInlineBox,
#[cfg(feature = "gecko")]
#[parse(condition = "moz_display_values_enabled")]

View file

@ -53,6 +53,8 @@ files = [
"./components/net/tests/parsable_mime/text",
# Mako does not lend itself easily to splitting long lines
"./components/style/properties/helpers/animated_properties.mako.rs",
# Long regexes are long.
"./components/style/gecko/regen_atoms.py",
# Helper macro where actually a pseudo-element per line makes sense.
"./components/style/gecko/non_ts_pseudo_class_list.rs",
"./resources/hsts_preload.json",

View file

@ -17,7 +17,7 @@ use std::sync::atomic::AtomicBool;
use style::context::QuirksMode;
use style::error_reporting::{ParseErrorReporter, ContextualParseError};
use style::media_queries::MediaList;
use style::properties::{CSSWideKeyword, CustomDeclaration, DeclarationPushMode};
use style::properties::{CSSWideKeyword, CustomDeclaration};
use style::properties::{DeclaredValueOwned, Importance};
use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
use style::properties::longhands::{self, animation_timing_function};
@ -34,7 +34,7 @@ pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock
where I: IntoIterator<Item=(PropertyDeclaration, Importance)> {
let mut block = PropertyDeclarationBlock::new();
for (d, i) in iterable {
block.push(d, i, DeclarationPushMode::Append);
block.push(d, i);
}
block
}