layout: Implement the correct hypothetical box behavior for

absolutely-positioned elements declared with `display: inline`.

Although the computed `display` property of elements with `position:
absolute` is `block`, `position: absolute; display: inline` can still
behave differently from `position: absolute; display: block`. This is
because the hypothetical box for `position: absolute` can be at the
position it would have been if it had `display: inline`. CSS 2.1 §
10.3.7 describes this case in a parenthetical:

"The static-position containing block is the containing block of a
hypothetical box that would have been the first box of the element if
its specified 'position' value had been 'static' and its specified
'float' had been 'none'. (Note that due to the rules in section 9.7 this
hypothetical calculation might require also assuming a different
computed value for 'display'.)"

To handle this, I had to change both style computation and layout. For
the former, I added an internal property
`-servo-display-for-hypothetical-box`, which stores the `display` value
supplied by the author, before the computed value is calculated. Flow
construction now uses this value.

As for layout, implementing the proper behavior is tricky because the
position of an inline fragment in the inline direction cannot be
determined until height assignment, which is a parallelism hazard
because in parallel layout widths are computed before heights. However,
in this particular case we can avoid the parallelism hazard because the
inline direction of a hypothetical box only affects the layout if an
absolutely-positioned element is unconstrained in the inline direction.
Therefore, we can just lay out such absolutely-positioned elements with
a bogus inline position and fix it up once the true inline position of
the hypothetical box is computed. The name for this fix-up process is
"late computation of inline position" (and the corresponding fix-up for
the block position is called "late computation of block position").

This improves the header on /r/rust.
This commit is contained in:
Patrick Walton 2014-09-30 13:19:39 -07:00
parent f800960695
commit 287fe3b3ab
17 changed files with 490 additions and 159 deletions

View file

@ -418,7 +418,6 @@ impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> {
return true;
}
let AbsoluteAssignBSizesTraversal(ref ctx) = *self;
block_flow.calculate_abs_block_size_and_margins(*ctx);
true
@ -746,48 +745,6 @@ impl BlockFlow {
max(self.base.intrinsic_inline_sizes.minimum_inline_size, available_inline_size))
}
/// Collect and update static y-offsets bubbled up by kids.
///
/// This would essentially give us offsets of all absolutely positioned
/// direct descendants and all fixed descendants, in tree order.
///
/// Assume that this is called in a bottom-up traversal (specifically, the
/// assign-block-size traversal). So, kids have their flow origin already set.
/// In the case of absolute flow kids, they have their hypothetical box
/// position already set.
fn collect_static_b_offsets_from_kids(&mut self) {
let mut abs_descendant_y_offsets = Vec::new();
for kid in self.base.child_iter() {
let mut gives_abs_offsets = true;
if kid.is_block_like() {
let kid_block = kid.as_block();
if kid_block.is_fixed() || kid_block.is_absolutely_positioned() {
// It won't contribute any offsets for descendants because it
// would be the CB for them.
gives_abs_offsets = false;
// Give the offset for the current absolute flow alone.
abs_descendant_y_offsets.push(kid_block.get_hypothetical_block_start_edge());
} else if kid_block.is_positioned() {
// It won't contribute any offsets because it would be the CB
// for the descendants.
gives_abs_offsets = false;
}
}
if gives_abs_offsets {
let kid_base = flow::mut_base(kid);
// Avoid copying the offset vector.
let offsets = mem::replace(&mut kid_base.abs_descendants.static_b_offsets, Vec::new());
// Consume all the static y-offsets bubbled up by kid.
for y_offset in offsets.into_iter() {
// The offsets are wrt the kid flow box. Translate them to current flow.
abs_descendant_y_offsets.push(y_offset + kid_base.position.start.b);
}
}
}
self.base.abs_descendants.static_b_offsets = abs_descendant_y_offsets;
}
/// If this is the root flow, shifts all kids down and adjusts our size to account for
/// root flow margins, which should never be collapsed according to CSS § 8.3.1.
///
@ -959,7 +916,7 @@ impl BlockFlow {
self.base.flags.set_layers_needed_for_descendants(layers_needed_for_descendants);
// Collect various offsets needed by absolutely positioned descendants.
self.collect_static_b_offsets_from_kids();
(&mut *self as &mut Flow).collect_static_block_offsets_from_children();
// Add in our block-end margin and compute our collapsible margins.
let can_collapse_block_end_margin_with_kids =
@ -1296,7 +1253,7 @@ impl BlockFlow {
/// During normal layout assign-block-size, the absolute flow's position is
/// roughly set to its static position (the position it would have had in
/// the normal flow).
fn get_hypothetical_block_start_edge(&self) -> Au {
pub fn get_hypothetical_block_start_edge(&self) -> Au {
self.base.position.start.b
}
@ -1315,13 +1272,14 @@ impl BlockFlow {
let mut inline_end_floats_impact_child = self.base.flags.impacted_by_right_floats();
let absolute_static_i_offset = if self.is_positioned() {
// This flow is the containing block. The static X offset will be the inline-start padding
// edge.
// This flow is the containing block. The static inline offset will be the inline-start
// padding edge.
self.fragment.border_padding.inline_start
- self.fragment.style().logical_border_width().inline_start
} else {
// For kids, the inline-start margin edge will be at our inline-start content edge. The current static
// offset is at our inline-start margin edge. So move in to the inline-start content edge.
// For kids, the inline-start margin edge will be at our inline-start content edge. The
// current static offset is at our inline-start margin edge. So move in to the
// inline-start content edge.
self.base.absolute_static_i_offset + inline_start_content_edge
};
@ -1355,12 +1313,11 @@ impl BlockFlow {
};
for (i, kid) in self.base.child_iter().enumerate() {
flow::mut_base(kid).block_container_explicit_block_size = explicit_content_size;
if kid.is_block_flow() {
let kid_block = kid.as_block();
kid_block.base.absolute_static_i_offset = absolute_static_i_offset;
kid_block.base.fixed_static_i_offset = fixed_static_i_offset;
{
let mut kid_base = flow::mut_base(kid);
kid_base.block_container_explicit_block_size = explicit_content_size;
kid_base.absolute_static_i_offset = absolute_static_i_offset;
kid_base.fixed_static_i_offset = fixed_static_i_offset;
}
match kid.float_kind() {
@ -1824,17 +1781,35 @@ impl Flow for BlockFlow {
fn is_absolute_containing_block(&self) -> bool {
self.is_positioned()
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
if self.is_absolutely_positioned() &&
self.fragment.style().logical_position().inline_start == LPA_Auto &&
self.fragment.style().logical_position().inline_end == LPA_Auto {
self.base.position.start.i = inline_position
}
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
if self.is_absolutely_positioned() &&
self.fragment.style().logical_position().block_start == LPA_Auto &&
self.fragment.style().logical_position().block_end == LPA_Auto {
self.base.position.start.b = block_position
}
}
}
impl fmt::Show for BlockFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(write!(f, "BlockFlow"));
if self.is_float() {
write!(f, "FloatFlow: {}", self.fragment)
try!(write!(f, "(Float)"));
} else if self.is_root() {
write!(f, "RootFlow: {}", self.fragment)
} else {
write!(f, "BlockFlow: {}", self.fragment)
try!(write!(f, "(Root)"));
} else if self.is_absolutely_positioned() {
try!(write!(f, "(Absolute)"));
}
write!(f, ": {} ({})", self.fragment, self.base)
}
}

View file

@ -27,12 +27,13 @@ use flow::{Flow, ImmutableFlowUtils, MutableOwnedFlowUtils};
use flow::{Descendants, AbsDescendants};
use flow;
use flow_ref::FlowRef;
use fragment::{InlineBlockFragment, InlineBlockFragmentInfo, InputFragment};
use fragment::{Fragment, GenericFragment, IframeFragment, IframeFragmentInfo};
use fragment::{ImageFragment, ImageFragmentInfo, SpecificFragmentInfo, TableFragment};
use fragment::{TableCellFragment, TableColumnFragment, TableColumnFragmentInfo};
use fragment::{Fragment, GenericFragment, IframeFragment, IframeFragmentInfo, ImageFragment};
use fragment::{ImageFragmentInfo, InlineAbsoluteHypotheticalFragment};
use fragment::{InlineAbsoluteHypotheticalFragmentInfo, InlineBlockFragment};
use fragment::{InlineBlockFragmentInfo, InputFragment, InputFragmentInfo, SpecificFragmentInfo};
use fragment::{TableCellFragment, TableColumnFragment, TableColumnFragmentInfo, TableFragment};
use fragment::{TableRowFragment, TableWrapperFragment, UnscannedTextFragment};
use fragment::{UnscannedTextFragmentInfo, InputFragmentInfo};
use fragment::{UnscannedTextFragmentInfo};
use inline::{InlineFragments, InlineFlow};
use parallel;
use table_wrapper::TableWrapperFlow;
@ -311,14 +312,16 @@ impl<'a> FlowConstructor<'a> {
let mut inline_block_flows = vec!();
for f in fragments.fragments.iter() {
match f.specific {
InlineBlockFragment(ref info) => {
inline_block_flows.push(info.flow_ref.clone());
},
InlineBlockFragment(ref info) => inline_block_flows.push(info.flow_ref.clone()),
InlineAbsoluteHypotheticalFragment(ref info) => {
inline_block_flows.push(info.flow_ref.clone())
}
_ => {}
}
}
let mut inline_flow_ref = FlowRef::new(box InlineFlow::from_fragments((*node).clone(), fragments));
let mut inline_flow_ref = FlowRef::new(box InlineFlow::from_fragments((*node).clone(),
fragments));
// Add all the inline-block fragments as children of the inline flow.
for inline_block_flow in inline_block_flows.iter() {
@ -327,7 +330,9 @@ impl<'a> FlowConstructor<'a> {
{
let inline_flow = inline_flow_ref.get_mut().as_inline();
let (ascent, descent) = inline_flow.compute_minimum_ascent_and_descent(self.layout_context.font_context(), &**node.style());
let (ascent, descent) =
inline_flow.compute_minimum_ascent_and_descent(self.layout_context.font_context(),
&**node.style());
inline_flow.minimum_block_size_above_baseline = ascent;
inline_flow.minimum_depth_below_baseline = descent;
TextRunScanner::new().scan_for_runs(self.layout_context.font_context(), inline_flow);
@ -362,21 +367,20 @@ impl<'a> FlowConstructor<'a> {
} else if flow.get().need_anonymous_flow(kid_flow.get()) {
consecutive_siblings.push(kid_flow)
} else {
// Flush any inline fragments that we were gathering up. This allows us to handle
// {ib} splits.
// Flush any inline fragments that we were gathering up. This allows us to
// handle {ib} splits.
debug!("flushing {} inline box(es) to flow A",
inline_fragment_accumulator.fragments.len());
self.flush_inline_fragments_to_flow_or_list(
mem::replace(inline_fragment_accumulator, InlineFragmentsAccumulator::new()),
mem::replace(inline_fragment_accumulator,
InlineFragmentsAccumulator::new()),
flow,
consecutive_siblings,
StripWhitespaceFromStart,
node);
if !consecutive_siblings.is_empty() {
let consecutive_siblings = mem::replace(consecutive_siblings, vec!());
self.generate_anonymous_missing_child(consecutive_siblings,
flow,
node);
self.generate_anonymous_missing_child(consecutive_siblings, flow, node);
}
flow.add_new_child(kid_flow);
}
@ -431,10 +435,12 @@ impl<'a> FlowConstructor<'a> {
inline_fragment_accumulator.fragments.push_all(successor_fragments);
abs_descendants.push_descendants(kid_abs_descendants);
}
ConstructionItemConstructionResult(WhitespaceConstructionItem(whitespace_node, whitespace_style)) => {
ConstructionItemConstructionResult(WhitespaceConstructionItem(whitespace_node,
whitespace_style)) => {
// Add whitespace results. They will be stripped out later on when
// between block elements, and retained when between inline elements.
let fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::from_text(" ".to_string()));
let fragment_info =
UnscannedTextFragment(UnscannedTextFragmentInfo::from_text(" ".to_string()));
let mut fragment = Fragment::from_opaque_node_and_style(whitespace_node,
whitespace_style,
fragment_info);
@ -501,16 +507,16 @@ impl<'a> FlowConstructor<'a> {
// The flow is done.
flow.finish(self.layout_context);
// Set up the absolute descendants.
let is_positioned = flow.get_mut().as_block().is_positioned();
let is_fixed_positioned = flow.get_mut().as_block().is_fixed();
let is_absolutely_positioned = flow.get_mut().as_block().is_absolutely_positioned();
if is_positioned {
// This is the CB for all the absolute descendants.
// This is the containing block for all the absolute descendants.
flow.set_absolute_descendants(abs_descendants);
abs_descendants = Descendants::new();
if is_fixed_positioned || is_absolutely_positioned {
if is_absolutely_positioned {
// This is now the only absolute flow in the subtree which hasn't yet
// reached its CB.
abs_descendants.push(flow.clone());
@ -689,6 +695,31 @@ impl<'a> FlowConstructor<'a> {
ConstructionItemConstructionResult(construction_item)
}
/// This is an annoying case, because the computed `display` value is `block`, but the
/// hypothetical box is inline.
fn build_fragment_for_absolutely_positioned_inline(&mut self, node: &ThreadSafeLayoutNode)
-> ConstructionResult {
let block_flow_result = self.build_flow_for_nonfloated_block(node);
let (block_flow, abs_descendants) = match block_flow_result {
FlowConstructionResult(block_flow, abs_descendants) => (block_flow, abs_descendants),
_ => unreachable!()
};
let fragment_info = InlineAbsoluteHypotheticalFragment(
InlineAbsoluteHypotheticalFragmentInfo::new(block_flow));
let mut fragment = Fragment::new_from_specific_info(node, fragment_info);
let mut fragment_accumulator = InlineFragmentsAccumulator::from_inline_node(node);
fragment_accumulator.fragments.push(&mut fragment);
let construction_item = InlineFragmentsConstructionItem(InlineFragmentsConstructionResult {
splits: Vec::new(),
fragments: fragment_accumulator.finish(),
abs_descendants: abs_descendants,
});
ConstructionItemConstructionResult(construction_item)
}
/// Builds one or more fragments for a node with `display: inline`. This yields an
/// `InlineFragmentsConstructionResult`.
fn build_fragments_for_inline(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
@ -913,7 +944,13 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> {
}
Some(ElementNodeTypeId(_)) => {
let style = node.style();
(style.get_box().display, style.get_box().float, style.get_box().position)
let munged_display = if style.get_box()._servo_display_for_hypothetical_box ==
display::inline {
display::inline
} else {
style.get_box().display
};
(munged_display, style.get_box().float, style.get_box().position)
}
Some(TextNodeTypeId) => (display::inline, float::none, position::static_),
Some(CommentNodeTypeId) |
@ -953,10 +990,18 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> {
node.set_flow_construction_result(self.build_flow_for_nonfloated_block(node))
}
// Inline items that are absolutely-positioned contribute inline fragment construction
// results with a hypothetical fragment.
(display::inline, _, position::absolute) => {
let construction_result =
self.build_fragment_for_absolutely_positioned_inline(node);
node.set_flow_construction_result(construction_result)
}
// Inline items contribute inline fragment construction results.
//
// FIXME(pcwalton, #3307): This is not sufficient to handle floated generated content.
(display::inline, _, _) => {
(display::inline, float::none, _) => {
let construction_result = self.build_fragments_for_inline(node);
node.set_flow_construction_result(construction_result)
}

View file

@ -279,7 +279,7 @@ pub trait Flow: fmt::Show + ToString + Sync {
self.positioning() == position::absolute || self.is_fixed()
}
/// Return true if this is the root of an Absolute flow tree.
/// Return true if this is the root of an absolute flow tree.
fn is_root_of_absolute_flow_tree(&self) -> bool {
false
}
@ -289,6 +289,14 @@ pub trait Flow: fmt::Show + ToString + Sync {
false
}
/// Updates the inline position of a child flow during the assign-height traversal. At present,
/// this is only used for absolutely-positioned inline-blocks.
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au);
/// Updates the block position of a child flow during the assign-height traversal. At present,
/// this is only used for absolutely-positioned inline-blocks.
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au);
/// Return the dimensions of the containing block generated by this flow for absolutely-
/// positioned descendants. For block flows, this is the padding box.
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
@ -426,6 +434,16 @@ pub trait MutableFlowUtils {
/// Builds the display lists for this flow.
fn build_display_list(self, layout_context: &LayoutContext);
/// Gathers static block-offsets bubbled up by kids.
///
/// This essentially gives us offsets of all absolutely positioned direct descendants and all
/// fixed descendants, in tree order.
///
/// This is called in a bottom-up traversal (specifically, the assign-block-size traversal).
/// So, kids have their flow origin already set. In the case of absolute flow kids, they have
/// their hypothetical box position already set.
fn collect_static_block_offsets_from_children(&mut self);
}
pub trait MutableOwnedFlowUtils {
@ -580,15 +598,15 @@ pub struct Descendants {
/// layout.
descendant_links: Vec<FlowRef>,
/// Static y offsets of all descendants from the start of this flow box.
pub static_b_offsets: Vec<Au>,
/// Static block-direction offsets of all descendants from the start of this flow box.
pub static_block_offsets: Vec<Au>,
}
impl Descendants {
pub fn new() -> Descendants {
Descendants {
descendant_links: Vec::new(),
static_b_offsets: Vec::new(),
static_block_offsets: Vec::new(),
}
}
@ -621,7 +639,7 @@ impl Descendants {
let descendant_iter = DescendantIter {
iter: self.descendant_links.slice_from_mut(0).iter_mut(),
};
descendant_iter.zip(self.static_b_offsets.slice_from_mut(0).iter_mut())
descendant_iter.zip(self.static_block_offsets.slice_from_mut(0).iter_mut())
}
}
@ -752,6 +770,16 @@ pub struct BaseFlow {
pub writing_mode: WritingMode,
}
impl fmt::Show for BaseFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,
"CC {}, ADC {}, CADC {}",
self.parallel.children_count.load(SeqCst),
self.abs_descendants.len(),
self.parallel.children_and_absolute_descendant_count.load(SeqCst))
}
}
impl<E, S: Encoder<E>> Encodable<S, E> for BaseFlow {
fn encode(&self, e: &mut S) -> Result<(), E> {
e.emit_struct("base", 0, |e| {
@ -1103,6 +1131,52 @@ impl<'a> MutableFlowUtils for &'a mut Flow + 'a {
}
}
}
/// Collect and update static y-offsets bubbled up by kids.
///
/// This would essentially give us offsets of all absolutely positioned
/// direct descendants and all fixed descendants, in tree order.
///
/// Assume that this is called in a bottom-up traversal (specifically, the
/// assign-block-size traversal). So, kids have their flow origin already set.
/// In the case of absolute flow kids, they have their hypothetical box
/// position already set.
fn collect_static_block_offsets_from_children(&mut self) {
let mut absolute_descendant_block_offsets = Vec::new();
for kid in mut_base(*self).child_iter() {
let mut gives_absolute_offsets = true;
if kid.is_block_like() {
let kid_block = kid.as_block();
if kid_block.is_fixed() || kid_block.is_absolutely_positioned() {
// It won't contribute any offsets for descendants because it would be the
// containing block for them.
gives_absolute_offsets = false;
// Give the offset for the current absolute flow alone.
absolute_descendant_block_offsets.push(
kid_block.get_hypothetical_block_start_edge());
} else if kid_block.is_positioned() {
// It won't contribute any offsets because it would be the containing block
// for the descendants.
gives_absolute_offsets = false;
}
}
if gives_absolute_offsets {
let kid_base = mut_base(kid);
// Avoid copying the offset vector.
let offsets = mem::replace(&mut kid_base.abs_descendants.static_block_offsets,
Vec::new());
// Consume all the static block-offsets bubbled up by kids.
for block_offset in offsets.into_iter() {
// The offsets are with respect to the kid flow's fragment. Translate them to
// that of the current flow.
absolute_descendant_block_offsets.push(
block_offset + kid_base.position.start.b);
}
}
}
mut_base(*self).abs_descendants.static_block_offsets = absolute_descendant_block_offsets
}
}
impl MutableOwnedFlowUtils for FlowRef {

View file

@ -131,6 +131,11 @@ pub enum SpecificFragmentInfo {
GenericFragment,
IframeFragment(IframeFragmentInfo),
ImageFragment(ImageFragmentInfo),
/// A hypothetical box (see CSS 2.1 § 10.3.7) for an absolutely-positioned block that was
/// declared with `display: inline;`.
InlineAbsoluteHypotheticalFragment(InlineAbsoluteHypotheticalFragmentInfo),
InlineBlockFragment(InlineBlockFragmentInfo),
InputFragment(InputFragmentInfo),
ScannedTextFragment(ScannedTextFragmentInfo),
@ -142,7 +147,28 @@ pub enum SpecificFragmentInfo {
UnscannedTextFragment(UnscannedTextFragmentInfo),
}
/// A hypothetical box (see CSS 2.1 § 10.3.7) for an absolutely-positioned block that was declared
/// with `display: inline;`.
///
/// FIXME(pcwalton): Stop leaking this `FlowRef` to layout; that is not memory safe because layout
/// can clone it.
#[deriving(Clone)]
pub struct InlineAbsoluteHypotheticalFragmentInfo {
pub flow_ref: FlowRef,
}
impl InlineAbsoluteHypotheticalFragmentInfo {
pub fn new(flow_ref: FlowRef) -> InlineAbsoluteHypotheticalFragmentInfo {
InlineAbsoluteHypotheticalFragmentInfo {
flow_ref: flow_ref,
}
}
}
/// A fragment that represents an inline-block element.
///
/// FIXME(pcwalton): Stop leaking this `FlowRef` to layout; that is not memory safe because layout
/// can clone it.
#[deriving(Clone)]
pub struct InlineBlockFragmentInfo {
pub flow_ref: FlowRef,
@ -512,8 +538,8 @@ impl Fragment {
self.inline_context.as_mut().unwrap().styles.push(style.clone());
}
/// Uses the style only to estimate the intrinsic inline-sizes. These may be modified for text or
/// replaced elements.
/// Uses the style only to estimate the intrinsic inline-sizes. These may be modified for text
/// or replaced elements.
fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizes {
let (use_margins, use_padding) = match self.specific {
GenericFragment | IframeFragment(_) | ImageFragment(_) | InlineBlockFragment(_) |
@ -521,14 +547,16 @@ impl Fragment {
TableFragment | TableCellFragment => (false, true),
TableWrapperFragment => (true, false),
TableRowFragment => (false, false),
ScannedTextFragment(_) | TableColumnFragment(_) | UnscannedTextFragment(_) => {
ScannedTextFragment(_) | TableColumnFragment(_) | UnscannedTextFragment(_) |
InlineAbsoluteHypotheticalFragment(_) => {
// Styles are irrelevant for these kinds of fragments.
return IntrinsicISizes::new()
}
};
let style = self.style();
let inline_size = MaybeAuto::from_style(style.content_inline_size(), Au::new(0)).specified_or_zero();
let inline_size = MaybeAuto::from_style(style.content_inline_size(),
Au(0)).specified_or_zero();
let margin = style.logical_margin();
let (margin_inline_start, margin_inline_end) = if use_margins {
@ -1147,7 +1175,8 @@ impl Fragment {
text_fragment))
}
GenericFragment | IframeFragment(..) | TableFragment | TableCellFragment |
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) => {
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) |
InlineAbsoluteHypotheticalFragment(_) => {
// FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We
// should have a real `SERVO_DEBUG` system.
debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin))
@ -1216,7 +1245,8 @@ impl Fragment {
match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
TableColumnFragment(_) | TableRowFragment | TableWrapperFragment => {}
TableColumnFragment(_) | TableRowFragment | TableWrapperFragment |
InlineAbsoluteHypotheticalFragment(_) => {}
InlineBlockFragment(ref mut info) => {
let block_flow = info.flow_ref.get_mut().as_block();
result.minimum_inline_size = max(result.minimum_inline_size,
@ -1225,7 +1255,7 @@ impl Fragment {
result.preferred_inline_size = max(result.preferred_inline_size,
block_flow.base.intrinsic_inline_sizes.preferred_inline_size +
block_flow.base.intrinsic_inline_sizes.surround_inline_size);
},
}
ImageFragment(ref mut image_fragment_info) => {
let image_inline_size = image_fragment_info.image_inline_size();
result.minimum_inline_size = max(result.minimum_inline_size, image_inline_size);
@ -1283,8 +1313,9 @@ impl Fragment {
/// TODO: What exactly does this function return? Why is it Au(0) for GenericFragment?
pub fn content_inline_size(&self) -> Au {
match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) => Au(0),
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) |
InputFragment(_) | InlineAbsoluteHypotheticalFragment(_) => Au(0),
ImageFragment(ref image_fragment_info) => {
image_fragment_info.computed_inline_size()
}
@ -1303,7 +1334,7 @@ impl Fragment {
match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) |
InputFragment(_) => Au(0),
InputFragment(_) | InlineAbsoluteHypotheticalFragment(_) => Au(0),
ImageFragment(ref image_fragment_info) => {
image_fragment_info.computed_block_size()
}
@ -1340,7 +1371,9 @@ impl Fragment {
TableRowFragment | TableWrapperFragment | InputFragment(_) => None,
TableColumnFragment(_) => fail!("Table column fragments do not need to split"),
UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
InlineBlockFragment(_) => fail!("Inline blocks do not get split"),
InlineBlockFragment(_) | InlineAbsoluteHypotheticalFragment(_) => {
fail!("Inline blocks or inline absolute hypothetical fragments do not get split")
}
ScannedTextFragment(ref text_fragment_info) => {
let mut new_line_pos = self.new_line_pos.clone();
let cur_new_line_pos = new_line_pos.remove(0).unwrap();
@ -1378,7 +1411,8 @@ impl Fragment {
-> Option<(Option<SplitInfo>, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> {
match self.specific {
GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment |
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) => None,
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) |
InlineAbsoluteHypotheticalFragment(_) => None,
TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
ScannedTextFragment(ref text_fragment_info) => {
@ -1487,7 +1521,8 @@ impl Fragment {
UnscannedTextFragment(_) => {
fail!("Unscanned text fragments should have been scanned by now!")
}
ImageFragment(_) | ScannedTextFragment(_) | InlineBlockFragment(_) => {}
ImageFragment(_) | ScannedTextFragment(_) | InlineBlockFragment(_) |
InlineAbsoluteHypotheticalFragment(_) => {}
};
self.compute_border_padding_margins(container_inline_size);
@ -1501,6 +1536,15 @@ impl Fragment {
let noncontent_inline_size = self.border_padding.inline_start_end();
match self.specific {
InlineAbsoluteHypotheticalFragment(ref mut info) => {
let block_flow = info.flow_ref.get_mut().as_block();
block_flow.base.position.size.inline =
block_flow.base.intrinsic_inline_sizes.preferred_inline_size +
block_flow.base.intrinsic_inline_sizes.surround_inline_size;
// This is a hypothetical box, so it takes up no space.
self.border_box.size.inline = Au(0);
}
InlineBlockFragment(ref mut info) => {
let block_flow = info.flow_ref.get_mut().as_block();
self.border_box.size.inline = block_flow.base.intrinsic_inline_sizes.preferred_inline_size +
@ -1570,7 +1614,8 @@ impl Fragment {
UnscannedTextFragment(_) => {
fail!("Unscanned text fragments should have been scanned by now!")
}
ImageFragment(_) | ScannedTextFragment(_) | InlineBlockFragment(_) => {}
ImageFragment(_) | ScannedTextFragment(_) | InlineBlockFragment(_) |
InlineAbsoluteHypotheticalFragment(_) => {}
}
let style_block_size = self.style().content_block_size();
@ -1616,6 +1661,11 @@ impl Fragment {
let block_flow = info.flow_ref.get_mut().as_block();
self.border_box.size.block = block_flow.base.position.size.block;
}
InlineAbsoluteHypotheticalFragment(ref mut info) => {
// Not the primary fragment, so we do not take the noncontent size into account.
let block_flow = info.flow_ref.get_mut().as_block();
self.border_box.size.block = block_flow.base.position.size.block;
}
_ => fail!("should have been handled above"),
}
}
@ -1641,7 +1691,16 @@ impl Fragment {
// See CSS 2.1 § 10.8.1.
let block_flow = info.flow_ref.get().as_immutable_block();
let font_style = text::computed_style_to_font_style(&*self.style);
let font_metrics = text::font_metrics_for_style(layout_context.font_context(), &font_style);
let font_metrics = text::font_metrics_for_style(layout_context.font_context(),
&font_style);
InlineMetrics::from_block_height(&font_metrics, block_flow.base.position.size.block)
}
InlineAbsoluteHypotheticalFragment(ref info) => {
// See CSS 2.1 § 10.8.1.
let block_flow = info.flow_ref.get().as_immutable_block();
let font_style = text::computed_style_to_font_style(&*self.style);
let font_metrics = text::font_metrics_for_style(layout_context.font_context(),
&font_style);
InlineMetrics::from_block_height(&font_metrics, block_flow.base.position.size.block)
}
_ => {
@ -1704,12 +1763,33 @@ impl Fragment {
/// because the corresponding table flow is the primary fragment.
fn is_primary_fragment(&self) -> bool {
match self.specific {
InlineBlockFragment(_) | TableWrapperFragment => false,
InlineBlockFragment(_) | InlineAbsoluteHypotheticalFragment(_) |
TableWrapperFragment => false,
GenericFragment | IframeFragment(_) | ImageFragment(_) | ScannedTextFragment(_) |
TableFragment | TableCellFragment | TableColumnFragment(_) | TableRowFragment |
UnscannedTextFragment(_) | InputFragment(_) => true,
}
}
pub fn update_late_computed_inline_position_if_necessary(&mut self) {
match self.specific {
InlineAbsoluteHypotheticalFragment(ref mut info) => {
let position = self.border_box.start.i;
info.flow_ref.get_mut().update_late_computed_inline_position_if_necessary(position)
}
_ => {}
}
}
pub fn update_late_computed_block_position_if_necessary(&mut self) {
match self.specific {
InlineAbsoluteHypotheticalFragment(ref mut info) => {
let position = self.border_box.start.b;
info.flow_ref.get_mut().update_late_computed_block_position_if_necessary(position)
}
_ => {}
}
}
}
impl fmt::Show for Fragment {
@ -1720,6 +1800,9 @@ impl fmt::Show for Fragment {
GenericFragment => "GenericFragment",
IframeFragment(_) => "IframeFragment",
ImageFragment(_) => "ImageFragment",
InlineAbsoluteHypotheticalFragment(_) => "InlineAbsoluteHypotheticalFragment",
InlineBlockFragment(_) => "InlineBlockFragment",
InputFragment(_) => "InputFragment",
ScannedTextFragment(_) => "ScannedTextFragment",
TableFragment => "TableFragment",
TableCellFragment => "TableCellFragment",
@ -1727,8 +1810,6 @@ impl fmt::Show for Fragment {
TableRowFragment => "TableRowFragment",
TableWrapperFragment => "TableWrapperFragment",
UnscannedTextFragment(_) => "UnscannedTextFragment",
InlineBlockFragment(_) => "InlineBlockFragment",
InputFragment(_) => "InputFragment",
}));
try!(write!(f, "bp {}", self.border_padding));
try!(write!(f, " "));

View file

@ -7,10 +7,11 @@
use css::node_style::StyledNode;
use context::LayoutContext;
use floats::{FloatLeft, Floats, PlacementInfo};
use flow::{BaseFlow, FlowClass, Flow, InlineFlowClass};
use flow::{BaseFlow, FlowClass, Flow, InlineFlowClass, MutableFlowUtils};
use flow;
use fragment::{Fragment, InlineBlockFragment, ScannedTextFragment, ScannedTextFragmentInfo};
use fragment::{SplitInfo};
use layout_debug;
use fragment::{Fragment, InlineBlockFragment, ScannedTextFragment, ScannedTextFragmentInfo, SplitInfo};
use model::IntrinsicISizes;
use text;
use wrapper::ThreadSafeLayoutNode;
@ -875,16 +876,16 @@ impl InlineFlow {
}
}
/// Sets fragment X positions based on alignment for one line.
fn set_horizontal_fragment_positions(fragments: &mut InlineFragments,
/// Sets fragment positions in the inline direction based on alignment for one line.
fn set_inline_fragment_positions(fragments: &mut InlineFragments,
line: &Line,
line_align: text_align::T) {
// Figure out how much inline-size we have.
let slack_inline_size = Au::max(Au(0), line.green_zone.inline - line.bounds.size.inline);
// Set the fragment x positions based on that alignment.
let mut offset_x = line.bounds.start.i;
offset_x = offset_x + match line_align {
// Set the fragment inline positions based on that alignment.
let mut offset = line.bounds.start.i;
offset = offset + match line_align {
// So sorry, but justified text is more complicated than shuffling line
// coordinates.
//
@ -897,10 +898,41 @@ impl InlineFlow {
for i in each_fragment_index(&line.range) {
let fragment = fragments.get_mut(i.to_uint());
let size = fragment.border_box.size;
fragment.border_box = LogicalRect::new(
fragment.style.writing_mode, offset_x, fragment.border_box.start.b,
size.inline, size.block);
offset_x = offset_x + size.inline;
fragment.border_box = LogicalRect::new(fragment.style.writing_mode,
offset,
fragment.border_box.start.b,
size.inline,
size.block);
fragment.update_late_computed_inline_position_if_necessary();
offset = offset + size.inline;
}
}
/// Sets final fragment positions in the block direction for one line. Assumes that
/// the fragment positions were initially set to the distance from the baseline first.
fn set_block_fragment_positions(fragments: &mut InlineFragments,
line: &Line,
line_distance_from_flow_block_start: Au,
baseline_distance_from_block_start: Au,
largest_depth_below_baseline: Au) {
for fragment_i in each_fragment_index(&line.range) {
let fragment = fragments.get_mut(fragment_i.to_uint());
match fragment.vertical_align() {
vertical_align::top => {
fragment.border_box.start.b = fragment.border_box.start.b +
line_distance_from_flow_block_start
}
vertical_align::bottom => {
fragment.border_box.start.b = fragment.border_box.start.b +
line_distance_from_flow_block_start + baseline_distance_from_block_start +
largest_depth_below_baseline
}
_ => {
fragment.border_box.start.b = fragment.border_box.start.b +
line_distance_from_flow_block_start + baseline_distance_from_block_start
}
}
fragment.update_late_computed_block_position_if_necessary();
}
}
@ -1014,6 +1046,10 @@ impl Flow for InlineFlow {
fn assign_block_size(&mut self, ctx: &LayoutContext) {
let _scope = layout_debug_scope!("inline::assign_block_size {:s}", self.base.debug_id());
// Collect various offsets needed by absolutely positioned inline-block or hypothetical
// absolute descendants.
(&mut *self as &mut Flow).collect_static_block_offsets_from_children();
// Divide the fragments into lines.
//
// TODO(#226): Get the CSS `line-block-size` property from the containing block's style to
@ -1040,10 +1076,10 @@ impl Flow for InlineFlow {
// Now, go through each line and lay out the fragments inside.
let mut line_distance_from_flow_block_start = Au(0);
for line in self.lines.iter_mut() {
// Lay out fragments horizontally.
InlineFlow::set_horizontal_fragment_positions(&mut self.fragments, line, text_align);
// Lay out fragments in the inline direction.
InlineFlow::set_inline_fragment_positions(&mut self.fragments, line, text_align);
// Set the block-start y position of the current line.
// Set the block-start position of the current line.
// `line_height_offset` is updated at the end of the previous loop.
line.bounds.start.b = line_distance_from_flow_block_start;
@ -1053,8 +1089,8 @@ impl Flow for InlineFlow {
// Calculate the largest block-size among fragments with 'top' and 'bottom' values
// respectively.
let (mut largest_block_size_for_top_fragments, mut largest_block_size_for_bottom_fragments) =
(Au(0), Au(0));
let (mut largest_block_size_for_top_fragments,
mut largest_block_size_for_bottom_fragments) = (Au(0), Au(0));
for fragment_i in each_fragment_index(&line.range) {
let fragment = self.fragments.fragments.get_mut(fragment_i.to_uint());
@ -1128,24 +1164,11 @@ impl Flow for InlineFlow {
// Compute the final positions in the block direction of each fragment. Recall that
// `fragment.border_box.start.b` was set to the distance from the baseline above.
for fragment_i in each_fragment_index(&line.range) {
let fragment = self.fragments.get_mut(fragment_i.to_uint());
match fragment.vertical_align() {
vertical_align::top => {
fragment.border_box.start.b = fragment.border_box.start.b +
line_distance_from_flow_block_start
}
vertical_align::bottom => {
fragment.border_box.start.b = fragment.border_box.start.b +
line_distance_from_flow_block_start + baseline_distance_from_block_start +
largest_depth_below_baseline
}
_ => {
fragment.border_box.start.b = fragment.border_box.start.b +
line_distance_from_flow_block_start + baseline_distance_from_block_start
}
}
}
InlineFlow::set_block_fragment_positions(&mut self.fragments,
line,
line_distance_from_flow_block_start,
baseline_distance_from_block_start,
largest_depth_below_baseline);
// This is used to set the block-start y position of the next line in the next loop.
line.bounds.size.block = largest_block_size_above_baseline + largest_depth_below_baseline;
@ -1170,13 +1193,18 @@ impl Flow for InlineFlow {
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
block_flow.base.abs_position = self.base.abs_position +
block_flow.base.abs_position =
self.base.abs_position +
f.border_box.start.to_physical(self.base.writing_mode, container_size);
}
_ => {}
}
}
}
fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {}
fn update_late_computed_block_position_if_necessary(&mut self, _: Au) {}
}
impl fmt::Show for InlineFlow {
@ -1189,7 +1217,7 @@ impl fmt::Show for InlineFlow {
try!(write!(f, ", {}", fragment))
}
}
Ok(())
write!(f, " ({})", self.base)
}
}

View file

@ -521,8 +521,9 @@ fn compute_absolute_position(unsafe_flow: UnsafeFlow,
// Compute the absolute position for the flow.
flow.get_mut().compute_absolute_position();
// Count the number of absolutely-positioned children, so that we can subtract it from
// from `children_and_absolute_descendant_count` to get the number of real children.
// If we are the containing block, count the number of absolutely-positioned children, so
// that we don't double-count them in the `children_and_absolute_descendant_count`
// reference count.
let mut absolutely_positioned_child_count = 0u;
for kid in flow::child_iter(flow.get_mut()) {
if kid.is_absolutely_positioned() {
@ -530,13 +531,12 @@ fn compute_absolute_position(unsafe_flow: UnsafeFlow,
}
}
// Don't enqueue absolutely positioned children.
drop(flow::mut_base(flow.get_mut()).parallel
.children_and_absolute_descendant_count
.fetch_sub(absolutely_positioned_child_count as int,
SeqCst));
// Possibly enqueue the children.
// Enqueue all non-absolutely-positioned children.
for kid in flow::child_iter(flow.get_mut()) {
if !kid.is_absolutely_positioned() {
had_descendants = true;
@ -559,8 +559,7 @@ fn compute_absolute_position(unsafe_flow: UnsafeFlow,
// If there were no more descendants, start building the display list.
if !had_descendants {
build_display_list(mut_owned_flow_to_unsafe_flow(flow),
proxy)
build_display_list(mut_owned_flow_to_unsafe_flow(flow), proxy)
}
}
}

View file

@ -315,6 +315,14 @@ impl Flow for TableFlow {
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
self.block_flow.generated_containing_block_rect()
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow.update_late_computed_block_position_if_necessary(block_position)
}
}
impl fmt::Show for TableFlow {

View file

@ -12,6 +12,7 @@ use context::LayoutContext;
use flow::{TableCaptionFlowClass, FlowClass, Flow};
use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use std::fmt;
/// A table formatting context.
@ -64,6 +65,14 @@ impl Flow for TableCaptionFlow {
fn compute_absolute_position(&mut self) {
self.block_flow.compute_absolute_position()
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow.update_late_computed_block_position_if_necessary(block_position)
}
}
impl fmt::Show for TableCaptionFlow {

View file

@ -123,6 +123,14 @@ impl Flow for TableCellFlow {
fn compute_absolute_position(&mut self) {
self.block_flow.compute_absolute_position()
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow.update_late_computed_block_position_if_necessary(block_position)
}
}
impl fmt::Show for TableCellFlow {

View file

@ -80,6 +80,10 @@ impl Flow for TableColGroupFlow {
/// Table column do not have block-size.
fn assign_block_size(&mut self, _ctx: &LayoutContext) {
}
fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {}
fn update_late_computed_block_position_if_necessary(&mut self, _: Au) {}
}
impl fmt::Show for TableColGroupFlow {

View file

@ -227,6 +227,14 @@ impl Flow for TableRowFlow {
fn compute_absolute_position(&mut self) {
self.block_flow.compute_absolute_position()
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow.update_late_computed_block_position_if_necessary(block_position)
}
}
impl fmt::Show for TableRowFlow {

View file

@ -210,6 +210,14 @@ impl Flow for TableRowGroupFlow {
fn compute_absolute_position(&mut self) {
self.block_flow.compute_absolute_position()
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow.update_late_computed_block_position_if_necessary(block_position)
}
}
impl fmt::Show for TableRowGroupFlow {

View file

@ -333,6 +333,14 @@ impl Flow for TableWrapperFlow {
}
impacted
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow.update_late_computed_block_position_if_necessary(block_position)
}
}
impl fmt::Show for TableWrapperFlow {

View file

@ -133,7 +133,7 @@ pub mod longhands {
<%def name="longhand(name, no_super=False, derived_from=None, experimental=False)">
<%self:raw_longhand name="${name}" derived_from="${derived_from}"
experimental="${experimental}">
experimental="${experimental}" no_super="${no_super}">
${caller.body()}
% if derived_from is None:
pub fn parse_specified(_input: &[ComponentValue], _base_url: &Url)
@ -235,7 +235,7 @@ pub mod longhands {
${single_keyword("border-top-style", values="none solid double dotted dashed hidden groove ridge inset outset")}
% for side in ["right", "bottom", "left"]:
<%self:longhand name="border-${side}-style", no_super="True">
<%self:longhand name="border-${side}-style">
pub use super::border_top_style::{get_initial_value, parse, to_computed_value};
pub type SpecifiedValue = super::border_top_style::SpecifiedValue;
pub mod computed_value {
@ -327,6 +327,25 @@ pub mod longhands {
${single_keyword("float", "none left right")}
${single_keyword("clear", "none left right both")}
<%self:longhand name="-servo-display-for-hypothetical-box" derived_from="display" no_super="True">
pub use super::computed_as_specified as to_computed_value;
pub use super::display::{SpecifiedValue, get_initial_value};
pub use super::display::{parse};
use super::computed;
use super::display;
pub mod computed_value {
pub type T = super::SpecifiedValue;
}
#[inline]
pub fn derive_from_display(_: display::computed_value::T, context: &computed::Context)
-> computed_value::T {
context.display
}
</%self:longhand>
${new_style_struct("InheritedBox", is_inherited=True)}
${single_keyword("direction", "ltr rtl", experimental=True)}
@ -1840,7 +1859,7 @@ fn cascade_with_cached_declarations(applicable_declarations: &[DeclarationBlock]
% if style_struct.inherited:
let mut style_${style_struct.ident} = parent_style.${style_struct.ident}.clone();
% else:
let style_${style_struct.ident} = cached_style.${style_struct.ident}.clone();
let mut style_${style_struct.ident} = cached_style.${style_struct.ident}.clone();
% endif
% endfor

View file

@ -161,3 +161,4 @@ fragment=top != ../html/acid2.html acid2_ref.html
== vertical_align_super_a.html vertical_align_super_ref.html
== vertical_align_text_top_a.html vertical_align_text_top_ref.html
== vertical_align_text_bottom_a.html vertical_align_text_bottom_ref.html
== inline_hypothetical_box_a.html inline_hypothetical_box_ref.html

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<style>
.a {
position: relative;
height: 200px;
}
.b {
position: absolute;
}
.c {
background: blue;
display: inline-block;
height: 8px;
width: 32px;
}
</style>
</head>
<body>
<div class=a><span class=c></span><span class=b style="top: 100px;">Hello</span>world!</div>
<div class=a><span class=c></span><span class=b style="left: 100px;">Hello</span>world!</div>
</body>
</html>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<style>
.a {
position: relative;
height: 200px;
}
.b {
position: absolute;
display: block;
}
.c {
background: blue;
display: inline-block;
height: 8px;
width: 32px;
}
</style>
</head>
<body>
<div class=a><span class=c></span>world!<span class=b style="top: 100px; left: 32px;">Hello</span></div>
<div class=a><span class=c></span>world!<span class=b style="top: 0; left: 100px;">Hello</span></div>
</body>
</html>