mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Implement position: absolute
for replaced elements.
Add reftests for replaced and nested absolute flows.
This commit is contained in:
parent
4a6077ca4c
commit
070be51910
9 changed files with 402 additions and 43 deletions
|
@ -10,8 +10,8 @@
|
|||
//! The term 'positioned element' refers to elements with position =
|
||||
//! 'relative', 'absolute', or 'fixed'.
|
||||
|
||||
use layout::box_::Box;
|
||||
use layout::construct::{FlowConstructor, OptVector};
|
||||
use layout::box_::{Box, ImageBox, ScannedTextBox};
|
||||
use layout::construct::FlowConstructor;
|
||||
use layout::context::LayoutContext;
|
||||
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
|
||||
use layout::floats::{FloatKind, Floats, PlacementInfo};
|
||||
|
@ -78,6 +78,94 @@ impl WidthConstraintSolution {
|
|||
margin_right: margin_right,
|
||||
}
|
||||
}
|
||||
|
||||
/// Solve the horizontal constraint equation for absolute replaced elements.
|
||||
///
|
||||
/// `static_x_offset`: total offset of current flow's hypothetical
|
||||
/// position (static position) from its actual Containing Block.
|
||||
///
|
||||
/// Assumption: The used value for width has already been calculated.
|
||||
///
|
||||
/// CSS Section 10.3.8
|
||||
/// Constraint equation:
|
||||
/// left + right + width + margin-left + margin-right
|
||||
/// = absolute containing block width - (horizontal padding and border)
|
||||
/// [aka available_width]
|
||||
///
|
||||
/// Return the solution for the equation.
|
||||
fn solve_horiz_constraints_abs_replaced(width: Au,
|
||||
left_margin: MaybeAuto,
|
||||
right_margin: MaybeAuto,
|
||||
left: MaybeAuto,
|
||||
right: MaybeAuto,
|
||||
available_width: Au,
|
||||
static_x_offset: Au)
|
||||
-> WidthConstraintSolution {
|
||||
// TODO: Check for direction of static-position Containing Block (aka
|
||||
// parent flow, _not_ the actual Containing Block) when right-to-left
|
||||
// is implemented
|
||||
// Assume direction is 'ltr' for now
|
||||
// TODO: Handle all the cases for 'rtl' direction.
|
||||
|
||||
// Distance from the left edge of the Absolute Containing Block to the
|
||||
// left margin edge of a hypothetical box that would have been the
|
||||
// first box of the element.
|
||||
let static_position_left = static_x_offset;
|
||||
|
||||
let (left, right, width, margin_left, margin_right) = match (left, right) {
|
||||
(Auto, Auto) => {
|
||||
let left = static_position_left;
|
||||
let margin_l = left_margin.specified_or_zero();
|
||||
let margin_r = right_margin.specified_or_zero();
|
||||
let sum = left + width + margin_l + margin_r;
|
||||
(left, available_width - sum, width, margin_l, margin_r)
|
||||
}
|
||||
// If only one is Auto, solve for it
|
||||
(Auto, Specified(right)) => {
|
||||
let margin_l = left_margin.specified_or_zero();
|
||||
let margin_r = right_margin.specified_or_zero();
|
||||
let sum = right + width + margin_l + margin_r;
|
||||
(available_width - sum, right, width, margin_l, margin_r)
|
||||
}
|
||||
(Specified(left), Auto) => {
|
||||
let margin_l = left_margin.specified_or_zero();
|
||||
let margin_r = right_margin.specified_or_zero();
|
||||
let sum = left + width + margin_l + margin_r;
|
||||
(left, available_width - sum, width, margin_l, margin_r)
|
||||
}
|
||||
(Specified(left), Specified(right)) => {
|
||||
match (left_margin, right_margin) {
|
||||
(Auto, Auto) => {
|
||||
let total_margin_val = (available_width - left - right - width);
|
||||
if total_margin_val < Au(0) {
|
||||
// margin-left becomes 0 because direction is 'ltr'.
|
||||
(left, right, width, Au(0), total_margin_val)
|
||||
} else {
|
||||
// Equal margins
|
||||
(left, right, width,
|
||||
total_margin_val.scale_by(0.5),
|
||||
total_margin_val.scale_by(0.5))
|
||||
}
|
||||
}
|
||||
(Specified(margin_l), Auto) => {
|
||||
let sum = left + right + width + margin_l;
|
||||
(left, right, width, margin_l, available_width - sum)
|
||||
}
|
||||
(Auto, Specified(margin_r)) => {
|
||||
let sum = left + right + width + margin_r;
|
||||
(left, right, width, available_width - sum, margin_r)
|
||||
}
|
||||
(Specified(margin_l), Specified(margin_r)) => {
|
||||
// Values are over-constrained.
|
||||
// Ignore value for 'right' cos direction is 'ltr'.
|
||||
let sum = left + width + margin_l + margin_r;
|
||||
(left, available_width - sum, width, margin_l, margin_r)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
WidthConstraintSolution::new(left, right, width, margin_left, margin_right)
|
||||
}
|
||||
}
|
||||
|
||||
/// The solutions for the heights-and-margins constraint equation.
|
||||
|
@ -110,7 +198,7 @@ impl HeightConstraintSolution {
|
|||
/// [aka available_height]
|
||||
///
|
||||
/// Return the solution for the equation.
|
||||
fn solve_vertical_constraints_abs_position(height: MaybeAuto,
|
||||
fn solve_vertical_constraints_abs_nonreplaced(height: MaybeAuto,
|
||||
top_margin: MaybeAuto,
|
||||
bottom_margin: MaybeAuto,
|
||||
top: MaybeAuto,
|
||||
|
@ -123,7 +211,7 @@ impl HeightConstraintSolution {
|
|||
// top margin edge of a hypothetical box that would have been the
|
||||
// first box of the element.
|
||||
let static_position_top = static_y_offset;
|
||||
;
|
||||
|
||||
let (top, bottom, height, margin_top, margin_bottom) = match (top, bottom, height) {
|
||||
(Auto, Auto, Auto) => {
|
||||
let margin_top = top_margin.specified_or_zero();
|
||||
|
@ -209,6 +297,80 @@ impl HeightConstraintSolution {
|
|||
};
|
||||
HeightConstraintSolution::new(top, bottom, height, margin_top, margin_bottom)
|
||||
}
|
||||
|
||||
/// Solve the vertical constraint equation for absolute replaced elements.
|
||||
///
|
||||
/// Assumption: The used value for height has already been calculated.
|
||||
///
|
||||
/// CSS Section 10.6.5
|
||||
/// Constraint equation:
|
||||
/// top + bottom + height + margin-top + margin-bottom
|
||||
/// = absolute containing block height - (vertical padding and border)
|
||||
/// [aka available_height]
|
||||
///
|
||||
/// Return the solution for the equation.
|
||||
fn solve_vertical_constraints_abs_replaced(height: Au,
|
||||
top_margin: MaybeAuto,
|
||||
bottom_margin: MaybeAuto,
|
||||
top: MaybeAuto,
|
||||
bottom: MaybeAuto,
|
||||
_: Au,
|
||||
available_height: Au,
|
||||
static_y_offset: Au)
|
||||
-> HeightConstraintSolution {
|
||||
// Distance from the top edge of the Absolute Containing Block to the
|
||||
// top margin edge of a hypothetical box that would have been the
|
||||
// first box of the element.
|
||||
let static_position_top = static_y_offset;
|
||||
|
||||
let (top, bottom, height, margin_top, margin_bottom) = match (top, bottom) {
|
||||
(Auto, Auto) => {
|
||||
let margin_top = top_margin.specified_or_zero();
|
||||
let margin_bottom = bottom_margin.specified_or_zero();
|
||||
let top = static_position_top;
|
||||
let sum = top + height + margin_top + margin_bottom;
|
||||
(top, available_height - sum, height, margin_top, margin_bottom)
|
||||
}
|
||||
(Specified(top), Specified(bottom)) => {
|
||||
match (top_margin, bottom_margin) {
|
||||
(Auto, Auto) => {
|
||||
let total_margin_val = (available_height - top - bottom - height);
|
||||
(top, bottom, height,
|
||||
total_margin_val.scale_by(0.5),
|
||||
total_margin_val.scale_by(0.5))
|
||||
}
|
||||
(Specified(margin_top), Auto) => {
|
||||
let sum = top + bottom + height + margin_top;
|
||||
(top, bottom, height, margin_top, available_height - sum)
|
||||
}
|
||||
(Auto, Specified(margin_bottom)) => {
|
||||
let sum = top + bottom + height + margin_bottom;
|
||||
(top, bottom, height, available_height - sum, margin_bottom)
|
||||
}
|
||||
(Specified(margin_top), Specified(margin_bottom)) => {
|
||||
// Values are over-constrained. Ignore value for 'bottom'.
|
||||
let sum = top + height + margin_top + margin_bottom;
|
||||
(top, available_height - sum, height, margin_top, margin_bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If only one is Auto, solve for it
|
||||
(Auto, Specified(bottom)) => {
|
||||
let margin_top = top_margin.specified_or_zero();
|
||||
let margin_bottom = bottom_margin.specified_or_zero();
|
||||
let sum = bottom + height + margin_top + margin_bottom;
|
||||
(available_height - sum, bottom, height, margin_top, margin_bottom)
|
||||
}
|
||||
(Specified(top), Auto) => {
|
||||
let margin_top = top_margin.specified_or_zero();
|
||||
let margin_bottom = bottom_margin.specified_or_zero();
|
||||
let sum = top + height + margin_top + margin_bottom;
|
||||
(top, available_height - sum, height, margin_top, margin_bottom)
|
||||
}
|
||||
};
|
||||
HeightConstraintSolution::new(top, bottom, height, margin_top, margin_bottom)
|
||||
}
|
||||
}
|
||||
|
||||
/// The real assign-heights traversal for flows with position 'absolute'.
|
||||
|
@ -424,6 +586,22 @@ impl BlockFlow {
|
|||
traversal.process(flow)
|
||||
}
|
||||
|
||||
/// Return true if this has a replaced box.
|
||||
///
|
||||
/// The only two types of replaced boxes currently are text boxes and
|
||||
/// image boxes.
|
||||
fn is_replaced_content(&self) -> bool {
|
||||
match self.box_ {
|
||||
Some(ref box_) => {
|
||||
match box_.specific {
|
||||
ScannedTextBox(_) | ImageBox(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn teardown(&mut self) {
|
||||
for box_ in self.box_.iter() {
|
||||
box_.teardown();
|
||||
|
@ -521,14 +699,28 @@ impl BlockFlow {
|
|||
(MaybeAuto::from_style(style.PositionOffsets.get().left, containing_block_width),
|
||||
MaybeAuto::from_style(style.PositionOffsets.get().right, containing_block_width));
|
||||
let available_width = containing_block_width - box_.border_and_padding_horiz();
|
||||
// TODO: Extract this later into a SolveConstraints trait or something
|
||||
let solution = self.solve_horiz_constraints_abs_position(width,
|
||||
|
||||
let solution = if self.is_replaced_content() {
|
||||
// Calculate used value of width just like we do for inline replaced elements.
|
||||
box_.assign_replaced_width_if_necessary(containing_block_width);
|
||||
let width = box_.border_box.get().size.width;
|
||||
WidthConstraintSolution::solve_horiz_constraints_abs_replaced(width,
|
||||
margin_left,
|
||||
margin_right,
|
||||
left,
|
||||
right,
|
||||
available_width,
|
||||
static_x_offset);
|
||||
static_x_offset)
|
||||
} else {
|
||||
// TODO: Extract this later into a SolveConstraints trait or something
|
||||
self.solve_horiz_constraints_abs_nonreplaced(width,
|
||||
margin_left,
|
||||
margin_right,
|
||||
left,
|
||||
right,
|
||||
available_width,
|
||||
static_x_offset)
|
||||
};
|
||||
|
||||
let mut margin = box_.margin.get();
|
||||
margin.left = solution.margin_left;
|
||||
|
@ -614,7 +806,7 @@ impl BlockFlow {
|
|||
/// [aka available_width]
|
||||
///
|
||||
/// Return the solution for the equation.
|
||||
fn solve_horiz_constraints_abs_position(&self,
|
||||
fn solve_horiz_constraints_abs_nonreplaced(&self,
|
||||
width: MaybeAuto,
|
||||
left_margin: MaybeAuto,
|
||||
right_margin: MaybeAuto,
|
||||
|
@ -623,8 +815,9 @@ impl BlockFlow {
|
|||
available_width: Au,
|
||||
static_x_offset: Au)
|
||||
-> WidthConstraintSolution {
|
||||
// TODO: Check for direction of parent flow (NOT Containing Block)
|
||||
// when right-to-left is implemented.
|
||||
// TODO: Check for direction of static-position Containing Block (aka
|
||||
// parent flow, _not_ the actual Containing Block) when right-to-left
|
||||
// is implemented
|
||||
// Assume direction is 'ltr' for now
|
||||
|
||||
// Distance from the left edge of the Absolute Containing Block to the
|
||||
|
@ -1358,7 +1551,25 @@ impl BlockFlow {
|
|||
MaybeAuto::from_style(style.PositionOffsets.get().bottom, containing_block_height));
|
||||
let available_height = containing_block_height - box_.border_and_padding_vert();
|
||||
|
||||
let solution = HeightConstraintSolution::solve_vertical_constraints_abs_position(
|
||||
let solution = if self.is_replaced_content() {
|
||||
// Calculate used value of height just like we do for inline replaced elements.
|
||||
// TODO: Pass in the containing block height when Box's
|
||||
// assign-height can handle it correctly.
|
||||
box_.assign_replaced_height_if_necessary();
|
||||
// TODO: Right now, this content height value includes the
|
||||
// margin because of erroneous height calculation in Box_.
|
||||
// Check this when that has been fixed.
|
||||
let height_used_val = box_.border_box.get().size.height;
|
||||
HeightConstraintSolution::solve_vertical_constraints_abs_replaced(height_used_val,
|
||||
margin_top,
|
||||
margin_bottom,
|
||||
top,
|
||||
bottom,
|
||||
content_height,
|
||||
available_height,
|
||||
static_y_offset)
|
||||
} else {
|
||||
HeightConstraintSolution::solve_vertical_constraints_abs_nonreplaced(
|
||||
height_used_val,
|
||||
margin_top,
|
||||
margin_bottom,
|
||||
|
@ -1366,7 +1577,8 @@ impl BlockFlow {
|
|||
bottom,
|
||||
content_height,
|
||||
available_height,
|
||||
static_y_offset);
|
||||
static_y_offset)
|
||||
};
|
||||
|
||||
let mut margin = box_.margin.get();
|
||||
margin.top = solution.margin_top;
|
||||
|
@ -1626,7 +1838,7 @@ impl Flow for BlockFlow {
|
|||
fn assign_height(&mut self, ctx: &mut LayoutContext) {
|
||||
// Assign height for box if it is an image box.
|
||||
for box_ in self.box_.iter() {
|
||||
box_.assign_height();
|
||||
box_.assign_replaced_height_if_necessary();
|
||||
}
|
||||
|
||||
if self.is_float() {
|
||||
|
|
|
@ -166,6 +166,10 @@ impl ImageBoxInfo {
|
|||
Au::from_px(image_ref.get().get_size().unwrap_or(Size2D(0,0)).width)
|
||||
}
|
||||
|
||||
// Return used value for width or height.
|
||||
//
|
||||
// `dom_length`: width or height as specified in the `img` tag.
|
||||
// `style_length`: width as given in the CSS
|
||||
pub fn style_length(style_length: LengthOrPercentageOrAuto,
|
||||
dom_length: Option<Au>,
|
||||
container_width: Au) -> MaybeAuto {
|
||||
|
@ -1455,6 +1459,7 @@ impl Box {
|
|||
|
||||
/// Assigns replaced width for this box only if it is replaced content.
|
||||
///
|
||||
/// This assigns only the width, not margin or anything else.
|
||||
/// CSS 2.1 § 10.3.2.
|
||||
pub fn assign_replaced_width_if_necessary(&self,container_width: Au) {
|
||||
match self.specific {
|
||||
|
@ -1502,8 +1507,10 @@ impl Box {
|
|||
}
|
||||
}
|
||||
|
||||
/// Assign height for image and scanned text boxes.
|
||||
pub fn assign_height(&self) {
|
||||
/// Assign height for this box if it is replaced content.
|
||||
///
|
||||
/// Ideally, this should follow CSS 2.1 § 10.6.2
|
||||
pub fn assign_replaced_height_if_necessary(&self) {
|
||||
match self.specific {
|
||||
GenericBox | IframeBox(_) => {
|
||||
}
|
||||
|
|
|
@ -687,6 +687,13 @@ impl<'a> FlowConstructor<'a> {
|
|||
}
|
||||
|
||||
impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> {
|
||||
// Construct Flow based on 'display', 'position', and 'float' values.
|
||||
//
|
||||
// CSS 2.1 Section 9.7
|
||||
//
|
||||
// TODO: This should actually consult the table in that section to get the
|
||||
// final computed value for 'display'.
|
||||
//
|
||||
// `#[inline(always)]` because this is always called from the traversal function and for some
|
||||
// reason LLVM's inlining heuristics go awry here.
|
||||
#[inline(always)]
|
||||
|
@ -718,6 +725,10 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
// Absolutely positioned elements will have computed value of
|
||||
// `float` as 'none' and `display` as per the table.
|
||||
// Currently, for original `display` value of 'inline', the new
|
||||
// `display` value is 'block'.
|
||||
(_, _, position::absolute) | (_, _, position::fixed) => {
|
||||
node.set_flow_construction_result(self.build_flow_for_block(node))
|
||||
}
|
||||
|
|
|
@ -698,7 +698,7 @@ impl Flow for InlineFlow {
|
|||
debug!("assign_height_inline: floats in: {:?}", self.base.floats);
|
||||
// assign height for inline boxes
|
||||
for box_ in self.boxes.iter() {
|
||||
box_.assign_height();
|
||||
box_.assign_replaced_height_if_necessary();
|
||||
}
|
||||
let scanner_floats = self.base.floats.clone();
|
||||
let mut scanner = LineboxScanner::new(scanner_floats);
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
== position_abs_cb_with_non_cb_kid_a.html position_abs_cb_with_non_cb_kid_b.html
|
||||
== position_abs_height_width_a.html position_abs_height_width_b.html
|
||||
== position_abs_left_a.html position_abs_left_b.html
|
||||
== position_abs_nested_a.html position_abs_nested_b.html
|
||||
== position_abs_replaced_simple_a.html position_abs_replaced_simple_b.html
|
||||
== position_abs_static_y_a.html position_abs_static_y_b.html
|
||||
== position_abs_width_percentage_a.html position_abs_width_percentage_b.html
|
||||
== position_fixed_a.html position_fixed_b.html
|
||||
|
|
34
src/test/ref/position_abs_nested_a.html
Normal file
34
src/test/ref/position_abs_nested_a.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
<html>
|
||||
<head>
|
||||
<style>
|
||||
#first {
|
||||
position: relative;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border: solid 1px;
|
||||
}
|
||||
#abs {
|
||||
position: absolute;
|
||||
left: 30px;
|
||||
top: 30px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background: blue;
|
||||
}
|
||||
#abs2 {
|
||||
position: absolute;
|
||||
background: green;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="first">
|
||||
<div id="abs">
|
||||
<div id="abs2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
36
src/test/ref/position_abs_nested_b.html
Normal file
36
src/test/ref/position_abs_nested_b.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
<html>
|
||||
<head>
|
||||
<style>
|
||||
#first {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border: solid 1px;
|
||||
}
|
||||
.row {
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
}
|
||||
.center {
|
||||
margin-left: 30px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background: blue;
|
||||
}
|
||||
.little-box {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
background: green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="first">
|
||||
<div class="row"></div>
|
||||
<div class="center">
|
||||
<div class="little-box">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
25
src/test/ref/position_abs_replaced_simple_a.html
Normal file
25
src/test/ref/position_abs_replaced_simple_a.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<html>
|
||||
<head>
|
||||
<style>
|
||||
#first {
|
||||
position: relative;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border: solid 1px;
|
||||
}
|
||||
#abs {
|
||||
position: absolute;
|
||||
margin: 0px;
|
||||
top: 30px;
|
||||
right: 30px;
|
||||
bottom: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="first">
|
||||
<img src="rust_logo.png" width="100" id="abs" alt="Rust Logo" />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
32
src/test/ref/position_abs_replaced_simple_b.html
Normal file
32
src/test/ref/position_abs_replaced_simple_b.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
<html>
|
||||
<head>
|
||||
<style>
|
||||
#first {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border: solid 1px;
|
||||
}
|
||||
.row {
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
}
|
||||
.center {
|
||||
margin-left: 30px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background: green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="first">
|
||||
<div class="row">
|
||||
</div>
|
||||
<div class="center">
|
||||
<img src="rust_logo.png" width="30" id="abs" alt="Rust Logo" />
|
||||
</div>
|
||||
<div class="row">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue