mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
layout: Allow inline elements to be containing blocks for
absolutely-positioned elements. This also implements a little bit of the infrastructure needed to support for fragmentation via support for multiple positioned fragments in one flow. Improves Google.
This commit is contained in:
parent
b3b9deafa7
commit
1f0b5889da
19 changed files with 592 additions and 241 deletions
|
@ -33,7 +33,7 @@ use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode};
|
||||||
use display_list_builder::{FragmentDisplayListBuilding};
|
use display_list_builder::{FragmentDisplayListBuilding};
|
||||||
use floats::{ClearType, FloatKind, Floats, PlacementInfo};
|
use floats::{ClearType, FloatKind, Floats, PlacementInfo};
|
||||||
use flow::{self, AbsolutePositionInfo, BaseFlow, ForceNonfloatedFlag, FlowClass, Flow};
|
use flow::{self, AbsolutePositionInfo, BaseFlow, ForceNonfloatedFlag, FlowClass, Flow};
|
||||||
use flow::{ImmutableFlowUtils, PreorderFlowTraversal};
|
use flow::{ImmutableFlowUtils, MutableFlowUtils, OpaqueFlow, PreorderFlowTraversal};
|
||||||
use flow::{PostorderFlowTraversal, mut_base};
|
use flow::{PostorderFlowTraversal, mut_base};
|
||||||
use flow::{BLOCK_POSITION_IS_STATIC, HAS_LEFT_FLOATED_DESCENDANTS, HAS_RIGHT_FLOATED_DESCENDANTS};
|
use flow::{BLOCK_POSITION_IS_STATIC, HAS_LEFT_FLOATED_DESCENDANTS, HAS_RIGHT_FLOATED_DESCENDANTS};
|
||||||
use flow::{IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS, INLINE_POSITION_IS_STATIC};
|
use flow::{IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS, INLINE_POSITION_IS_STATIC};
|
||||||
|
@ -433,26 +433,31 @@ fn translate_including_floats(cur_b: &mut Au, delta: Au, floats: &mut Floats) {
|
||||||
///
|
///
|
||||||
/// Note that flows with position 'fixed' just form a flat list as they all
|
/// Note that flows with position 'fixed' just form a flat list as they all
|
||||||
/// have the Root flow as their CB.
|
/// have the Root flow as their CB.
|
||||||
struct AbsoluteAssignBSizesTraversal<'a>(&'a LayoutContext<'a>);
|
pub struct AbsoluteAssignBSizesTraversal<'a>(pub &'a LayoutContext<'a>);
|
||||||
|
|
||||||
impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> {
|
impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn process(&self, flow: &mut Flow) {
|
fn process(&self, flow: &mut Flow) {
|
||||||
let block_flow = flow.as_block();
|
{
|
||||||
|
|
||||||
// The root of the absolute flow tree is definitely not absolutely
|
// The root of the absolute flow tree is definitely not absolutely
|
||||||
// positioned. Nothing to process here.
|
// positioned. Nothing to process here.
|
||||||
if block_flow.is_root_of_absolute_flow_tree() {
|
let flow: &Flow = flow;
|
||||||
|
if flow.contains_roots_of_absolute_flow_tree() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if !flow.is_block_like() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assert!(block_flow.base.flags.contains(IS_ABSOLUTELY_POSITIONED));
|
let block = flow.as_block();
|
||||||
if !block_flow.base.restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW) {
|
debug_assert!(block.base.flags.contains(IS_ABSOLUTELY_POSITIONED));
|
||||||
|
if !block.base.restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let AbsoluteAssignBSizesTraversal(ref layout_context) = *self;
|
let AbsoluteAssignBSizesTraversal(ref layout_context) = *self;
|
||||||
block_flow.calculate_absolute_block_size_and_margins(*layout_context);
|
block.calculate_absolute_block_size_and_margins(*layout_context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,17 +467,20 @@ impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> {
|
||||||
/// not including the root of the Absolute flow tree.
|
/// not including the root of the Absolute flow tree.
|
||||||
/// After that, it is up to the normal store-overflow traversal to propagate
|
/// After that, it is up to the normal store-overflow traversal to propagate
|
||||||
/// it further up.
|
/// it further up.
|
||||||
struct AbsoluteStoreOverflowTraversal<'a>{
|
pub struct AbsoluteStoreOverflowTraversal<'a>{
|
||||||
layout_context: &'a LayoutContext<'a>,
|
pub layout_context: &'a LayoutContext<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PostorderFlowTraversal for AbsoluteStoreOverflowTraversal<'a> {
|
impl<'a> PostorderFlowTraversal for AbsoluteStoreOverflowTraversal<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn process(&self, flow: &mut Flow) {
|
fn process(&self, flow: &mut Flow) {
|
||||||
|
{
|
||||||
// This will be taken care of by the normal store-overflow traversal.
|
// This will be taken care of by the normal store-overflow traversal.
|
||||||
if flow.is_root_of_absolute_flow_tree() {
|
let flow: &Flow = flow;
|
||||||
|
if flow.contains_roots_of_absolute_flow_tree() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
flow.store_overflow(self.layout_context);
|
flow.store_overflow(self.layout_context);
|
||||||
}
|
}
|
||||||
|
@ -656,56 +664,23 @@ impl BlockFlow {
|
||||||
&mut self.fragment
|
&mut self.fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the size of the Containing Block for this flow.
|
/// Return the size of the containing block for the given immediate absolute descendant of this
|
||||||
|
/// flow.
|
||||||
///
|
///
|
||||||
/// Right now, this only gets the Containing Block size for absolutely
|
/// Right now, this only gets the containing block size for absolutely positioned elements.
|
||||||
/// positioned elements.
|
/// Note: We assume this is called in a top-down traversal, so it is ok to reference the CB.
|
||||||
/// Note: Assume this is called in a top-down traversal, so it is ok to
|
|
||||||
/// reference the CB.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn containing_block_size(&mut self, viewport_size: Size2D<Au>) -> LogicalSize<Au> {
|
pub fn containing_block_size(&mut self, viewport_size: &Size2D<Au>, descendant: OpaqueFlow)
|
||||||
|
-> LogicalSize<Au> {
|
||||||
debug_assert!(self.base.flags.contains(IS_ABSOLUTELY_POSITIONED));
|
debug_assert!(self.base.flags.contains(IS_ABSOLUTELY_POSITIONED));
|
||||||
if self.is_fixed() {
|
if self.is_fixed() {
|
||||||
// Initial containing block is the CB for the root
|
// Initial containing block is the CB for the root
|
||||||
LogicalSize::from_physical(self.base.writing_mode, viewport_size)
|
LogicalSize::from_physical(self.base.writing_mode, *viewport_size)
|
||||||
} else {
|
} else {
|
||||||
self.base.absolute_cb.generated_containing_block_rect().size
|
self.base.absolute_cb.generated_containing_block_size(descendant)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Traverse the Absolute flow tree in preorder.
|
|
||||||
///
|
|
||||||
/// Traverse all your direct absolute descendants, who will then traverse
|
|
||||||
/// their direct absolute descendants.
|
|
||||||
///
|
|
||||||
/// Return true if the traversal is to continue or false to stop.
|
|
||||||
fn traverse_preorder_absolute_flows<T>(&mut self, traversal: &mut T)
|
|
||||||
where T: PreorderFlowTraversal {
|
|
||||||
let flow = self as &mut Flow;
|
|
||||||
|
|
||||||
traversal.process(flow);
|
|
||||||
|
|
||||||
let descendant_offset_iter = mut_base(flow).abs_descendants.iter();
|
|
||||||
for ref mut descendant_link in descendant_offset_iter {
|
|
||||||
descendant_link.as_block().traverse_preorder_absolute_flows(traversal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Traverse the Absolute flow tree in postorder.
|
|
||||||
///
|
|
||||||
/// Return true if the traversal is to continue or false to stop.
|
|
||||||
fn traverse_postorder_absolute_flows<T>(&mut self, traversal: &mut T)
|
|
||||||
where T: PostorderFlowTraversal {
|
|
||||||
let flow = self as &mut Flow;
|
|
||||||
|
|
||||||
for descendant_link in mut_base(flow).abs_descendants.iter() {
|
|
||||||
let block = descendant_link.as_block();
|
|
||||||
block.traverse_postorder_absolute_flows(traversal);
|
|
||||||
}
|
|
||||||
|
|
||||||
traversal.process(flow)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return true if this has a replaced fragment.
|
/// Return true if this has a replaced fragment.
|
||||||
///
|
///
|
||||||
/// Text, Images, Inline Block and
|
/// Text, Images, Inline Block and
|
||||||
|
@ -1006,14 +981,15 @@ impl BlockFlow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_root_of_absolute_flow_tree() {
|
if (&*self as &Flow).contains_roots_of_absolute_flow_tree() {
|
||||||
// Assign block-sizes for all flows in this absolute flow tree.
|
// Assign block-sizes for all flows in this absolute flow tree.
|
||||||
// This is preorder because the block-size of an absolute flow may depend on
|
// This is preorder because the block-size of an absolute flow may depend on
|
||||||
// the block-size of its containing block, which may also be an absolute flow.
|
// the block-size of its containing block, which may also be an absolute flow.
|
||||||
self.traverse_preorder_absolute_flows(&mut AbsoluteAssignBSizesTraversal(
|
(&mut *self as &mut Flow).traverse_preorder_absolute_flows(
|
||||||
layout_context));
|
&mut AbsoluteAssignBSizesTraversal(layout_context));
|
||||||
// Store overflow for all absolute descendants.
|
// Store overflow for all absolute descendants.
|
||||||
self.traverse_postorder_absolute_flows(&mut AbsoluteStoreOverflowTraversal {
|
(&mut *self as &mut Flow).traverse_postorder_absolute_flows(
|
||||||
|
&mut AbsoluteStoreOverflowTraversal {
|
||||||
layout_context: layout_context,
|
layout_context: layout_context,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1086,8 +1062,10 @@ impl BlockFlow {
|
||||||
/// + block-size for the flow
|
/// + block-size for the flow
|
||||||
/// + position in the block direction of the flow with respect to its Containing Block.
|
/// + position in the block direction of the flow with respect to its Containing Block.
|
||||||
/// + block-size, vertical margins, and y-coordinate for the flow's box.
|
/// + block-size, vertical margins, and y-coordinate for the flow's box.
|
||||||
fn calculate_absolute_block_size_and_margins(&mut self, ctx: &LayoutContext) {
|
fn calculate_absolute_block_size_and_margins(&mut self, layout_context: &LayoutContext) {
|
||||||
let containing_block_block_size = self.containing_block_size(ctx.shared.screen_size).block;
|
let opaque_self = OpaqueFlow::from_flow(self);
|
||||||
|
let containing_block_block_size =
|
||||||
|
self.containing_block_size(&layout_context.shared.screen_size, opaque_self).block;
|
||||||
|
|
||||||
// This is the stored content block-size value from assign-block-size
|
// This is the stored content block-size value from assign-block-size
|
||||||
let content_block_size = self.fragment.border_box.size.block;
|
let content_block_size = self.fragment.border_box.size.block;
|
||||||
|
@ -1250,8 +1228,9 @@ impl BlockFlow {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate containing block inline size.
|
// Calculate containing block inline size.
|
||||||
|
let opaque_self = OpaqueFlow::from_flow(self);
|
||||||
let containing_block_size = if flags.contains(IS_ABSOLUTELY_POSITIONED) {
|
let containing_block_size = if flags.contains(IS_ABSOLUTELY_POSITIONED) {
|
||||||
self.containing_block_size(layout_context.shared.screen_size).inline
|
self.containing_block_size(&layout_context.shared.screen_size, opaque_self).inline
|
||||||
} else {
|
} else {
|
||||||
content_inline_size
|
content_inline_size
|
||||||
};
|
};
|
||||||
|
@ -1724,13 +1703,15 @@ impl Flow for BlockFlow {
|
||||||
self.fragment.relative_position(&self.base
|
self.fragment.relative_position(&self.base
|
||||||
.absolute_position_info
|
.absolute_position_info
|
||||||
.relative_containing_block_size);
|
.relative_containing_block_size);
|
||||||
if self.is_positioned() {
|
if self.contains_positioned_fragments() {
|
||||||
|
let border_box_origin = (self.fragment.border_box -
|
||||||
|
self.fragment.style.logical_border_width()).start;
|
||||||
self.base
|
self.base
|
||||||
.absolute_position_info
|
.absolute_position_info
|
||||||
.stacking_relative_position_of_absolute_containing_block =
|
.stacking_relative_position_of_absolute_containing_block =
|
||||||
self.base.stacking_relative_position +
|
self.base.stacking_relative_position +
|
||||||
(self.generated_containing_block_rect().start +
|
(border_box_origin + relative_offset).to_physical(self.base.writing_mode,
|
||||||
relative_offset).to_physical(self.base.writing_mode, container_size)
|
container_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute absolute position info for children.
|
// Compute absolute position info for children.
|
||||||
|
@ -1741,7 +1722,7 @@ impl Flow for BlockFlow {
|
||||||
logical_border_width.inline_start,
|
logical_border_width.inline_start,
|
||||||
logical_border_width.block_start);
|
logical_border_width.block_start);
|
||||||
let position = position.to_physical(self.base.writing_mode, container_size);
|
let position = position.to_physical(self.base.writing_mode, container_size);
|
||||||
if self.is_positioned() {
|
if self.contains_positioned_fragments() {
|
||||||
position
|
position
|
||||||
} else {
|
} else {
|
||||||
// We establish a stacking context but are not positioned. (This will happen
|
// We establish a stacking context but are not positioned. (This will happen
|
||||||
|
@ -1850,17 +1831,10 @@ impl Flow for BlockFlow {
|
||||||
self.fragment.style.get_box().position
|
self.fragment.style.get_box().position
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if this is the root of an Absolute flow tree.
|
|
||||||
///
|
|
||||||
/// It has to be either relatively positioned or the Root flow.
|
|
||||||
fn is_root_of_absolute_flow_tree(&self) -> bool {
|
|
||||||
self.is_relatively_positioned() || self.is_root()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the dimensions of the containing block generated by this flow for absolutely-
|
/// Return the dimensions of the containing block generated by this flow for absolutely-
|
||||||
/// positioned descendants. For block flows, this is the padding box.
|
/// positioned descendants. For block flows, this is the padding box.
|
||||||
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
|
fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
self.fragment.border_box - self.fragment.style().logical_border_width()
|
(self.fragment.border_box - self.fragment.style().logical_border_width()).size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layer_id(&self, fragment_index: u32) -> LayerId {
|
fn layer_id(&self, fragment_index: u32) -> LayerId {
|
||||||
|
@ -1871,7 +1845,7 @@ impl Flow for BlockFlow {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_absolute_containing_block(&self) -> bool {
|
fn is_absolute_containing_block(&self) -> bool {
|
||||||
self.is_positioned()
|
self.contains_positioned_fragments()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||||
|
@ -2491,7 +2465,8 @@ impl ISizeAndMarginsComputer for AbsoluteNonReplaced {
|
||||||
_: Au,
|
_: Au,
|
||||||
layout_context: &LayoutContext)
|
layout_context: &LayoutContext)
|
||||||
-> Au {
|
-> Au {
|
||||||
block.containing_block_size(layout_context.shared.screen_size).inline
|
let opaque_block = OpaqueFlow::from_flow(block);
|
||||||
|
block.containing_block_size(&layout_context.shared.screen_size, opaque_block).inline
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_inline_position_of_flow_if_necessary(&self,
|
fn set_inline_position_of_flow_if_necessary(&self,
|
||||||
|
@ -2601,8 +2576,9 @@ impl ISizeAndMarginsComputer for AbsoluteReplaced {
|
||||||
_: Au,
|
_: Au,
|
||||||
layout_context: &LayoutContext)
|
layout_context: &LayoutContext)
|
||||||
-> MaybeAuto {
|
-> MaybeAuto {
|
||||||
|
let opaque_block = OpaqueFlow::from_flow(block);
|
||||||
let containing_block_inline_size =
|
let containing_block_inline_size =
|
||||||
block.containing_block_size(layout_context.shared.screen_size).inline;
|
block.containing_block_size(&layout_context.shared.screen_size, opaque_block).inline;
|
||||||
let fragment = block.fragment();
|
let fragment = block.fragment();
|
||||||
fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size);
|
fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size);
|
||||||
// For replaced absolute flow, the rest of the constraint solving will
|
// For replaced absolute flow, the rest of the constraint solving will
|
||||||
|
@ -2610,9 +2586,13 @@ impl ISizeAndMarginsComputer for AbsoluteReplaced {
|
||||||
MaybeAuto::Specified(fragment.content_inline_size())
|
MaybeAuto::Specified(fragment.content_inline_size())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, ctx: &LayoutContext)
|
fn containing_block_inline_size(&self,
|
||||||
|
block: &mut BlockFlow,
|
||||||
|
_: Au,
|
||||||
|
layout_context: &LayoutContext)
|
||||||
-> Au {
|
-> Au {
|
||||||
block.containing_block_size(ctx.shared.screen_size).inline
|
let opaque_block = OpaqueFlow::from_flow(block);
|
||||||
|
block.containing_block_size(&layout_context.shared.screen_size, opaque_block).inline
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_inline_position_of_flow_if_necessary(&self,
|
fn set_inline_position_of_flow_if_necessary(&self,
|
||||||
|
|
|
@ -24,12 +24,9 @@ use flow::{IS_ABSOLUTELY_POSITIONED};
|
||||||
use flow;
|
use flow;
|
||||||
use flow_ref::FlowRef;
|
use flow_ref::FlowRef;
|
||||||
use fragment::{Fragment, GeneratedContentInfo, IframeFragmentInfo};
|
use fragment::{Fragment, GeneratedContentInfo, IframeFragmentInfo};
|
||||||
use fragment::CanvasFragmentInfo;
|
use fragment::{CanvasFragmentInfo, ImageFragmentInfo, InlineAbsoluteFragmentInfo};
|
||||||
use fragment::ImageFragmentInfo;
|
use fragment::{InlineAbsoluteHypotheticalFragmentInfo, TableColumnFragmentInfo};
|
||||||
use fragment::InlineAbsoluteHypotheticalFragmentInfo;
|
use fragment::{InlineBlockFragmentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo};
|
||||||
use fragment::TableColumnFragmentInfo;
|
|
||||||
use fragment::UnscannedTextFragmentInfo;
|
|
||||||
use fragment::{InlineBlockFragmentInfo, SpecificFragmentInfo};
|
|
||||||
use incremental::{RECONSTRUCT_FLOW, RestyleDamage};
|
use incremental::{RECONSTRUCT_FLOW, RestyleDamage};
|
||||||
use inline::{InlineFlow, InlineFragmentNodeInfo};
|
use inline::{InlineFlow, InlineFragmentNodeInfo};
|
||||||
use list_item::{ListItemFlow, ListStyleTypeContent};
|
use list_item::{ListItemFlow, ListStyleTypeContent};
|
||||||
|
@ -121,7 +118,7 @@ pub struct InlineFragmentsConstructionResult {
|
||||||
pub splits: LinkedList<InlineBlockSplit>,
|
pub splits: LinkedList<InlineBlockSplit>,
|
||||||
|
|
||||||
/// Any fragments that succeed the {ib} splits.
|
/// Any fragments that succeed the {ib} splits.
|
||||||
pub fragments: LinkedList<Fragment>,
|
pub fragments: IntermediateInlineFragments,
|
||||||
|
|
||||||
/// Any absolute descendants that we're bubbling up.
|
/// Any absolute descendants that we're bubbling up.
|
||||||
pub abs_descendants: AbsDescendants,
|
pub abs_descendants: AbsDescendants,
|
||||||
|
@ -156,16 +153,44 @@ pub struct InlineFragmentsConstructionResult {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct InlineBlockSplit {
|
pub struct InlineBlockSplit {
|
||||||
/// The inline fragments that precede the flow.
|
/// The inline fragments that precede the flow.
|
||||||
pub predecessors: LinkedList<Fragment>,
|
pub predecessors: IntermediateInlineFragments,
|
||||||
|
|
||||||
/// The flow that caused this {ib} split.
|
/// The flow that caused this {ib} split.
|
||||||
pub flow: FlowRef,
|
pub flow: FlowRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Holds inline fragments and absolute descendants.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct IntermediateInlineFragments {
|
||||||
|
/// The list of fragments.
|
||||||
|
pub fragments: LinkedList<Fragment>,
|
||||||
|
|
||||||
|
/// The list of absolute descendants of those inline fragments.
|
||||||
|
pub absolute_descendants: AbsDescendants,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntermediateInlineFragments {
|
||||||
|
fn new() -> IntermediateInlineFragments {
|
||||||
|
IntermediateInlineFragments {
|
||||||
|
fragments: LinkedList::new(),
|
||||||
|
absolute_descendants: Descendants::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.fragments.is_empty() && self.absolute_descendants.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_all(&mut self, mut other: IntermediateInlineFragments) {
|
||||||
|
self.fragments.append(&mut other.fragments);
|
||||||
|
self.absolute_descendants.push_descendants(other.absolute_descendants);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Holds inline fragments that we're gathering for children of an inline node.
|
/// Holds inline fragments that we're gathering for children of an inline node.
|
||||||
struct InlineFragmentsAccumulator {
|
struct InlineFragmentsAccumulator {
|
||||||
/// The list of fragments.
|
/// The list of fragments.
|
||||||
fragments: LinkedList<Fragment>,
|
fragments: IntermediateInlineFragments,
|
||||||
|
|
||||||
/// Whether we've created a range to enclose all the fragments. This will be Some() if the
|
/// Whether we've created a range to enclose all the fragments. This will be Some() if the
|
||||||
/// outer node is an inline and None otherwise.
|
/// outer node is an inline and None otherwise.
|
||||||
|
@ -175,37 +200,38 @@ struct InlineFragmentsAccumulator {
|
||||||
impl InlineFragmentsAccumulator {
|
impl InlineFragmentsAccumulator {
|
||||||
fn new() -> InlineFragmentsAccumulator {
|
fn new() -> InlineFragmentsAccumulator {
|
||||||
InlineFragmentsAccumulator {
|
InlineFragmentsAccumulator {
|
||||||
fragments: LinkedList::new(),
|
fragments: IntermediateInlineFragments::new(),
|
||||||
enclosing_node: None,
|
enclosing_node: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_inline_node(node: &ThreadSafeLayoutNode) -> InlineFragmentsAccumulator {
|
fn from_inline_node(node: &ThreadSafeLayoutNode) -> InlineFragmentsAccumulator {
|
||||||
let fragments = LinkedList::new();
|
|
||||||
InlineFragmentsAccumulator {
|
InlineFragmentsAccumulator {
|
||||||
fragments: fragments,
|
fragments: IntermediateInlineFragments::new(),
|
||||||
enclosing_node: Some(InlineFragmentNodeInfo {
|
enclosing_node: Some(InlineFragmentNodeInfo {
|
||||||
address: OpaqueNodeMethods::from_thread_safe_layout_node(node),
|
address: OpaqueNodeMethods::from_thread_safe_layout_node(node),
|
||||||
style: node.style().clone() }),
|
style: node.style().clone(),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_all(&mut self, mut fragments: LinkedList<Fragment>) {
|
fn push(&mut self, fragment: Fragment) {
|
||||||
if fragments.len() == 0 {
|
self.fragments.fragments.push_back(fragment)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fragments.append(&mut fragments)
|
fn push_all(&mut self, mut fragments: IntermediateInlineFragments) {
|
||||||
|
self.fragments.fragments.append(&mut fragments.fragments);
|
||||||
|
self.fragments.absolute_descendants.push_descendants(fragments.absolute_descendants);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_dlist(self) -> LinkedList<Fragment> {
|
fn to_intermediate_inline_fragments(self) -> IntermediateInlineFragments {
|
||||||
let InlineFragmentsAccumulator {
|
let InlineFragmentsAccumulator {
|
||||||
mut fragments,
|
mut fragments,
|
||||||
enclosing_node,
|
enclosing_node,
|
||||||
} = self;
|
} = self;
|
||||||
if let Some(enclosing_node) = enclosing_node {
|
if let Some(enclosing_style) = enclosing_style {
|
||||||
let frag_len = fragments.len();
|
let frag_len = fragments.fragments.len();
|
||||||
for (idx, frag) in fragments.iter_mut().enumerate() {
|
for (idx, frag) in fragments.fragments.iter_mut().enumerate() {
|
||||||
|
|
||||||
// frag is first inline fragment in the inline node
|
// frag is first inline fragment in the inline node
|
||||||
let is_first = idx == 0;
|
let is_first = idx == 0;
|
||||||
|
@ -355,7 +381,7 @@ impl<'a> FlowConstructor<'a> {
|
||||||
flow_list: &mut Vec<FlowRef>,
|
flow_list: &mut Vec<FlowRef>,
|
||||||
whitespace_stripping: WhitespaceStrippingMode,
|
whitespace_stripping: WhitespaceStrippingMode,
|
||||||
node: &ThreadSafeLayoutNode) {
|
node: &ThreadSafeLayoutNode) {
|
||||||
let mut fragments = fragment_accumulator.to_dlist();
|
let mut fragments = fragment_accumulator.to_intermediate_inline_fragments();
|
||||||
if fragments.is_empty() {
|
if fragments.is_empty() {
|
||||||
return
|
return
|
||||||
};
|
};
|
||||||
|
@ -363,13 +389,13 @@ impl<'a> FlowConstructor<'a> {
|
||||||
match whitespace_stripping {
|
match whitespace_stripping {
|
||||||
WhitespaceStrippingMode::None => {}
|
WhitespaceStrippingMode::None => {}
|
||||||
WhitespaceStrippingMode::FromStart => {
|
WhitespaceStrippingMode::FromStart => {
|
||||||
strip_ignorable_whitespace_from_start(&mut fragments);
|
strip_ignorable_whitespace_from_start(&mut fragments.fragments);
|
||||||
if fragments.is_empty() {
|
if fragments.is_empty() {
|
||||||
return
|
return
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
WhitespaceStrippingMode::FromEnd => {
|
WhitespaceStrippingMode::FromEnd => {
|
||||||
strip_ignorable_whitespace_from_end(&mut fragments);
|
strip_ignorable_whitespace_from_end(&mut fragments.fragments);
|
||||||
if fragments.is_empty() {
|
if fragments.is_empty() {
|
||||||
return
|
return
|
||||||
};
|
};
|
||||||
|
@ -378,7 +404,7 @@ impl<'a> FlowConstructor<'a> {
|
||||||
|
|
||||||
// Build a list of all the inline-block fragments before fragments is moved.
|
// Build a list of all the inline-block fragments before fragments is moved.
|
||||||
let mut inline_block_flows = vec!();
|
let mut inline_block_flows = vec!();
|
||||||
for fragment in fragments.iter() {
|
for fragment in fragments.fragments.iter() {
|
||||||
match fragment.specific {
|
match fragment.specific {
|
||||||
SpecificFragmentInfo::InlineBlock(ref info) => {
|
SpecificFragmentInfo::InlineBlock(ref info) => {
|
||||||
inline_block_flows.push(info.flow_ref.clone())
|
inline_block_flows.push(info.flow_ref.clone())
|
||||||
|
@ -386,6 +412,9 @@ impl<'a> FlowConstructor<'a> {
|
||||||
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => {
|
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => {
|
||||||
inline_block_flows.push(info.flow_ref.clone())
|
inline_block_flows.push(info.flow_ref.clone())
|
||||||
}
|
}
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(ref info) => {
|
||||||
|
inline_block_flows.push(info.flow_ref.clone())
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -393,16 +422,25 @@ impl<'a> FlowConstructor<'a> {
|
||||||
// We must scan for runs before computing minimum ascent and descent because scanning
|
// We must scan for runs before computing minimum ascent and descent because scanning
|
||||||
// for runs might collapse so much whitespace away that only hypothetical fragments
|
// for runs might collapse so much whitespace away that only hypothetical fragments
|
||||||
// remain. In that case the inline flow will compute its ascent and descent to be zero.
|
// remain. In that case the inline flow will compute its ascent and descent to be zero.
|
||||||
let fragments = TextRunScanner::new().scan_for_runs(self.layout_context.font_context(),
|
let scanned_fragments =
|
||||||
fragments);
|
TextRunScanner::new().scan_for_runs(self.layout_context.font_context(),
|
||||||
|
fragments.fragments);
|
||||||
let mut inline_flow_ref =
|
let mut inline_flow_ref =
|
||||||
FlowRef::new(box InlineFlow::from_fragments(fragments, node.style().writing_mode));
|
FlowRef::new(box InlineFlow::from_fragments(scanned_fragments,
|
||||||
|
node.style().writing_mode));
|
||||||
|
|
||||||
// Add all the inline-block fragments as children of the inline flow.
|
// Add all the inline-block fragments as children of the inline flow.
|
||||||
for inline_block_flow in inline_block_flows.iter() {
|
for inline_block_flow in inline_block_flows.iter() {
|
||||||
inline_flow_ref.add_new_child(inline_block_flow.clone());
|
inline_flow_ref.add_new_child(inline_block_flow.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up absolute descendants as necessary.
|
||||||
|
let contains_positioned_fragments = inline_flow_ref.contains_positioned_fragments();
|
||||||
|
if contains_positioned_fragments {
|
||||||
|
// This is the containing block for all the absolute descendants.
|
||||||
|
inline_flow_ref.set_absolute_descendants(fragments.absolute_descendants);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let inline_flow = inline_flow_ref.as_inline();
|
let inline_flow = inline_flow_ref.as_inline();
|
||||||
|
|
||||||
|
@ -447,7 +485,7 @@ impl<'a> FlowConstructor<'a> {
|
||||||
// Flush any inline fragments that we were gathering up. This allows us to
|
// Flush any inline fragments that we were gathering up. This allows us to
|
||||||
// handle {ib} splits.
|
// handle {ib} splits.
|
||||||
debug!("flushing {} inline box(es) to flow A",
|
debug!("flushing {} inline box(es) to flow A",
|
||||||
inline_fragment_accumulator.fragments.len());
|
inline_fragment_accumulator.fragments.fragments.len());
|
||||||
self.flush_inline_fragments_to_flow_or_list(
|
self.flush_inline_fragments_to_flow_or_list(
|
||||||
mem::replace(inline_fragment_accumulator,
|
mem::replace(inline_fragment_accumulator,
|
||||||
InlineFragmentsAccumulator::new()),
|
InlineFragmentsAccumulator::new()),
|
||||||
|
@ -491,7 +529,7 @@ impl<'a> FlowConstructor<'a> {
|
||||||
|
|
||||||
// Flush any inline fragments that we were gathering up.
|
// Flush any inline fragments that we were gathering up.
|
||||||
debug!("flushing {} inline box(es) to flow A",
|
debug!("flushing {} inline box(es) to flow A",
|
||||||
inline_fragment_accumulator.fragments.len());
|
inline_fragment_accumulator.fragments.fragments.len());
|
||||||
self.flush_inline_fragments_to_flow_or_list(
|
self.flush_inline_fragments_to_flow_or_list(
|
||||||
mem::replace(inline_fragment_accumulator,
|
mem::replace(inline_fragment_accumulator,
|
||||||
InlineFragmentsAccumulator::new()),
|
InlineFragmentsAccumulator::new()),
|
||||||
|
@ -526,7 +564,7 @@ impl<'a> FlowConstructor<'a> {
|
||||||
whitespace_style,
|
whitespace_style,
|
||||||
whitespace_damage,
|
whitespace_damage,
|
||||||
fragment_info);
|
fragment_info);
|
||||||
inline_fragment_accumulator.fragments.push_back(fragment);
|
inline_fragment_accumulator.fragments.fragments.push_back(fragment);
|
||||||
}
|
}
|
||||||
ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(_)) => {
|
ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(_)) => {
|
||||||
// TODO: Implement anonymous table objects for missing parents
|
// TODO: Implement anonymous table objects for missing parents
|
||||||
|
@ -538,16 +576,17 @@ impl<'a> FlowConstructor<'a> {
|
||||||
/// Constructs a block flow, beginning with the given `initial_fragments` if present and then
|
/// Constructs a block flow, beginning with the given `initial_fragments` if present and then
|
||||||
/// appending the construction results of children to the child list of the block flow. {ib}
|
/// appending the construction results of children to the child list of the block flow. {ib}
|
||||||
/// splits and absolutely-positioned descendants are handled correctly.
|
/// splits and absolutely-positioned descendants are handled correctly.
|
||||||
fn build_flow_for_block_starting_with_fragments(&mut self,
|
fn build_flow_for_block_starting_with_fragments(
|
||||||
|
&mut self,
|
||||||
mut flow: FlowRef,
|
mut flow: FlowRef,
|
||||||
node: &ThreadSafeLayoutNode,
|
node: &ThreadSafeLayoutNode,
|
||||||
mut initial_fragments: LinkedList<Fragment>)
|
initial_fragments: IntermediateInlineFragments)
|
||||||
-> ConstructionResult {
|
-> ConstructionResult {
|
||||||
// Gather up fragments for the inline flows we might need to create.
|
// Gather up fragments for the inline flows we might need to create.
|
||||||
let mut inline_fragment_accumulator = InlineFragmentsAccumulator::new();
|
let mut inline_fragment_accumulator = InlineFragmentsAccumulator::new();
|
||||||
let mut consecutive_siblings = vec!();
|
let mut consecutive_siblings = vec!();
|
||||||
|
|
||||||
inline_fragment_accumulator.fragments.append(&mut initial_fragments);
|
inline_fragment_accumulator.fragments.push_all(initial_fragments);
|
||||||
let mut first_fragment = inline_fragment_accumulator.fragments.is_empty();
|
let mut first_fragment = inline_fragment_accumulator.fragments.is_empty();
|
||||||
|
|
||||||
// List of absolute descendants, in tree order.
|
// List of absolute descendants, in tree order.
|
||||||
|
@ -582,9 +621,9 @@ impl<'a> FlowConstructor<'a> {
|
||||||
flow.finish();
|
flow.finish();
|
||||||
|
|
||||||
// Set up the absolute descendants.
|
// Set up the absolute descendants.
|
||||||
let is_positioned = flow.as_block().is_positioned();
|
let contains_positioned_fragments = flow.contains_positioned_fragments();
|
||||||
let is_absolutely_positioned = flow::base(&*flow).flags.contains(IS_ABSOLUTELY_POSITIONED);
|
let is_absolutely_positioned = flow::base(&*flow).flags.contains(IS_ABSOLUTELY_POSITIONED);
|
||||||
if is_positioned {
|
if contains_positioned_fragments {
|
||||||
// This is the containing block for all the absolute descendants.
|
// This is the containing block for all the absolute descendants.
|
||||||
flow.set_absolute_descendants(abs_descendants);
|
flow.set_absolute_descendants(abs_descendants);
|
||||||
|
|
||||||
|
@ -611,7 +650,7 @@ impl<'a> FlowConstructor<'a> {
|
||||||
/// `<textarea>`.
|
/// `<textarea>`.
|
||||||
fn build_flow_for_block_like(&mut self, flow: FlowRef, node: &ThreadSafeLayoutNode)
|
fn build_flow_for_block_like(&mut self, flow: FlowRef, node: &ThreadSafeLayoutNode)
|
||||||
-> ConstructionResult {
|
-> ConstructionResult {
|
||||||
let mut initial_fragments = LinkedList::new();
|
let mut initial_fragments = IntermediateInlineFragments::new();
|
||||||
if node.get_pseudo_element_type() != PseudoElementType::Normal ||
|
if node.get_pseudo_element_type() != PseudoElementType::Normal ||
|
||||||
node.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement(
|
node.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||||
HTMLElementTypeId::HTMLInputElement))) ||
|
HTMLElementTypeId::HTMLInputElement))) ||
|
||||||
|
@ -634,7 +673,7 @@ impl<'a> FlowConstructor<'a> {
|
||||||
|
|
||||||
/// Pushes fragments appropriate for the content of the given node onto the given list.
|
/// Pushes fragments appropriate for the content of the given node onto the given list.
|
||||||
fn create_fragments_for_node_text_content(&self,
|
fn create_fragments_for_node_text_content(&self,
|
||||||
fragments: &mut LinkedList<Fragment>,
|
fragments: &mut IntermediateInlineFragments,
|
||||||
node: &ThreadSafeLayoutNode,
|
node: &ThreadSafeLayoutNode,
|
||||||
style: &Arc<ComputedValues>) {
|
style: &Arc<ComputedValues>) {
|
||||||
for content_item in node.text_content().into_iter() {
|
for content_item in node.text_content().into_iter() {
|
||||||
|
@ -650,7 +689,8 @@ impl<'a> FlowConstructor<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let opaque_node = OpaqueNodeMethods::from_thread_safe_layout_node(node);
|
let opaque_node = OpaqueNodeMethods::from_thread_safe_layout_node(node);
|
||||||
fragments.push_back(Fragment::from_opaque_node_and_style(opaque_node,
|
fragments.fragments
|
||||||
|
.push_back(Fragment::from_opaque_node_and_style(opaque_node,
|
||||||
style.clone(),
|
style.clone(),
|
||||||
node.restyle_damage(),
|
node.restyle_damage(),
|
||||||
specific))
|
specific))
|
||||||
|
@ -672,6 +712,30 @@ impl<'a> FlowConstructor<'a> {
|
||||||
self.build_flow_for_block_like(FlowRef::new(flow), node)
|
self.build_flow_for_block_like(FlowRef::new(flow), node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bubbles up {ib} splits.
|
||||||
|
fn accumulate_inline_block_splits(&mut self,
|
||||||
|
splits: LinkedList<InlineBlockSplit>,
|
||||||
|
node: &ThreadSafeLayoutNode,
|
||||||
|
fragment_accumulator: &mut InlineFragmentsAccumulator,
|
||||||
|
opt_inline_block_splits: &mut LinkedList<InlineBlockSplit>) {
|
||||||
|
for split in splits.into_iter() {
|
||||||
|
let InlineBlockSplit {
|
||||||
|
predecessors,
|
||||||
|
flow: kid_flow
|
||||||
|
} = split;
|
||||||
|
fragment_accumulator.push_all(predecessors);
|
||||||
|
|
||||||
|
let split = InlineBlockSplit {
|
||||||
|
predecessors: mem::replace(
|
||||||
|
fragment_accumulator,
|
||||||
|
InlineFragmentsAccumulator::from_inline_node(
|
||||||
|
node)).to_intermediate_inline_fragments(),
|
||||||
|
flow: kid_flow,
|
||||||
|
};
|
||||||
|
opt_inline_block_splits.push_back(split)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Concatenates the fragments of kids, adding in our own borders/padding/margins if necessary.
|
/// Concatenates the fragments of kids, adding in our own borders/padding/margins if necessary.
|
||||||
/// Returns the `InlineFragmentsConstructionResult`, if any. There will be no
|
/// Returns the `InlineFragmentsConstructionResult`, if any. There will be no
|
||||||
/// `InlineFragmentsConstructionResult` if this node consisted entirely of ignorable
|
/// `InlineFragmentsConstructionResult` if this node consisted entirely of ignorable
|
||||||
|
@ -689,18 +753,36 @@ impl<'a> FlowConstructor<'a> {
|
||||||
}
|
}
|
||||||
match kid.swap_out_construction_result() {
|
match kid.swap_out_construction_result() {
|
||||||
ConstructionResult::None => {}
|
ConstructionResult::None => {}
|
||||||
ConstructionResult::Flow(flow, kid_abs_descendants) => {
|
ConstructionResult::Flow(mut flow, kid_abs_descendants) => {
|
||||||
|
if !flow::base(&*flow).flags.contains(IS_ABSOLUTELY_POSITIONED) {
|
||||||
// {ib} split. Flush the accumulator to our new split and make a new
|
// {ib} split. Flush the accumulator to our new split and make a new
|
||||||
// accumulator to hold any subsequent fragments we come across.
|
// accumulator to hold any subsequent fragments we come across.
|
||||||
let split = InlineBlockSplit {
|
let split = InlineBlockSplit {
|
||||||
predecessors:
|
predecessors:
|
||||||
mem::replace(
|
mem::replace(
|
||||||
&mut fragment_accumulator,
|
&mut fragment_accumulator,
|
||||||
InlineFragmentsAccumulator::from_inline_node(node)).to_dlist(),
|
InlineFragmentsAccumulator::from_inline_node(
|
||||||
|
node)).to_intermediate_inline_fragments(),
|
||||||
flow: flow,
|
flow: flow,
|
||||||
};
|
};
|
||||||
opt_inline_block_splits.push_back(split);
|
opt_inline_block_splits.push_back(split);
|
||||||
abs_descendants.push_descendants(kid_abs_descendants);
|
abs_descendants.push_descendants(kid_abs_descendants);
|
||||||
|
} else {
|
||||||
|
// Push the absolutely-positioned kid as an inline containing block.
|
||||||
|
let kid_node = flow.as_block().fragment.node;
|
||||||
|
let kid_style = flow.as_block().fragment.style.clone();
|
||||||
|
let kid_restyle_damage = flow.as_block().fragment.restyle_damage;
|
||||||
|
let fragment_info = SpecificFragmentInfo::InlineAbsolute(
|
||||||
|
InlineAbsoluteFragmentInfo::new(flow));
|
||||||
|
fragment_accumulator.push(Fragment::from_opaque_node_and_style(
|
||||||
|
kid_node,
|
||||||
|
kid_style,
|
||||||
|
kid_restyle_damage,
|
||||||
|
fragment_info));
|
||||||
|
fragment_accumulator.fragments
|
||||||
|
.absolute_descendants
|
||||||
|
.push_descendants(kid_abs_descendants);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ConstructionResult::ConstructionItem(ConstructionItem::InlineFragments(
|
ConstructionResult::ConstructionItem(ConstructionItem::InlineFragments(
|
||||||
InlineFragmentsConstructionResult {
|
InlineFragmentsConstructionResult {
|
||||||
|
@ -710,22 +792,10 @@ impl<'a> FlowConstructor<'a> {
|
||||||
})) => {
|
})) => {
|
||||||
|
|
||||||
// Bubble up {ib} splits.
|
// Bubble up {ib} splits.
|
||||||
for split in splits.into_iter() {
|
self.accumulate_inline_block_splits(splits,
|
||||||
let InlineBlockSplit {
|
node,
|
||||||
predecessors,
|
&mut fragment_accumulator,
|
||||||
flow: kid_flow
|
&mut opt_inline_block_splits);
|
||||||
} = split;
|
|
||||||
fragment_accumulator.push_all(predecessors);
|
|
||||||
|
|
||||||
let split = InlineBlockSplit {
|
|
||||||
predecessors:
|
|
||||||
mem::replace(&mut fragment_accumulator,
|
|
||||||
InlineFragmentsAccumulator::from_inline_node(node))
|
|
||||||
.to_dlist(),
|
|
||||||
flow: kid_flow,
|
|
||||||
};
|
|
||||||
opt_inline_block_splits.push_back(split)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push residual fragments.
|
// Push residual fragments.
|
||||||
fragment_accumulator.push_all(successors);
|
fragment_accumulator.push_all(successors);
|
||||||
|
@ -743,7 +813,7 @@ impl<'a> FlowConstructor<'a> {
|
||||||
whitespace_style,
|
whitespace_style,
|
||||||
whitespace_damage,
|
whitespace_damage,
|
||||||
fragment_info);
|
fragment_info);
|
||||||
fragment_accumulator.fragments.push_back(fragment)
|
fragment_accumulator.fragments.fragments.push_back(fragment)
|
||||||
}
|
}
|
||||||
ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(_)) => {
|
ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(_)) => {
|
||||||
// TODO: Implement anonymous table objects for missing parents
|
// TODO: Implement anonymous table objects for missing parents
|
||||||
|
@ -753,12 +823,12 @@ impl<'a> FlowConstructor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, make a new construction result.
|
// Finally, make a new construction result.
|
||||||
if opt_inline_block_splits.len() > 0 || fragment_accumulator.fragments.len() > 0
|
if opt_inline_block_splits.len() > 0 || !fragment_accumulator.fragments.is_empty()
|
||||||
|| abs_descendants.len() > 0 {
|
|| abs_descendants.len() > 0 {
|
||||||
let construction_item = ConstructionItem::InlineFragments(
|
let construction_item = ConstructionItem::InlineFragments(
|
||||||
InlineFragmentsConstructionResult {
|
InlineFragmentsConstructionResult {
|
||||||
splits: opt_inline_block_splits,
|
splits: opt_inline_block_splits,
|
||||||
fragments: fragment_accumulator.to_dlist(),
|
fragments: fragment_accumulator.to_intermediate_inline_fragments(),
|
||||||
abs_descendants: abs_descendants,
|
abs_descendants: abs_descendants,
|
||||||
});
|
});
|
||||||
ConstructionResult::ConstructionItem(construction_item)
|
ConstructionResult::ConstructionItem(construction_item)
|
||||||
|
@ -795,13 +865,13 @@ impl<'a> FlowConstructor<'a> {
|
||||||
// If this is generated content, then we need to initialize the accumulator with the
|
// If this is generated content, then we need to initialize the accumulator with the
|
||||||
// fragment corresponding to that content. Otherwise, just initialize with the ordinary
|
// fragment corresponding to that content. Otherwise, just initialize with the ordinary
|
||||||
// fragment that needs to be generated for this inline node.
|
// fragment that needs to be generated for this inline node.
|
||||||
let mut fragments = LinkedList::new();
|
let mut fragments = IntermediateInlineFragments::new();
|
||||||
match (node.get_pseudo_element_type(), node.type_id()) {
|
match (node.get_pseudo_element_type(), node.type_id()) {
|
||||||
(_, Some(NodeTypeId::CharacterData(CharacterDataTypeId::Text))) => {
|
(_, Some(NodeTypeId::CharacterData(CharacterDataTypeId::Text))) => {
|
||||||
self.create_fragments_for_node_text_content(&mut fragments, node, &style)
|
self.create_fragments_for_node_text_content(&mut fragments, node, &style)
|
||||||
}
|
}
|
||||||
(PseudoElementType::Normal, _) => {
|
(PseudoElementType::Normal, _) => {
|
||||||
fragments.push_back(self.build_fragment_for_block(node));
|
fragments.fragments.push_back(self.build_fragment_for_block(node));
|
||||||
}
|
}
|
||||||
(_, _) => self.create_fragments_for_node_text_content(&mut fragments, node, &style),
|
(_, _) => self.create_fragments_for_node_text_content(&mut fragments, node, &style),
|
||||||
}
|
}
|
||||||
|
@ -827,13 +897,13 @@ impl<'a> FlowConstructor<'a> {
|
||||||
block_flow));
|
block_flow));
|
||||||
let fragment = Fragment::new(node, fragment_info);
|
let fragment = Fragment::new(node, fragment_info);
|
||||||
|
|
||||||
let mut fragment_accumulator = InlineFragmentsAccumulator::new();
|
let mut fragment_accumulator = InlineFragmentsAccumulator::from_inline_node(node);
|
||||||
fragment_accumulator.fragments.push_back(fragment);
|
fragment_accumulator.fragments.fragments.push_back(fragment);
|
||||||
|
|
||||||
let construction_item =
|
let construction_item =
|
||||||
ConstructionItem::InlineFragments(InlineFragmentsConstructionResult {
|
ConstructionItem::InlineFragments(InlineFragmentsConstructionResult {
|
||||||
splits: LinkedList::new(),
|
splits: LinkedList::new(),
|
||||||
fragments: fragment_accumulator.to_dlist(),
|
fragments: fragment_accumulator.to_intermediate_inline_fragments(),
|
||||||
abs_descendants: abs_descendants,
|
abs_descendants: abs_descendants,
|
||||||
});
|
});
|
||||||
ConstructionResult::ConstructionItem(construction_item)
|
ConstructionResult::ConstructionItem(construction_item)
|
||||||
|
@ -854,12 +924,12 @@ impl<'a> FlowConstructor<'a> {
|
||||||
let fragment = Fragment::new(node, fragment_info);
|
let fragment = Fragment::new(node, fragment_info);
|
||||||
|
|
||||||
let mut fragment_accumulator = InlineFragmentsAccumulator::from_inline_node(node);
|
let mut fragment_accumulator = InlineFragmentsAccumulator::from_inline_node(node);
|
||||||
fragment_accumulator.fragments.push_back(fragment);
|
fragment_accumulator.fragments.fragments.push_back(fragment);
|
||||||
|
|
||||||
let construction_item =
|
let construction_item =
|
||||||
ConstructionItem::InlineFragments(InlineFragmentsConstructionResult {
|
ConstructionItem::InlineFragments(InlineFragmentsConstructionResult {
|
||||||
splits: LinkedList::new(),
|
splits: LinkedList::new(),
|
||||||
fragments: fragment_accumulator.to_dlist(),
|
fragments: fragment_accumulator.to_intermediate_inline_fragments(),
|
||||||
abs_descendants: abs_descendants,
|
abs_descendants: abs_descendants,
|
||||||
});
|
});
|
||||||
ConstructionResult::ConstructionItem(construction_item)
|
ConstructionResult::ConstructionItem(construction_item)
|
||||||
|
@ -973,11 +1043,11 @@ impl<'a> FlowConstructor<'a> {
|
||||||
|
|
||||||
// The flow is done.
|
// The flow is done.
|
||||||
wrapper_flow.finish();
|
wrapper_flow.finish();
|
||||||
let is_positioned = wrapper_flow.as_block().is_positioned();
|
let contains_positioned_fragments = wrapper_flow.contains_positioned_fragments();
|
||||||
let is_fixed_positioned = wrapper_flow.as_block().is_fixed();
|
let is_fixed_positioned = wrapper_flow.as_block().is_fixed();
|
||||||
let is_absolutely_positioned =
|
let is_absolutely_positioned =
|
||||||
flow::base(&*wrapper_flow).flags.contains(IS_ABSOLUTELY_POSITIONED);
|
flow::base(&*wrapper_flow).flags.contains(IS_ABSOLUTELY_POSITIONED);
|
||||||
if is_positioned {
|
if contains_positioned_fragments {
|
||||||
// This is the containing block for all the absolute descendants.
|
// This is the containing block for all the absolute descendants.
|
||||||
wrapper_flow.set_absolute_descendants(abs_descendants);
|
wrapper_flow.set_absolute_descendants(abs_descendants);
|
||||||
|
|
||||||
|
@ -1085,7 +1155,7 @@ impl<'a> FlowConstructor<'a> {
|
||||||
// we adopt Gecko's behavior rather than WebKit's when the marker causes an {ib} split,
|
// we adopt Gecko's behavior rather than WebKit's when the marker causes an {ib} split,
|
||||||
// which has caused some malaise (Bugzilla #36854) but CSS 2.1 § 12.5.1 lets me do it, so
|
// which has caused some malaise (Bugzilla #36854) but CSS 2.1 § 12.5.1 lets me do it, so
|
||||||
// there.
|
// there.
|
||||||
let mut initial_fragments = LinkedList::new();
|
let mut initial_fragments = IntermediateInlineFragments::new();
|
||||||
let main_fragment = self.build_fragment_for_block(node);
|
let main_fragment = self.build_fragment_for_block(node);
|
||||||
let flow = match node.style().get_list().list_style_position {
|
let flow = match node.style().get_list().list_style_position {
|
||||||
list_style_position::T::outside => {
|
list_style_position::T::outside => {
|
||||||
|
@ -1096,7 +1166,7 @@ impl<'a> FlowConstructor<'a> {
|
||||||
}
|
}
|
||||||
list_style_position::T::inside => {
|
list_style_position::T::inside => {
|
||||||
if let Some(marker_fragment) = marker_fragment {
|
if let Some(marker_fragment) = marker_fragment {
|
||||||
initial_fragments.push_back(marker_fragment)
|
initial_fragments.fragments.push_back(marker_fragment)
|
||||||
}
|
}
|
||||||
box ListItemFlow::from_node_fragments_and_flotation(node,
|
box ListItemFlow::from_node_fragments_and_flotation(node,
|
||||||
main_fragment,
|
main_fragment,
|
||||||
|
@ -1194,7 +1264,9 @@ impl<'a> FlowConstructor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let damage = node.restyle_damage();
|
let damage = node.restyle_damage();
|
||||||
for fragment in inline_fragments_construction_result.fragments.iter_mut() {
|
for fragment in inline_fragments_construction_result.fragments
|
||||||
|
.fragments
|
||||||
|
.iter_mut() {
|
||||||
match fragment.specific {
|
match fragment.specific {
|
||||||
SpecificFragmentInfo::InlineBlock(ref mut inline_block_fragment) => {
|
SpecificFragmentInfo::InlineBlock(ref mut inline_block_fragment) => {
|
||||||
flow::mut_base(&mut *inline_block_fragment.flow_ref).restyle_damage
|
flow::mut_base(&mut *inline_block_fragment.flow_ref).restyle_damage
|
||||||
|
|
|
@ -81,7 +81,7 @@ impl LayoutDataWrapper {
|
||||||
ConstructionResult::ConstructionItem(ref construction_item) => {
|
ConstructionResult::ConstructionItem(ref construction_item) => {
|
||||||
match construction_item {
|
match construction_item {
|
||||||
&ConstructionItem::InlineFragments(ref inline_fragments) => {
|
&ConstructionItem::InlineFragments(ref inline_fragments) => {
|
||||||
for fragment in inline_fragments.fragments.iter() {
|
for fragment in inline_fragments.fragments.fragments.iter() {
|
||||||
fragment.remove_compositor_layers(constellation_chan.clone());
|
fragment.remove_compositor_layers(constellation_chan.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1001,7 +1001,8 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
SpecificFragmentInfo::TableRow |
|
SpecificFragmentInfo::TableRow |
|
||||||
SpecificFragmentInfo::TableWrapper |
|
SpecificFragmentInfo::TableWrapper |
|
||||||
SpecificFragmentInfo::InlineBlock(_) |
|
SpecificFragmentInfo::InlineBlock(_) |
|
||||||
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {
|
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(_) => {
|
||||||
if opts::get().show_debug_fragment_borders {
|
if opts::get().show_debug_fragment_borders {
|
||||||
self.build_debug_borders_around_fragment(display_list,
|
self.build_debug_borders_around_fragment(display_list,
|
||||||
stacking_relative_border_box,
|
stacking_relative_border_box,
|
||||||
|
@ -1364,8 +1365,10 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
|
||||||
background_border_level);
|
background_border_level);
|
||||||
|
|
||||||
self.base.display_list_building_result = if self.fragment.establishes_stacking_context() {
|
self.base.display_list_building_result = if self.fragment.establishes_stacking_context() {
|
||||||
DisplayListBuildingResult::StackingContext(
|
DisplayListBuildingResult::StackingContext(self.fragment.create_stacking_context(
|
||||||
self.fragment.create_stacking_context(&self.base, display_list, None))
|
&self.base,
|
||||||
|
display_list,
|
||||||
|
None))
|
||||||
} else {
|
} else {
|
||||||
match self.fragment.style.get_box().position {
|
match self.fragment.style.get_box().position {
|
||||||
position::T::static_ => {}
|
position::T::static_ => {}
|
||||||
|
@ -1391,8 +1394,10 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
|
||||||
!self.base.flags.contains(NEEDS_LAYER) {
|
!self.base.flags.contains(NEEDS_LAYER) {
|
||||||
// We didn't need a layer.
|
// We didn't need a layer.
|
||||||
self.base.display_list_building_result =
|
self.base.display_list_building_result =
|
||||||
DisplayListBuildingResult::StackingContext(
|
DisplayListBuildingResult::StackingContext(self.fragment.create_stacking_context(
|
||||||
self.fragment.create_stacking_context(&self.base, display_list, None));
|
&self.base,
|
||||||
|
display_list,
|
||||||
|
None));
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1492,6 +1497,11 @@ impl InlineFlowDisplayListBuilding for InlineFlow {
|
||||||
flow::mut_base(block_flow).display_list_building_result
|
flow::mut_base(block_flow).display_list_building_result
|
||||||
.add_to(&mut *display_list)
|
.add_to(&mut *display_list)
|
||||||
}
|
}
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(ref mut block_flow) => {
|
||||||
|
let block_flow = &mut *block_flow.flow_ref;
|
||||||
|
flow::mut_base(block_flow).display_list_building_result
|
||||||
|
.add_to(&mut *display_list)
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -324,19 +324,15 @@ pub trait Flow: fmt::Debug + Sync {
|
||||||
self.positioning() == position::T::fixed
|
self.positioning() == position::T::fixed
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_positioned(&self) -> bool {
|
fn contains_positioned_fragments(&self) -> bool {
|
||||||
self.is_relatively_positioned() || base(self).flags.contains(IS_ABSOLUTELY_POSITIONED)
|
self.contains_relatively_positioned_fragments() ||
|
||||||
|
base(self).flags.contains(IS_ABSOLUTELY_POSITIONED)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_relatively_positioned(&self) -> bool {
|
fn contains_relatively_positioned_fragments(&self) -> bool {
|
||||||
self.positioning() == position::T::relative
|
self.positioning() == position::T::relative
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if this is the root of an absolute flow tree.
|
|
||||||
fn is_root_of_absolute_flow_tree(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if this is an absolute containing block.
|
/// Returns true if this is an absolute containing block.
|
||||||
fn is_absolute_containing_block(&self) -> bool {
|
fn is_absolute_containing_block(&self) -> bool {
|
||||||
false
|
false
|
||||||
|
@ -350,14 +346,12 @@ pub trait Flow: fmt::Debug + Sync {
|
||||||
/// this is only used for absolutely-positioned inline-blocks.
|
/// this is only used for absolutely-positioned inline-blocks.
|
||||||
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au);
|
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-
|
/// Return the size of the containing block generated by this flow for the absolutely-
|
||||||
/// positioned descendants. For block flows, this is the padding box.
|
/// positioned descendant referenced by `for_flow`. For block flows, this is the padding box.
|
||||||
///
|
///
|
||||||
/// NB: Do not change this `&self` to `&mut self` under any circumstances! It has security
|
/// NB: Do not change this `&self` to `&mut self` under any circumstances! It has security
|
||||||
/// implications because this can be called on parents concurrently from descendants!
|
/// implications because this can be called on parents concurrently from descendants!
|
||||||
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
|
fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au>;
|
||||||
panic!("generated_containing_block_rect not yet implemented for this flow")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a layer ID for the given fragment.
|
/// Returns a layer ID for the given fragment.
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
|
@ -440,6 +434,9 @@ pub trait ImmutableFlowUtils {
|
||||||
/// Generates missing child flow of this flow.
|
/// Generates missing child flow of this flow.
|
||||||
fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> FlowRef;
|
fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> FlowRef;
|
||||||
|
|
||||||
|
/// Returns true if this flow contains fragments that are roots of an absolute flow tree.
|
||||||
|
fn contains_roots_of_absolute_flow_tree(&self) -> bool;
|
||||||
|
|
||||||
/// Returns true if this flow has no children.
|
/// Returns true if this flow has no children.
|
||||||
fn is_leaf(self) -> bool;
|
fn is_leaf(self) -> bool;
|
||||||
|
|
||||||
|
@ -471,6 +468,21 @@ pub trait MutableFlowUtils {
|
||||||
/// Traverses the tree in postorder.
|
/// Traverses the tree in postorder.
|
||||||
fn traverse_postorder<T:PostorderFlowTraversal>(self, traversal: &T);
|
fn traverse_postorder<T:PostorderFlowTraversal>(self, traversal: &T);
|
||||||
|
|
||||||
|
/// Traverse the Absolute flow tree in preorder.
|
||||||
|
///
|
||||||
|
/// Traverse all your direct absolute descendants, who will then traverse
|
||||||
|
/// their direct absolute descendants.
|
||||||
|
///
|
||||||
|
/// Return true if the traversal is to continue or false to stop.
|
||||||
|
fn traverse_preorder_absolute_flows<T>(&mut self, traversal: &mut T)
|
||||||
|
where T: PreorderFlowTraversal;
|
||||||
|
|
||||||
|
/// Traverse the Absolute flow tree in postorder.
|
||||||
|
///
|
||||||
|
/// Return true if the traversal is to continue or false to stop.
|
||||||
|
fn traverse_postorder_absolute_flows<T>(&mut self, traversal: &mut T)
|
||||||
|
where T: PostorderFlowTraversal;
|
||||||
|
|
||||||
// Mutators
|
// Mutators
|
||||||
|
|
||||||
/// Calls `repair_style` and `bubble_inline_sizes`. You should use this method instead of
|
/// Calls `repair_style` and `bubble_inline_sizes`. You should use this method instead of
|
||||||
|
@ -1183,6 +1195,11 @@ impl<'a> ImmutableFlowUtils for &'a (Flow + 'a) {
|
||||||
FlowRef::new(flow)
|
FlowRef::new(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this flow contains fragments that are roots of an absolute flow tree.
|
||||||
|
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
|
||||||
|
self.contains_relatively_positioned_fragments() || self.is_root()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if this flow has no children.
|
/// Returns true if this flow has no children.
|
||||||
fn is_leaf(self) -> bool {
|
fn is_leaf(self) -> bool {
|
||||||
base(self).children.len() == 0
|
base(self).children.len() == 0
|
||||||
|
@ -1276,6 +1293,34 @@ impl<'a> MutableFlowUtils for &'a mut (Flow + 'a) {
|
||||||
self.repair_style(style);
|
self.repair_style(style);
|
||||||
self.bubble_inline_sizes();
|
self.bubble_inline_sizes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Traverse the Absolute flow tree in preorder.
|
||||||
|
///
|
||||||
|
/// Traverse all your direct absolute descendants, who will then traverse
|
||||||
|
/// their direct absolute descendants.
|
||||||
|
///
|
||||||
|
/// Return true if the traversal is to continue or false to stop.
|
||||||
|
fn traverse_preorder_absolute_flows<T>(&mut self, traversal: &mut T)
|
||||||
|
where T: PreorderFlowTraversal {
|
||||||
|
traversal.process(*self);
|
||||||
|
|
||||||
|
let descendant_offset_iter = mut_base(*self).abs_descendants.iter();
|
||||||
|
for ref mut descendant_link in descendant_offset_iter {
|
||||||
|
descendant_link.traverse_preorder_absolute_flows(traversal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverse the Absolute flow tree in postorder.
|
||||||
|
///
|
||||||
|
/// Return true if the traversal is to continue or false to stop.
|
||||||
|
fn traverse_postorder_absolute_flows<T>(&mut self, traversal: &mut T)
|
||||||
|
where T: PostorderFlowTraversal {
|
||||||
|
for mut descendant_link in mut_base(*self).abs_descendants.iter() {
|
||||||
|
descendant_link.traverse_postorder_absolute_flows(traversal);
|
||||||
|
}
|
||||||
|
|
||||||
|
traversal.process(*self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MutableOwnedFlowUtils for FlowRef {
|
impl MutableOwnedFlowUtils for FlowRef {
|
||||||
|
@ -1288,13 +1333,11 @@ impl MutableOwnedFlowUtils for FlowRef {
|
||||||
/// construction is allowed to possess.
|
/// construction is allowed to possess.
|
||||||
fn set_absolute_descendants(&mut self, abs_descendants: AbsDescendants) {
|
fn set_absolute_descendants(&mut self, abs_descendants: AbsDescendants) {
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
|
let base = mut_base(&mut **self);
|
||||||
let block = self.as_block();
|
base.abs_descendants = abs_descendants;
|
||||||
block.base.abs_descendants = abs_descendants;
|
for descendant_link in base.abs_descendants.iter() {
|
||||||
|
let descendant_base = mut_base(descendant_link);
|
||||||
for descendant_link in block.base.abs_descendants.iter() {
|
descendant_base.absolute_cb.set(this.clone());
|
||||||
let base = mut_base(descendant_link);
|
|
||||||
base.absolute_cb.set(this.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1329,10 +1372,33 @@ impl ContainingBlockLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn generated_containing_block_rect(&mut self) -> LogicalRect<Au> {
|
pub fn generated_containing_block_size(&mut self, for_flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
match self.link {
|
match self.link {
|
||||||
None => panic!("haven't done it"),
|
None => {
|
||||||
Some(ref mut link) => link.generated_containing_block_rect(),
|
panic!("Link to containing block not established; perhaps you forgot to call \
|
||||||
|
`set_absolute_descendants`?")
|
||||||
|
}
|
||||||
|
Some(ref mut link) => link.generated_containing_block_size(for_flow),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A wrapper for the pointer address of a flow. These pointer addresses may only be compared for
|
||||||
|
/// equality with other such pointer addresses, never dereferenced.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct OpaqueFlow(pub usize);
|
||||||
|
|
||||||
|
impl OpaqueFlow {
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
pub fn from_flow(flow: &Flow) -> OpaqueFlow {
|
||||||
|
unsafe {
|
||||||
|
let object = mem::transmute::<&Flow,raw::TraitObject>(flow);
|
||||||
|
OpaqueFlow(object.data as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_base_flow(base_flow: &BaseFlow) -> OpaqueFlow {
|
||||||
|
OpaqueFlow(base_flow as *const BaseFlow as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,11 @@ pub enum SpecificFragmentInfo {
|
||||||
InlineAbsoluteHypothetical(InlineAbsoluteHypotheticalFragmentInfo),
|
InlineAbsoluteHypothetical(InlineAbsoluteHypotheticalFragmentInfo),
|
||||||
|
|
||||||
InlineBlock(InlineBlockFragmentInfo),
|
InlineBlock(InlineBlockFragmentInfo),
|
||||||
|
|
||||||
|
/// An inline fragment that establishes an absolute containing block for its descendants (i.e.
|
||||||
|
/// a positioned inline fragment).
|
||||||
|
InlineAbsolute(InlineAbsoluteFragmentInfo),
|
||||||
|
|
||||||
ScannedText(Box<ScannedTextFragmentInfo>),
|
ScannedText(Box<ScannedTextFragmentInfo>),
|
||||||
Table,
|
Table,
|
||||||
TableCell,
|
TableCell,
|
||||||
|
@ -175,6 +180,7 @@ impl SpecificFragmentInfo {
|
||||||
SpecificFragmentInfo::UnscannedText(_) |
|
SpecificFragmentInfo::UnscannedText(_) |
|
||||||
SpecificFragmentInfo::Generic => return RestyleDamage::empty(),
|
SpecificFragmentInfo::Generic => return RestyleDamage::empty(),
|
||||||
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => &info.flow_ref,
|
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => &info.flow_ref,
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(ref info) => &info.flow_ref,
|
||||||
SpecificFragmentInfo::InlineBlock(ref info) => &info.flow_ref,
|
SpecificFragmentInfo::InlineBlock(ref info) => &info.flow_ref,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -188,6 +194,7 @@ impl SpecificFragmentInfo {
|
||||||
SpecificFragmentInfo::GeneratedContent(_) => "SpecificFragmentInfo::GeneratedContent",
|
SpecificFragmentInfo::GeneratedContent(_) => "SpecificFragmentInfo::GeneratedContent",
|
||||||
SpecificFragmentInfo::Iframe(_) => "SpecificFragmentInfo::Iframe",
|
SpecificFragmentInfo::Iframe(_) => "SpecificFragmentInfo::Iframe",
|
||||||
SpecificFragmentInfo::Image(_) => "SpecificFragmentInfo::Image",
|
SpecificFragmentInfo::Image(_) => "SpecificFragmentInfo::Image",
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(_) => "SpecificFragmentInfo::InlineAbsolute",
|
||||||
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {
|
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {
|
||||||
"SpecificFragmentInfo::InlineAbsoluteHypothetical"
|
"SpecificFragmentInfo::InlineAbsoluteHypothetical"
|
||||||
}
|
}
|
||||||
|
@ -260,6 +267,24 @@ impl InlineBlockFragmentInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An inline fragment that establishes an absolute containing block for its descendants (i.e.
|
||||||
|
/// a positioned inline fragment).
|
||||||
|
///
|
||||||
|
/// FIXME(pcwalton): Stop leaking this `FlowRef` to layout; that is not memory safe because layout
|
||||||
|
/// can clone it.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct InlineAbsoluteFragmentInfo {
|
||||||
|
pub flow_ref: FlowRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlineAbsoluteFragmentInfo {
|
||||||
|
pub fn new(flow_ref: FlowRef) -> InlineAbsoluteFragmentInfo {
|
||||||
|
InlineAbsoluteFragmentInfo {
|
||||||
|
flow_ref: flow_ref,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CanvasFragmentInfo {
|
pub struct CanvasFragmentInfo {
|
||||||
pub replaced_image_fragment_info: ReplacedImageFragmentInfo,
|
pub replaced_image_fragment_info: ReplacedImageFragmentInfo,
|
||||||
|
@ -868,7 +893,8 @@ impl Fragment {
|
||||||
SpecificFragmentInfo::Generic |
|
SpecificFragmentInfo::Generic |
|
||||||
SpecificFragmentInfo::GeneratedContent(_) |
|
SpecificFragmentInfo::GeneratedContent(_) |
|
||||||
SpecificFragmentInfo::Iframe(_) |
|
SpecificFragmentInfo::Iframe(_) |
|
||||||
SpecificFragmentInfo::Image(_) => {
|
SpecificFragmentInfo::Image(_) |
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(_) => {
|
||||||
QuantitiesIncludedInIntrinsicInlineSizes::all()
|
QuantitiesIncludedInIntrinsicInlineSizes::all()
|
||||||
}
|
}
|
||||||
SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell => {
|
SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell => {
|
||||||
|
@ -1247,6 +1273,10 @@ impl Fragment {
|
||||||
let block_flow = info.flow_ref.as_block();
|
let block_flow = info.flow_ref.as_block();
|
||||||
result.union_block(&block_flow.base.intrinsic_inline_sizes)
|
result.union_block(&block_flow.base.intrinsic_inline_sizes)
|
||||||
}
|
}
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
|
||||||
|
let block_flow = info.flow_ref.as_block();
|
||||||
|
result.union_block(&block_flow.base.intrinsic_inline_sizes)
|
||||||
|
}
|
||||||
SpecificFragmentInfo::Image(ref mut image_fragment_info) => {
|
SpecificFragmentInfo::Image(ref mut image_fragment_info) => {
|
||||||
let image_inline_size = match image_fragment_info.replaced_image_fragment_info
|
let image_inline_size = match image_fragment_info.replaced_image_fragment_info
|
||||||
.dom_inline_size {
|
.dom_inline_size {
|
||||||
|
@ -1318,7 +1348,8 @@ impl Fragment {
|
||||||
SpecificFragmentInfo::TableRow |
|
SpecificFragmentInfo::TableRow |
|
||||||
SpecificFragmentInfo::TableWrapper |
|
SpecificFragmentInfo::TableWrapper |
|
||||||
SpecificFragmentInfo::InlineBlock(_) |
|
SpecificFragmentInfo::InlineBlock(_) |
|
||||||
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => Au(0),
|
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(_) => Au(0),
|
||||||
SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => {
|
SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => {
|
||||||
canvas_fragment_info.replaced_image_fragment_info.computed_inline_size()
|
canvas_fragment_info.replaced_image_fragment_info.computed_inline_size()
|
||||||
}
|
}
|
||||||
|
@ -1608,6 +1639,7 @@ impl Fragment {
|
||||||
SpecificFragmentInfo::Iframe(_) |
|
SpecificFragmentInfo::Iframe(_) |
|
||||||
SpecificFragmentInfo::InlineBlock(_) |
|
SpecificFragmentInfo::InlineBlock(_) |
|
||||||
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
|
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(_) |
|
||||||
SpecificFragmentInfo::ScannedText(_) => {}
|
SpecificFragmentInfo::ScannedText(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1631,6 +1663,14 @@ impl Fragment {
|
||||||
block_flow.base.block_container_inline_size = self.border_box.size.inline;
|
block_flow.base.block_container_inline_size = self.border_box.size.inline;
|
||||||
block_flow.base.block_container_writing_mode = self.style.writing_mode;
|
block_flow.base.block_container_writing_mode = self.style.writing_mode;
|
||||||
}
|
}
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
|
||||||
|
let block_flow = info.flow_ref.as_block();
|
||||||
|
self.border_box.size.inline =
|
||||||
|
max(block_flow.base.intrinsic_inline_sizes.minimum_inline_size,
|
||||||
|
block_flow.base.intrinsic_inline_sizes.preferred_inline_size);
|
||||||
|
block_flow.base.block_container_inline_size = self.border_box.size.inline;
|
||||||
|
block_flow.base.block_container_writing_mode = self.style.writing_mode;
|
||||||
|
}
|
||||||
SpecificFragmentInfo::ScannedText(ref info) => {
|
SpecificFragmentInfo::ScannedText(ref info) => {
|
||||||
// Scanned text fragments will have already had their content inline-sizes assigned
|
// Scanned text fragments will have already had their content inline-sizes assigned
|
||||||
// by this point.
|
// by this point.
|
||||||
|
@ -1690,6 +1730,7 @@ impl Fragment {
|
||||||
SpecificFragmentInfo::Image(_) |
|
SpecificFragmentInfo::Image(_) |
|
||||||
SpecificFragmentInfo::InlineBlock(_) |
|
SpecificFragmentInfo::InlineBlock(_) |
|
||||||
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
|
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(_) |
|
||||||
SpecificFragmentInfo::ScannedText(_) => {}
|
SpecificFragmentInfo::ScannedText(_) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1735,6 +1776,12 @@ impl Fragment {
|
||||||
let block_flow = info.flow_ref.as_block();
|
let block_flow = info.flow_ref.as_block();
|
||||||
self.border_box.size.block = block_flow.base.position.size.block;
|
self.border_box.size.block = block_flow.base.position.size.block;
|
||||||
}
|
}
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
|
||||||
|
// Not the primary fragment, so we do not take the noncontent size into account.
|
||||||
|
let block_flow = info.flow_ref.as_block();
|
||||||
|
self.border_box.size.block = block_flow.base.position.size.block +
|
||||||
|
block_flow.fragment.margin.block_start_end()
|
||||||
|
}
|
||||||
SpecificFragmentInfo::Iframe(_) => {
|
SpecificFragmentInfo::Iframe(_) => {
|
||||||
self.border_box.size.block = IframeFragmentInfo::calculate_replaced_block_size(
|
self.border_box.size.block = IframeFragmentInfo::calculate_replaced_block_size(
|
||||||
style, containing_block_block_size) +
|
style, containing_block_block_size) +
|
||||||
|
@ -1774,7 +1821,8 @@ impl Fragment {
|
||||||
block_flow.fragment.margin.block_start,
|
block_flow.fragment.margin.block_start,
|
||||||
block_flow.fragment.margin.block_end)
|
block_flow.fragment.margin.block_end)
|
||||||
}
|
}
|
||||||
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {
|
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(_) => {
|
||||||
// Hypothetical boxes take up no space.
|
// Hypothetical boxes take up no space.
|
||||||
InlineMetrics {
|
InlineMetrics {
|
||||||
block_size_above_baseline: Au(0),
|
block_size_above_baseline: Au(0),
|
||||||
|
@ -1829,6 +1877,7 @@ impl Fragment {
|
||||||
match self.specific {
|
match self.specific {
|
||||||
SpecificFragmentInfo::InlineBlock(_) |
|
SpecificFragmentInfo::InlineBlock(_) |
|
||||||
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
|
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(_) |
|
||||||
SpecificFragmentInfo::TableWrapper => false,
|
SpecificFragmentInfo::TableWrapper => false,
|
||||||
SpecificFragmentInfo::Canvas(_) |
|
SpecificFragmentInfo::Canvas(_) |
|
||||||
SpecificFragmentInfo::Generic |
|
SpecificFragmentInfo::Generic |
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
|
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
|
use block::{AbsoluteAssignBSizesTraversal, AbsoluteStoreOverflowTraversal};
|
||||||
use css::node_style::StyledNode;
|
use css::node_style::StyledNode;
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use display_list_builder::{FragmentDisplayListBuilding, InlineFlowDisplayListBuilding};
|
use display_list_builder::{FragmentDisplayListBuilding, InlineFlowDisplayListBuilding};
|
||||||
use floats::{FloatKind, Floats, PlacementInfo};
|
use floats::{FloatKind, Floats, PlacementInfo};
|
||||||
use flow::{self, BaseFlow, FlowClass, Flow, ForceNonfloatedFlag, IS_ABSOLUTELY_POSITIONED};
|
use flow::{self, BaseFlow, FlowClass, Flow, ForceNonfloatedFlag, IS_ABSOLUTELY_POSITIONED};
|
||||||
|
use flow::{MutableFlowUtils, OpaqueFlow};
|
||||||
use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
|
use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
|
||||||
use incremental::{REFLOW, REFLOW_OUT_OF_FLOW, RESOLVE_GENERATED_CONTENT};
|
use incremental::{REFLOW, REFLOW_OUT_OF_FLOW, RESOLVE_GENERATED_CONTENT};
|
||||||
use layout_debug;
|
use layout_debug;
|
||||||
|
@ -16,7 +18,7 @@ use model::IntrinsicISizesContribution;
|
||||||
use text;
|
use text;
|
||||||
|
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
use geom::{Point2D, Rect};
|
use geom::{Point2D, Rect, Size2D};
|
||||||
use gfx::display_list::OpaqueNode;
|
use gfx::display_list::OpaqueNode;
|
||||||
use gfx::font::FontMetrics;
|
use gfx::font::FontMetrics;
|
||||||
use gfx::font_context::FontContext;
|
use gfx::font_context::FontContext;
|
||||||
|
@ -27,10 +29,10 @@ use std::fmt;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::u16;
|
use std::u16;
|
||||||
use style::computed_values::{display, overflow_x, text_align, text_justify, text_overflow};
|
use style::computed_values::{display, overflow_x, position, text_align, text_justify};
|
||||||
use style::computed_values::{vertical_align, white_space};
|
use style::computed_values::{text_overflow, vertical_align, white_space};
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use util::geometry::{Au, MAX_AU, ZERO_RECT};
|
use util::geometry::{Au, MAX_AU, ZERO_POINT, ZERO_RECT};
|
||||||
use util::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
|
use util::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
|
||||||
use util::range::{Range, RangeIndex};
|
use util::range::{Range, RangeIndex};
|
||||||
use util;
|
use util;
|
||||||
|
@ -1121,6 +1123,45 @@ impl InlineFlow {
|
||||||
|
|
||||||
self.base.restyle_damage = damage;
|
self.base.restyle_damage = damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn containing_block_range_for_flow_surrounding_fragment_at_index(&self,
|
||||||
|
fragment_index: FragmentIndex)
|
||||||
|
-> Range<FragmentIndex> {
|
||||||
|
let mut start_index = fragment_index;
|
||||||
|
while start_index > FragmentIndex(0) &&
|
||||||
|
self.fragments
|
||||||
|
.fragments[(start_index - FragmentIndex(1)).get() as usize]
|
||||||
|
.style
|
||||||
|
.get_box()
|
||||||
|
.position == position::T::static_ {
|
||||||
|
start_index = start_index - FragmentIndex(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut end_index = fragment_index + FragmentIndex(1);
|
||||||
|
while end_index < FragmentIndex(self.fragments.fragments.len() as isize) &&
|
||||||
|
self.fragments
|
||||||
|
.fragments[end_index.get() as usize]
|
||||||
|
.style
|
||||||
|
.get_box()
|
||||||
|
.position == position::T::static_ {
|
||||||
|
end_index = end_index + FragmentIndex(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
Range::new(start_index, end_index - start_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn containing_block_range_for_flow(&self, opaque_flow: OpaqueFlow) -> Range<FragmentIndex> {
|
||||||
|
let index = FragmentIndex(self.fragments.fragments.iter().position(|fragment| {
|
||||||
|
match fragment.specific {
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(ref inline_absolute) => {
|
||||||
|
OpaqueFlow::from_flow(&*inline_absolute.flow_ref) == opaque_flow
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}).expect("containing_block_range_for_flow(): couldn't find inline absolute fragment!")
|
||||||
|
as isize);
|
||||||
|
self.containing_block_range_for_flow_surrounding_fragment_at_index(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Flow for InlineFlow {
|
impl Flow for InlineFlow {
|
||||||
|
@ -1398,6 +1439,19 @@ impl Flow for InlineFlow {
|
||||||
kid.assign_block_size_for_inorder_child_if_necessary(layout_context, thread_id);
|
kid.assign_block_size_for_inorder_child_if_necessary(layout_context, thread_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.contains_positioned_fragments() {
|
||||||
|
// Assign block-sizes for all flows in this absolute flow tree.
|
||||||
|
// This is preorder because the block-size of an absolute flow may depend on
|
||||||
|
// the block-size of its containing block, which may also be an absolute flow.
|
||||||
|
(&mut *self as &mut Flow).traverse_preorder_absolute_flows(
|
||||||
|
&mut AbsoluteAssignBSizesTraversal(layout_context));
|
||||||
|
// Store overflow for all absolute descendants.
|
||||||
|
(&mut *self as &mut Flow).traverse_postorder_absolute_flows(
|
||||||
|
&mut AbsoluteStoreOverflowTraversal {
|
||||||
|
layout_context: layout_context,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
self.base.position.size.block = match self.lines.last() {
|
self.base.position.size.block = match self.lines.last() {
|
||||||
Some(ref last_line) => last_line.bounds.start.b + last_line.bounds.size.block,
|
Some(ref last_line) => last_line.bounds.start.b + last_line.bounds.size.block,
|
||||||
None => Au(0),
|
None => Au(0),
|
||||||
|
@ -1412,6 +1466,26 @@ impl Flow for InlineFlow {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_absolute_position(&mut self) {
|
fn compute_absolute_position(&mut self) {
|
||||||
|
// First, gather up the positions of all the containing blocks (if any).
|
||||||
|
let mut containing_block_positions = Vec::new();
|
||||||
|
let container_size = Size2D(self.base.block_container_inline_size, Au(0));
|
||||||
|
for (fragment_index, fragment) in self.fragments.fragments.iter().enumerate() {
|
||||||
|
if let SpecificFragmentInfo::InlineAbsolute(_) = fragment.specific {
|
||||||
|
let containing_block_range =
|
||||||
|
self.containing_block_range_for_flow_surrounding_fragment_at_index(
|
||||||
|
FragmentIndex(fragment_index as isize));
|
||||||
|
let first_fragment_index = containing_block_range.begin().get() as usize;
|
||||||
|
debug_assert!(first_fragment_index < self.fragments.fragments.len());
|
||||||
|
let first_fragment = &self.fragments.fragments[first_fragment_index];
|
||||||
|
let padding_box_origin = (first_fragment.border_box -
|
||||||
|
first_fragment.style.logical_border_width()).start;
|
||||||
|
containing_block_positions.push(
|
||||||
|
padding_box_origin.to_physical(self.base.writing_mode, container_size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then compute the positions of all of our fragments.
|
||||||
|
let mut containing_block_positions = containing_block_positions.iter();
|
||||||
for fragment in self.fragments.fragments.iter_mut() {
|
for fragment in self.fragments.fragments.iter_mut() {
|
||||||
let stacking_relative_border_box =
|
let stacking_relative_border_box =
|
||||||
fragment.stacking_relative_border_box(&self.base.stacking_relative_position,
|
fragment.stacking_relative_border_box(&self.base.stacking_relative_position,
|
||||||
|
@ -1440,6 +1514,22 @@ impl Flow for InlineFlow {
|
||||||
stacking_relative_border_box.origin
|
stacking_relative_border_box.origin
|
||||||
|
|
||||||
}
|
}
|
||||||
|
SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
|
||||||
|
flow::mut_base(&mut *info.flow_ref).clip = clip;
|
||||||
|
|
||||||
|
let block_flow = info.flow_ref.as_block();
|
||||||
|
block_flow.base.absolute_position_info = self.base.absolute_position_info;
|
||||||
|
|
||||||
|
let stacking_relative_position = self.base.stacking_relative_position;
|
||||||
|
let padding_box_origin = containing_block_positions.next().unwrap();
|
||||||
|
block_flow.base
|
||||||
|
.absolute_position_info
|
||||||
|
.stacking_relative_position_of_absolute_containing_block =
|
||||||
|
stacking_relative_position + *padding_box_origin;
|
||||||
|
|
||||||
|
block_flow.base.stacking_relative_position =
|
||||||
|
stacking_relative_border_box.origin
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1491,6 +1581,30 @@ impl Flow for InlineFlow {
|
||||||
(*mutator)(fragment)
|
(*mutator)(fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains_positioned_fragments(&self) -> bool {
|
||||||
|
self.fragments.fragments.iter().any(|fragment| {
|
||||||
|
fragment.style.get_box().position != position::T::static_
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_relatively_positioned_fragments(&self) -> bool {
|
||||||
|
self.fragments.fragments.iter().any(|fragment| {
|
||||||
|
fragment.style.get_box().position == position::T::relative
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generated_containing_block_size(&self, for_flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
|
let mut containing_block_size = LogicalSize::new(self.base.writing_mode, Au(0), Au(0));
|
||||||
|
for index in self.containing_block_range_for_flow(for_flow).each_index() {
|
||||||
|
let fragment = &self.fragments.fragments[index.get() as usize];
|
||||||
|
containing_block_size.inline = containing_block_size.inline +
|
||||||
|
fragment.border_box.size.inline;
|
||||||
|
containing_block_size.block = max(containing_block_size.block,
|
||||||
|
fragment.border_box.size.block)
|
||||||
|
}
|
||||||
|
containing_block_size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for InlineFlow {
|
impl fmt::Debug for InlineFlow {
|
||||||
|
|
|
@ -11,7 +11,7 @@ use block::BlockFlow;
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use display_list_builder::ListItemFlowDisplayListBuilding;
|
use display_list_builder::ListItemFlowDisplayListBuilding;
|
||||||
use floats::FloatKind;
|
use floats::FloatKind;
|
||||||
use flow::{Flow, FlowClass};
|
use flow::{Flow, FlowClass, OpaqueFlow};
|
||||||
use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, GeneratedContentInfo};
|
use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, GeneratedContentInfo};
|
||||||
use generated_content;
|
use generated_content;
|
||||||
use incremental::RESOLVE_GENERATED_CONTENT;
|
use incremental::RESOLVE_GENERATED_CONTENT;
|
||||||
|
@ -22,7 +22,7 @@ use wrapper::ThreadSafeLayoutNode;
|
||||||
use geom::{Point2D, Rect};
|
use geom::{Point2D, Rect};
|
||||||
use gfx::display_list::DisplayList;
|
use gfx::display_list::DisplayList;
|
||||||
use util::geometry::Au;
|
use util::geometry::Au;
|
||||||
use util::logical_geometry::LogicalRect;
|
use util::logical_geometry::LogicalSize;
|
||||||
use util::opts;
|
use util::opts;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::computed_values::list_style_type;
|
use style::computed_values::list_style_type;
|
||||||
|
@ -148,8 +148,8 @@ impl Flow for ListItemFlow {
|
||||||
self.block_flow.compute_overflow()
|
self.block_flow.compute_overflow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
|
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
self.block_flow.generated_containing_block_rect()
|
self.block_flow.generated_containing_block_size(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iterate_through_fragment_border_boxes(&self,
|
fn iterate_through_fragment_border_boxes(&self,
|
||||||
|
|
|
@ -9,13 +9,13 @@
|
||||||
use block::BlockFlow;
|
use block::BlockFlow;
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use floats::FloatKind;
|
use floats::FloatKind;
|
||||||
use flow::{FlowClass, Flow};
|
use flow::{FlowClass, Flow, OpaqueFlow};
|
||||||
use fragment::{Fragment, FragmentBorderBoxIterator};
|
use fragment::{Fragment, FragmentBorderBoxIterator};
|
||||||
use wrapper::ThreadSafeLayoutNode;
|
use wrapper::ThreadSafeLayoutNode;
|
||||||
|
|
||||||
use geom::{Point2D, Rect};
|
use geom::{Point2D, Rect};
|
||||||
use util::geometry::Au;
|
use util::geometry::Au;
|
||||||
use util::logical_geometry::LogicalRect;
|
use util::logical_geometry::LogicalSize;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -88,8 +88,8 @@ impl Flow for MulticolFlow {
|
||||||
self.block_flow.compute_overflow()
|
self.block_flow.compute_overflow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
|
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
self.block_flow.generated_containing_block_rect()
|
self.block_flow.generated_containing_block_size(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iterate_through_fragment_border_boxes(&self,
|
fn iterate_through_fragment_border_boxes(&self,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use block::{ISizeConstraintInput, ISizeConstraintSolution};
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode};
|
use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode};
|
||||||
use flow::{self, Flow, FlowClass, IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS};
|
use flow::{self, Flow, FlowClass, IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS};
|
||||||
use flow::{ImmutableFlowUtils};
|
use flow::{ImmutableFlowUtils, OpaqueFlow};
|
||||||
use fragment::{Fragment, FragmentBorderBoxIterator};
|
use fragment::{Fragment, FragmentBorderBoxIterator};
|
||||||
use incremental::{REFLOW, REFLOW_OUT_OF_FLOW};
|
use incremental::{REFLOW, REFLOW_OUT_OF_FLOW};
|
||||||
use layout_debug;
|
use layout_debug;
|
||||||
|
@ -32,7 +32,7 @@ use style::properties::ComputedValues;
|
||||||
use style::values::CSSFloat;
|
use style::values::CSSFloat;
|
||||||
use style::values::computed::LengthOrPercentageOrAuto;
|
use style::values::computed::LengthOrPercentageOrAuto;
|
||||||
use util::geometry::Au;
|
use util::geometry::Au;
|
||||||
use util::logical_geometry::LogicalRect;
|
use util::logical_geometry::LogicalSize;
|
||||||
|
|
||||||
/// A table flow corresponded to the table's internal table fragment under a table wrapper flow.
|
/// A table flow corresponded to the table's internal table fragment under a table wrapper flow.
|
||||||
/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment,
|
/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment,
|
||||||
|
@ -507,8 +507,8 @@ impl Flow for TableFlow {
|
||||||
self.block_flow.compute_absolute_position()
|
self.block_flow.compute_absolute_position()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
|
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
self.block_flow.generated_containing_block_rect()
|
self.block_flow.generated_containing_block_size(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
|
|
||||||
use block::BlockFlow;
|
use block::BlockFlow;
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use flow::{FlowClass, Flow};
|
use flow::{FlowClass, Flow, OpaqueFlow};
|
||||||
use fragment::{Fragment, FragmentBorderBoxIterator};
|
use fragment::{Fragment, FragmentBorderBoxIterator};
|
||||||
use wrapper::ThreadSafeLayoutNode;
|
use wrapper::ThreadSafeLayoutNode;
|
||||||
|
|
||||||
use geom::{Point2D, Rect};
|
use geom::{Point2D, Rect};
|
||||||
use util::geometry::Au;
|
use util::geometry::Au;
|
||||||
use util::logical_geometry::LogicalRect;
|
use util::logical_geometry::LogicalSize;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -89,8 +89,8 @@ impl Flow for TableCaptionFlow {
|
||||||
self.block_flow.compute_overflow()
|
self.block_flow.compute_overflow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
|
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
self.block_flow.generated_containing_block_rect()
|
self.block_flow.generated_containing_block_size(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iterate_through_fragment_border_boxes(&self,
|
fn iterate_through_fragment_border_boxes(&self,
|
||||||
|
|
|
@ -10,7 +10,7 @@ use block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayCollapseFlag};
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use css::node_style::StyledNode;
|
use css::node_style::StyledNode;
|
||||||
use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode};
|
use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode};
|
||||||
use flow::{Flow, FlowClass};
|
use flow::{Flow, FlowClass, OpaqueFlow};
|
||||||
use fragment::{Fragment, FragmentBorderBoxIterator};
|
use fragment::{Fragment, FragmentBorderBoxIterator};
|
||||||
use model::MaybeAuto;
|
use model::MaybeAuto;
|
||||||
use layout_debug;
|
use layout_debug;
|
||||||
|
@ -27,7 +27,7 @@ use style::computed_values::{border_collapse, border_top_style};
|
||||||
use style::legacy::UnsignedIntegerAttribute;
|
use style::legacy::UnsignedIntegerAttribute;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use util::geometry::Au;
|
use util::geometry::Au;
|
||||||
use util::logical_geometry::{LogicalMargin, LogicalRect, WritingMode};
|
use util::logical_geometry::{LogicalMargin, LogicalRect, LogicalSize, WritingMode};
|
||||||
|
|
||||||
/// A table formatting context.
|
/// A table formatting context.
|
||||||
#[derive(RustcEncodable)]
|
#[derive(RustcEncodable)]
|
||||||
|
@ -204,8 +204,8 @@ impl Flow for TableCellFlow {
|
||||||
self.block_flow.compute_overflow()
|
self.block_flow.compute_overflow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
|
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
self.block_flow.generated_containing_block_rect()
|
self.block_flow.generated_containing_block_size(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iterate_through_fragment_border_boxes(&self,
|
fn iterate_through_fragment_border_boxes(&self,
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use css::node_style::StyledNode;
|
use css::node_style::StyledNode;
|
||||||
use flow::{BaseFlow, FlowClass, Flow, ForceNonfloatedFlag};
|
use flow::{BaseFlow, FlowClass, Flow, ForceNonfloatedFlag, OpaqueFlow};
|
||||||
use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
|
use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
|
||||||
use layout_debug;
|
use layout_debug;
|
||||||
use wrapper::ThreadSafeLayoutNode;
|
use wrapper::ThreadSafeLayoutNode;
|
||||||
|
@ -20,6 +20,7 @@ use std::fmt;
|
||||||
use style::values::computed::LengthOrPercentageOrAuto;
|
use style::values::computed::LengthOrPercentageOrAuto;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use util::logical_geometry::LogicalSize;
|
||||||
|
|
||||||
/// A table formatting context.
|
/// A table formatting context.
|
||||||
pub struct TableColGroupFlow {
|
pub struct TableColGroupFlow {
|
||||||
|
@ -101,6 +102,10 @@ impl Flow for TableColGroupFlow {
|
||||||
ZERO_RECT
|
ZERO_RECT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
|
panic!("Table column groups can't be containing blocks!")
|
||||||
|
}
|
||||||
|
|
||||||
fn iterate_through_fragment_border_boxes(&self,
|
fn iterate_through_fragment_border_boxes(&self,
|
||||||
_: &mut FragmentBorderBoxIterator,
|
_: &mut FragmentBorderBoxIterator,
|
||||||
_: &Point2D<Au>) {}
|
_: &Point2D<Au>) {}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
use block::{BlockFlow, ISizeAndMarginsComputer};
|
use block::{BlockFlow, ISizeAndMarginsComputer};
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode};
|
use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode};
|
||||||
use flow::{self, FlowClass, Flow, ImmutableFlowUtils};
|
use flow::{self, FlowClass, Flow, ImmutableFlowUtils, OpaqueFlow};
|
||||||
use flow_list::MutFlowListIterator;
|
use flow_list::MutFlowListIterator;
|
||||||
use fragment::{Fragment, FragmentBorderBoxIterator};
|
use fragment::{Fragment, FragmentBorderBoxIterator};
|
||||||
use layout_debug;
|
use layout_debug;
|
||||||
|
@ -30,7 +30,7 @@ use style::computed_values::{border_collapse, border_spacing, border_top_style};
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::values::computed::LengthOrPercentageOrAuto;
|
use style::values::computed::LengthOrPercentageOrAuto;
|
||||||
use util::geometry::Au;
|
use util::geometry::Au;
|
||||||
use util::logical_geometry::{LogicalRect, PhysicalSide, WritingMode};
|
use util::logical_geometry::{LogicalSize, PhysicalSide, WritingMode};
|
||||||
|
|
||||||
/// A single row of a table.
|
/// A single row of a table.
|
||||||
pub struct TableRowFlow {
|
pub struct TableRowFlow {
|
||||||
|
@ -438,8 +438,8 @@ impl Flow for TableRowFlow {
|
||||||
self.block_flow.compute_overflow()
|
self.block_flow.compute_overflow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
|
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
self.block_flow.generated_containing_block_rect()
|
self.block_flow.generated_containing_block_size(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iterate_through_fragment_border_boxes(&self,
|
fn iterate_through_fragment_border_boxes(&self,
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
use block::{BlockFlow, ISizeAndMarginsComputer};
|
use block::{BlockFlow, ISizeAndMarginsComputer};
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use flow::{FlowClass, Flow};
|
use flow::{FlowClass, Flow, OpaqueFlow};
|
||||||
use fragment::{Fragment, FragmentBorderBoxIterator};
|
use fragment::{Fragment, FragmentBorderBoxIterator};
|
||||||
use layout_debug;
|
use layout_debug;
|
||||||
use style::computed_values::{border_collapse, border_spacing};
|
use style::computed_values::{border_collapse, border_spacing};
|
||||||
|
@ -23,7 +23,7 @@ use std::iter::{IntoIterator, Iterator, Peekable};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use util::geometry::Au;
|
use util::geometry::Au;
|
||||||
use util::logical_geometry::{LogicalRect, WritingMode};
|
use util::logical_geometry::{LogicalSize, WritingMode};
|
||||||
|
|
||||||
/// A table formatting context.
|
/// A table formatting context.
|
||||||
pub struct TableRowGroupFlow {
|
pub struct TableRowGroupFlow {
|
||||||
|
@ -228,8 +228,8 @@ impl Flow for TableRowGroupFlow {
|
||||||
self.block_flow.compute_overflow()
|
self.block_flow.compute_overflow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
|
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
self.block_flow.generated_containing_block_rect()
|
self.block_flow.generated_containing_block_size(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iterate_through_fragment_border_boxes(&self,
|
fn iterate_through_fragment_border_boxes(&self,
|
||||||
|
|
|
@ -18,7 +18,7 @@ use block::{ISizeConstraintSolution, MarginsMayCollapseFlag};
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use floats::FloatKind;
|
use floats::FloatKind;
|
||||||
use flow::{FlowClass, Flow, ImmutableFlowUtils};
|
use flow::{FlowClass, Flow, ImmutableFlowUtils};
|
||||||
use flow::{IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS};
|
use flow::{IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS, OpaqueFlow};
|
||||||
use fragment::{Fragment, FragmentBorderBoxIterator};
|
use fragment::{Fragment, FragmentBorderBoxIterator};
|
||||||
use model::MaybeAuto;
|
use model::MaybeAuto;
|
||||||
use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize};
|
use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize};
|
||||||
|
@ -27,7 +27,7 @@ use wrapper::ThreadSafeLayoutNode;
|
||||||
|
|
||||||
use geom::{Point2D, Rect};
|
use geom::{Point2D, Rect};
|
||||||
use util::geometry::Au;
|
use util::geometry::Au;
|
||||||
use util::logical_geometry::LogicalRect;
|
use util::logical_geometry::LogicalSize;
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
@ -397,8 +397,8 @@ impl Flow for TableWrapperFlow {
|
||||||
self.block_flow.update_late_computed_block_position_if_necessary(block_position)
|
self.block_flow.update_late_computed_block_position_if_necessary(block_position)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
|
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||||
self.block_flow.generated_containing_block_rect()
|
self.block_flow.generated_containing_block_size(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_display_list(&mut self, layout_context: &LayoutContext) {
|
fn build_display_list(&mut self, layout_context: &LayoutContext) {
|
||||||
|
|
30
tests/ref/absolute_inline_containing_block_a.html
Normal file
30
tests/ref/absolute_inline_containing_block_a.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.1px;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
#a {
|
||||||
|
padding-left: 100px;
|
||||||
|
}
|
||||||
|
#b {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#c {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
background: purple;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div><span id=a> </span><span id=b> <div id=c></div></span></span></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
24
tests/ref/absolute_inline_containing_block_ref.html
Normal file
24
tests/ref/absolute_inline_containing_block_ref.html
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.1px;
|
||||||
|
}
|
||||||
|
#a {
|
||||||
|
position: absolute;
|
||||||
|
left: 100px;
|
||||||
|
top: 0;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
background: purple;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id=a></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ fragment=top != ../html/acid2.html acid2_ref.html
|
||||||
== 2dcontext/lineto_a.html 2dcontext/lineto_ref.html
|
== 2dcontext/lineto_a.html 2dcontext/lineto_ref.html
|
||||||
== 2dcontext/transform_a.html 2dcontext/transform_ref.html
|
== 2dcontext/transform_a.html 2dcontext/transform_ref.html
|
||||||
|
|
||||||
|
== absolute_inline_containing_block_a.html absolute_inline_containing_block_ref.html
|
||||||
== acid1_a.html acid1_b.html
|
== acid1_a.html acid1_b.html
|
||||||
== acid2_noscroll.html acid2_ref_broken.html
|
== acid2_noscroll.html acid2_ref_broken.html
|
||||||
== after_block_iteration.html after_block_iteration_ref.html
|
== after_block_iteration.html after_block_iteration_ref.html
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue