diff --git a/components/layout/block.rs b/components/layout/block.rs index 987a3038e9b..c6fedc043bb 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -33,7 +33,8 @@ use display_list_builder::BlockFlowDisplayListBuilding; use display_list_builder::{BorderPaintingMode, DisplayListBuildState, FragmentDisplayListBuilding}; use euclid::{Point2D, Rect, Size2D}; use floats::{ClearType, FloatKind, Floats, PlacementInfo}; -use flow::{BLOCK_POSITION_IS_STATIC, CLEARS_LEFT, CLEARS_RIGHT, INLINE_POSITION_IS_STATIC}; +use flow::{BLOCK_POSITION_IS_STATIC, CLEARS_LEFT, CLEARS_RIGHT}; +use flow::{CONTAINS_TEXT_OR_REPLACED_FRAGMENTS, INLINE_POSITION_IS_STATIC}; use flow::{IS_ABSOLUTELY_POSITIONED}; use flow::{ImmutableFlowUtils, LateAbsolutePositionInfo, MutableFlowUtils, OpaqueFlow}; use flow::{NEEDS_LAYER, PostorderFlowTraversal, PreorderFlowTraversal, FragmentationContext}; @@ -1477,57 +1478,85 @@ impl BlockFlow { pub fn bubble_inline_sizes_for_block(&mut self, consult_children: bool) { let _scope = layout_debug_scope!("block::bubble_inline_sizes {:x}", self.base.debug_id()); - let flags = self.base.flags; - - // Find the maximum inline-size from children. - let mut computation = self.fragment.compute_intrinsic_inline_sizes(); - let (mut left_float_width, mut right_float_width) = (Au(0), Au(0)); - let (mut left_float_width_accumulator, mut right_float_width_accumulator) = (Au(0), Au(0)); - for kid in self.base.child_iter_mut() { - let is_absolutely_positioned = - flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED); - let child_base = flow::mut_base(kid); - let float_kind = child_base.flags.float_kind(); - if !is_absolutely_positioned && consult_children { - computation.content_intrinsic_sizes.minimum_inline_size = - max(computation.content_intrinsic_sizes.minimum_inline_size, - child_base.intrinsic_inline_sizes.minimum_inline_size); - - if child_base.flags.contains(CLEARS_LEFT) { - left_float_width = max(left_float_width, left_float_width_accumulator); - left_float_width_accumulator = Au(0) - } - if child_base.flags.contains(CLEARS_RIGHT) { - right_float_width = max(right_float_width, right_float_width_accumulator); - right_float_width_accumulator = Au(0) - } - - match float_kind { - float::T::none => { - computation.content_intrinsic_sizes.preferred_inline_size = - max(computation.content_intrinsic_sizes.preferred_inline_size, - child_base.intrinsic_inline_sizes.preferred_inline_size); - } - float::T::left => { - left_float_width_accumulator = left_float_width_accumulator + - child_base.intrinsic_inline_sizes.preferred_inline_size; - } - float::T::right => { - right_float_width_accumulator = right_float_width_accumulator + - child_base.intrinsic_inline_sizes.preferred_inline_size; - } + let mut flags = self.base.flags; + if self.definitely_has_zero_block_size() { + // This is kind of a hack for Acid2. But it's a harmless one, because (a) this behavior + // is unspecified; (b) it matches the behavior one would intuitively expect, since + // floats don't flow around blocks that take up no space in the block direction. + flags.remove(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); + } else if self.fragment.is_text_or_replaced() { + flags.insert(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); + } else { + flags.remove(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); + for kid in self.base.children.iter() { + if flow::base(kid).flags.contains(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS) { + flags.insert(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); + break } } } + // Find the maximum inline-size from children. + // + // See: https://lists.w3.org/Archives/Public/www-style/2014Nov/0085.html + // + // FIXME(pcwalton): This doesn't exactly follow that algorithm at the moment. // FIXME(pcwalton): This should consider all float descendants, not just children. - // FIXME(pcwalton): This is not well-spec'd; INTRINSIC specifies to do this, but CSS-SIZING - // says not to. In practice, Gecko and WebKit both do this. + let mut computation = self.fragment.compute_intrinsic_inline_sizes(); + let (mut left_float_width, mut right_float_width) = (Au(0), Au(0)); + let (mut left_float_width_accumulator, mut right_float_width_accumulator) = (Au(0), Au(0)); + let mut preferred_inline_size_of_children_without_text_or_replaced_fragments = Au(0); + for kid in self.base.child_iter_mut() { + if flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED) || !consult_children { + continue + } + + let child_base = flow::mut_base(kid); + let float_kind = child_base.flags.float_kind(); + computation.content_intrinsic_sizes.minimum_inline_size = + max(computation.content_intrinsic_sizes.minimum_inline_size, + child_base.intrinsic_inline_sizes.minimum_inline_size); + + if child_base.flags.contains(CLEARS_LEFT) { + left_float_width = max(left_float_width, left_float_width_accumulator); + left_float_width_accumulator = Au(0) + } + if child_base.flags.contains(CLEARS_RIGHT) { + right_float_width = max(right_float_width, right_float_width_accumulator); + right_float_width_accumulator = Au(0) + } + + match (float_kind, child_base.flags.contains(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS)) { + (float::T::none, true) => { + computation.content_intrinsic_sizes.preferred_inline_size = + max(computation.content_intrinsic_sizes.preferred_inline_size, + child_base.intrinsic_inline_sizes.preferred_inline_size); + } + (float::T::none, false) => { + preferred_inline_size_of_children_without_text_or_replaced_fragments = max( + preferred_inline_size_of_children_without_text_or_replaced_fragments, + child_base.intrinsic_inline_sizes.preferred_inline_size) + } + (float::T::left, _) => { + left_float_width_accumulator = left_float_width_accumulator + + child_base.intrinsic_inline_sizes.preferred_inline_size; + } + (float::T::right, _) => { + right_float_width_accumulator = right_float_width_accumulator + + child_base.intrinsic_inline_sizes.preferred_inline_size; + } + } + } + left_float_width = max(left_float_width, left_float_width_accumulator); right_float_width = max(right_float_width, right_float_width_accumulator); + + computation.content_intrinsic_sizes.preferred_inline_size = + computation.content_intrinsic_sizes.preferred_inline_size + left_float_width + + right_float_width; computation.content_intrinsic_sizes.preferred_inline_size = max(computation.content_intrinsic_sizes.preferred_inline_size, - left_float_width + right_float_width); + preferred_inline_size_of_children_without_text_or_replaced_fragments); self.base.intrinsic_inline_sizes = computation.finish(); self.base.flags = flags @@ -1630,6 +1659,18 @@ impl BlockFlow { FormattingContextType::None | FormattingContextType::Other => {} } } + + fn definitely_has_zero_block_size(&self) -> bool { + if !self.fragment.style.content_block_size().is_definitely_zero() { + return false + } + let border_width = self.fragment.border_width(); + if border_width.block_start != Au(0) || border_width.block_end != Au(0) { + return false + } + let padding = self.fragment.style.logical_padding(); + padding.block_start.is_definitely_zero() && padding.block_end.is_definitely_zero() + } } impl Flow for BlockFlow { diff --git a/components/layout/flow.rs b/components/layout/flow.rs index 9ca27c352ce..a77f636415b 100644 --- a/components/layout/flow.rs +++ b/components/layout/flow.rs @@ -512,6 +512,8 @@ pub trait ImmutableFlowUtils { /// Returns true if floats might flow through this flow, as determined by the float placement /// speculation pass. fn floats_might_flow_through(self) -> bool; + + fn baseline_offset_of_last_line_box_in_flow(self) -> Option; } pub trait MutableFlowUtils { @@ -634,45 +636,48 @@ bitflags! { #[doc = "Whether this flow must have its own layer. Even if this flag is not set, it might"] #[doc = "get its own layer if it's deemed to be likely to overlap flows with their own"] #[doc = "layer."] - const NEEDS_LAYER = 0b0000_0000_0000_0010_0000, + const NEEDS_LAYER = 0b0000_0000_0000_0000_0010_0000, #[doc = "Whether this flow is absolutely positioned. This is checked all over layout, so a"] #[doc = "virtual call is too expensive."] - const IS_ABSOLUTELY_POSITIONED = 0b0000_0000_0000_0100_0000, + const IS_ABSOLUTELY_POSITIONED = 0b0000_0000_0000_0000_0100_0000, #[doc = "Whether this flow clears to the left. This is checked all over layout, so a"] #[doc = "virtual call is too expensive."] - const CLEARS_LEFT = 0b0000_0000_0000_1000_0000, + const CLEARS_LEFT = 0b0000_0000_0000_0000_1000_0000, #[doc = "Whether this flow clears to the right. This is checked all over layout, so a"] #[doc = "virtual call is too expensive."] - const CLEARS_RIGHT = 0b0000_0000_0001_0000_0000, + const CLEARS_RIGHT = 0b0000_0000_0000_0001_0000_0000, #[doc = "Whether this flow is left-floated. This is checked all over layout, so a"] #[doc = "virtual call is too expensive."] - const FLOATS_LEFT = 0b0000_0000_0010_0000_0000, + const FLOATS_LEFT = 0b0000_0000_0000_0010_0000_0000, #[doc = "Whether this flow is right-floated. This is checked all over layout, so a"] #[doc = "virtual call is too expensive."] - const FLOATS_RIGHT = 0b0000_0000_0100_0000_0000, + const FLOATS_RIGHT = 0b0000_0000_0000_0100_0000_0000, #[doc = "Text alignment. \ NB: If you update this, update `TEXT_ALIGN_SHIFT` below."] - const TEXT_ALIGN = 0b0000_0111_1000_0000_0000, + const TEXT_ALIGN = 0b0000_0000_0111_1000_0000_0000, #[doc = "Whether this flow has a fragment with `counter-reset` or `counter-increment` \ styles."] - const AFFECTS_COUNTERS = 0b0000_1000_0000_0000_0000, + const AFFECTS_COUNTERS = 0b0000_0000_1000_0000_0000_0000, #[doc = "Whether this flow's descendants have fragments that affect `counter-reset` or \ `counter-increment` styles."] - const HAS_COUNTER_AFFECTING_CHILDREN = 0b0001_0000_0000_0000_0000, + const HAS_COUNTER_AFFECTING_CHILDREN = 0b0000_0001_0000_0000_0000_0000, #[doc = "Whether this flow behaves as though it had `position: static` for the purposes \ of positioning in the inline direction. This is set for flows with `position: \ static` and `position: relative` as well as absolutely-positioned flows with \ unconstrained positions in the inline direction."] - const INLINE_POSITION_IS_STATIC = 0b0010_0000_0000_0000_0000, + const INLINE_POSITION_IS_STATIC = 0b0000_0010_0000_0000_0000_0000, #[doc = "Whether this flow behaves as though it had `position: static` for the purposes \ of positioning in the block direction. This is set for flows with `position: \ static` and `position: relative` as well as absolutely-positioned flows with \ unconstrained positions in the block direction."] - const BLOCK_POSITION_IS_STATIC = 0b0100_0000_0000_0000_0000, + const BLOCK_POSITION_IS_STATIC = 0b0000_0100_0000_0000_0000_0000, /// Whether any ancestor is a fragmentation container - const CAN_BE_FRAGMENTED = 0b1000_0000_0000_0000_0000, + const CAN_BE_FRAGMENTED = 0b0000_1000_0000_0000_0000_0000, + + /// Whether this flow contains any text and/or replaced fragments. + const CONTAINS_TEXT_OR_REPLACED_FRAGMENTS = 0b0001_0000_0000_0000_0000_0000, } } @@ -1387,6 +1392,21 @@ impl<'a> ImmutableFlowUtils for &'a Flow { } self.as_block().formatting_context_type() == FormattingContextType::None } + + fn baseline_offset_of_last_line_box_in_flow(self) -> Option { + for kid in base(self).children.iter().rev() { + if kid.is_inline_flow() { + return kid.as_inline().baseline_offset_of_last_line() + } + if kid.is_block_like() && + kid.as_block().formatting_context_type() == FormattingContextType::None { + if let Some(baseline_offset) = kid.baseline_offset_of_last_line_box_in_flow() { + return Some(base(kid).position.start.b + baseline_offset) + } + } + } + None + } } impl<'a> MutableFlowUtils for &'a mut Flow { diff --git a/components/layout/flow_list.rs b/components/layout/flow_list.rs index 85657db8acd..5a0d389f5da 100644 --- a/components/layout/flow_list.rs +++ b/components/layout/flow_list.rs @@ -111,6 +111,12 @@ impl<'a> Iterator for FlowListIterator<'a> { } } +impl<'a> DoubleEndedIterator for FlowListIterator<'a> { + fn next_back(&mut self) -> Option<&'a Flow> { + self.it.next_back().map(|x| &**x) + } +} + impl<'a> Iterator for MutFlowListIterator<'a> { type Item = &'a mut Flow; #[inline] diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 67ced630250..2b7527af822 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -1077,7 +1077,7 @@ impl Fragment { /// Returns the sum of the inline-sizes of all the borders of this fragment. Note that this /// can be expensive to compute, so if possible use the `border_padding` field instead. #[inline] - fn border_width(&self) -> LogicalMargin { + pub fn border_width(&self) -> LogicalMargin { let style_border_width = match self.specific { SpecificFragmentInfo::ScannedText(_) | SpecificFragmentInfo::InlineBlock(_) => LogicalMargin::zero(self.style.writing_mode), @@ -1983,14 +1983,19 @@ impl Fragment { } SpecificFragmentInfo::InlineBlock(ref info) => { // See CSS 2.1 ยง 10.8.1. - let block_flow = info.flow_ref.as_block(); - let font_style = self.style.get_font_arc(); - let font_metrics = text::font_metrics_for_style(&mut layout_context.font_context(), - font_style); - InlineMetrics::from_block_height(&font_metrics, - block_flow.base.position.size.block, - block_flow.fragment.margin.block_start, - block_flow.fragment.margin.block_end) + let flow = &info.flow_ref; + let block_flow = flow.as_block(); + let baseline_offset = match flow.baseline_offset_of_last_line_box_in_flow() { + Some(baseline_offset) => baseline_offset, + None => block_flow.fragment.border_box.size.block, + }; + let start_margin = block_flow.fragment.margin.block_start; + let end_margin = block_flow.fragment.margin.block_end; + let depth_below_baseline = flow::base(&**flow).position.size.block - + baseline_offset + end_margin; + InlineMetrics::new(baseline_offset + start_margin, + depth_below_baseline, + baseline_offset) } SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | SpecificFragmentInfo::InlineAbsolute(_) => { @@ -2552,6 +2557,28 @@ impl Fragment { pub fn layer_id_for_overflow_scroll(&self) -> LayerId { LayerId::new_of_type(LayerType::OverflowScroll, self.node.id() as usize) } + + pub fn is_text_or_replaced(&self) -> bool { + match self.specific { + SpecificFragmentInfo::Generic | + SpecificFragmentInfo::InlineAbsolute(_) | + SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | + SpecificFragmentInfo::InlineBlock(_) | + SpecificFragmentInfo::Multicol | + SpecificFragmentInfo::MulticolColumn | + SpecificFragmentInfo::Table | + SpecificFragmentInfo::TableCell | + SpecificFragmentInfo::TableColumn(_) | + SpecificFragmentInfo::TableRow | + SpecificFragmentInfo::TableWrapper => false, + SpecificFragmentInfo::Canvas(_) | + SpecificFragmentInfo::GeneratedContent(_) | + SpecificFragmentInfo::Iframe(_) | + SpecificFragmentInfo::Image(_) | + SpecificFragmentInfo::ScannedText(_) | + SpecificFragmentInfo::UnscannedText(_) => true + } + } } impl fmt::Debug for Fragment { diff --git a/components/layout/inline.rs b/components/layout/inline.rs index 17fdfc82937..05c0d524912 100644 --- a/components/layout/inline.rs +++ b/components/layout/inline.rs @@ -11,7 +11,8 @@ use display_list_builder::DisplayListBuildState; use display_list_builder::{FragmentDisplayListBuilding, InlineFlowDisplayListBuilding}; use euclid::{Point2D, Size2D}; use floats::{FloatKind, Floats, PlacementInfo}; -use flow::{EarlyAbsolutePositionInfo, MutableFlowUtils, OpaqueFlow}; +use flow::{CONTAINS_TEXT_OR_REPLACED_FRAGMENTS, EarlyAbsolutePositionInfo, MutableFlowUtils}; +use flow::{OpaqueFlow}; use flow::{self, BaseFlow, Flow, FlowClass, ForceNonfloatedFlag, IS_ABSOLUTELY_POSITIONED}; use flow_ref; use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, Overflow}; @@ -1275,6 +1276,16 @@ impl InlineFlow { } } } + + pub fn baseline_offset_of_last_line(&self) -> Option { + match self.lines.last() { + None => None, + Some(ref last_line) => { + Some(last_line.bounds.start.b + last_line.bounds.size.block - + last_line.inline_metrics.depth_below_baseline) + } + } + } } impl Flow for InlineFlow { @@ -1300,6 +1311,8 @@ impl Flow for InlineFlow { flow::mut_base(kid).floats = Floats::new(writing_mode); } + self.base.flags.remove(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); + let mut intrinsic_sizes_for_flow = IntrinsicISizesContribution::new(); let mut intrinsic_sizes_for_inline_run = IntrinsicISizesContribution::new(); let mut intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new(); @@ -1358,6 +1371,10 @@ impl Flow for InlineFlow { } fragment.restyle_damage.remove(BUBBLE_ISIZES); + + if fragment.is_text_or_replaced() { + self.base.flags.insert(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); + } } // Flush any remaining nonbroken-run and inline-run intrinsic sizes. diff --git a/components/style/values.rs b/components/style/values.rs index 77a521469ad..2f295bd8775 100644 --- a/components/style/values.rs +++ b/components/style/values.rs @@ -1837,6 +1837,21 @@ pub mod computed { Auto, Calc(CalcLengthOrPercentage), } + + impl LengthOrPercentageOrAuto { + /// Returns true if the computed value is absolute 0 or 0%. + /// + /// (Returns false for calc() values, even if ones that may resolve to zero.) + #[inline] + pub fn is_definitely_zero(&self) -> bool { + use self::LengthOrPercentageOrAuto::*; + match *self { + Length(Au(0)) | Percentage(0.0) => true, + Length(_) | Percentage(_) | Calc(_) | Auto => false + } + } + } + impl fmt::Debug for LengthOrPercentageOrAuto { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self {