mirror of
https://github.com/servo/servo.git
synced 2025-07-30 18:50:36 +01:00
Handle min/max sizes in non-replaced positioned boxes
This commit is contained in:
parent
df83e2a998
commit
73dcce12b6
34 changed files with 240 additions and 113 deletions
|
@ -15,7 +15,7 @@ use rayon::iter::{IntoParallelRefMutIterator, ParallelExtend};
|
|||
use rayon_croissant::ParallelIteratorExt;
|
||||
use style::computed_values::position::T as Position;
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::{Length, LengthPercentage};
|
||||
use style::values::computed::{CSSPixelLength, Length, LengthPercentage};
|
||||
use style::values::specified::text::TextDecorationLine;
|
||||
use style::Zero;
|
||||
|
||||
|
@ -424,7 +424,7 @@ impl HoistedAbsolutelyPositionedBox {
|
|||
.style()
|
||||
.padding_border_margin(&containing_block.into());
|
||||
|
||||
let size = match &absolutely_positioned_box.context {
|
||||
let computed_size = match &absolutely_positioned_box.context {
|
||||
IndependentFormattingContext::Replaced(replaced) => {
|
||||
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
|
||||
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
|
||||
|
@ -445,32 +445,32 @@ impl HoistedAbsolutelyPositionedBox {
|
|||
|
||||
let shared_fragment = self.fragment.borrow();
|
||||
|
||||
let inline_axis = solve_axis(
|
||||
cbis,
|
||||
pbm.padding_border_sums.inline,
|
||||
pbm.margin.inline_start,
|
||||
pbm.margin.inline_end,
|
||||
/* avoid_negative_margin_start */ true,
|
||||
&shared_fragment.box_offsets.inline,
|
||||
size.inline,
|
||||
);
|
||||
|
||||
let block_axis = solve_axis(
|
||||
cbis,
|
||||
pbm.padding_border_sums.block,
|
||||
pbm.margin.block_start,
|
||||
pbm.margin.block_end,
|
||||
/* avoid_negative_margin_start */ false,
|
||||
&shared_fragment.box_offsets.block,
|
||||
size.block,
|
||||
);
|
||||
|
||||
let margin = Sides {
|
||||
inline_start: inline_axis.margin_start,
|
||||
inline_end: inline_axis.margin_end,
|
||||
block_start: block_axis.margin_start,
|
||||
block_end: block_axis.margin_end,
|
||||
let solve_inline_axis = |computed_size| {
|
||||
solve_axis(
|
||||
cbis,
|
||||
pbm.padding_border_sums.inline,
|
||||
pbm.margin.inline_start,
|
||||
pbm.margin.inline_end,
|
||||
/* avoid_negative_margin_start */ true,
|
||||
&shared_fragment.box_offsets.inline,
|
||||
computed_size,
|
||||
)
|
||||
};
|
||||
let solve_block_axis = |computed_size| {
|
||||
solve_axis(
|
||||
// TODO(delan) shouldn’t this be cbbs?
|
||||
cbis,
|
||||
pbm.padding_border_sums.block,
|
||||
pbm.margin.block_start,
|
||||
pbm.margin.block_end,
|
||||
/* avoid_negative_margin_start */ false,
|
||||
&shared_fragment.box_offsets.block,
|
||||
computed_size,
|
||||
)
|
||||
};
|
||||
|
||||
let mut inline_axis = solve_inline_axis(computed_size.inline);
|
||||
let mut block_axis = solve_block_axis(computed_size.block);
|
||||
|
||||
let mut positioning_context =
|
||||
PositioningContext::new_for_style(absolutely_positioned_box.context.style()).unwrap();
|
||||
|
@ -482,55 +482,134 @@ impl HoistedAbsolutelyPositionedBox {
|
|||
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
|
||||
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
|
||||
let style = &replaced.style;
|
||||
content_size = size.auto_is(|| unreachable!());
|
||||
content_size = computed_size.auto_is(|| unreachable!());
|
||||
fragments = replaced
|
||||
.contents
|
||||
.make_fragments(style, content_size.clone());
|
||||
},
|
||||
IndependentFormattingContext::NonReplaced(non_replaced) => {
|
||||
// https://drafts.csswg.org/css2/#min-max-widths
|
||||
// https://drafts.csswg.org/css2/#min-max-heights
|
||||
let min_size = non_replaced
|
||||
.style
|
||||
.content_min_box_size(&containing_block.into(), &pbm)
|
||||
.auto_is(|| Length::zero());
|
||||
let max_size = non_replaced
|
||||
.style
|
||||
.content_max_box_size(&containing_block.into(), &pbm);
|
||||
|
||||
// https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width
|
||||
// https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height
|
||||
let inline_size = inline_axis.size.auto_is(|| {
|
||||
let mut inline_size = inline_axis.size.auto_is(|| {
|
||||
let anchor = match inline_axis.anchor {
|
||||
Anchor::Start(start) => start,
|
||||
Anchor::End(end) => end,
|
||||
};
|
||||
let margin_sum = inline_axis.margin_start + inline_axis.margin_end;
|
||||
let available_size =
|
||||
cbis - anchor - pbm.padding_border_sums.inline - margin.inline_sum();
|
||||
cbis - anchor - pbm.padding_border_sums.inline - margin_sum;
|
||||
non_replaced
|
||||
.inline_content_sizes(layout_context)
|
||||
.shrink_to_fit(available_size)
|
||||
});
|
||||
|
||||
let containing_block_for_children = ContainingBlock {
|
||||
inline_size,
|
||||
block_size: block_axis.size,
|
||||
style: &non_replaced.style,
|
||||
};
|
||||
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
|
||||
assert_eq!(
|
||||
containing_block.style.writing_mode,
|
||||
containing_block_for_children.style.writing_mode,
|
||||
"Mixed writing modes are not supported yet"
|
||||
);
|
||||
let dummy_tree_rank = 0;
|
||||
let independent_layout = non_replaced.layout(
|
||||
layout_context,
|
||||
&mut positioning_context,
|
||||
&containing_block_for_children,
|
||||
dummy_tree_rank,
|
||||
);
|
||||
// If the tentative used inline size is greater than ‘max-inline-size’,
|
||||
// recalculate the inline size and margins with ‘max-inline-size’ as the
|
||||
// computed ‘inline-size’. We can assume the new inline size won’t be ‘auto’,
|
||||
// because a non-‘auto’ computed ‘inline-size’ always becomes the used value.
|
||||
// https://drafts.csswg.org/css2/#min-max-widths (step 2)
|
||||
if let Some(max) = max_size.inline {
|
||||
if inline_size > max {
|
||||
inline_axis = solve_inline_axis(LengthOrAuto::LengthPercentage(max));
|
||||
inline_size = inline_axis.size.auto_is(|| unreachable!());
|
||||
}
|
||||
}
|
||||
|
||||
content_size = Vec2 {
|
||||
inline: inline_size,
|
||||
block: block_axis
|
||||
.size
|
||||
.auto_is(|| independent_layout.content_block_size),
|
||||
// If the tentative used inline size is less than ‘min-inline-size’,
|
||||
// recalculate the inline size and margins with ‘min-inline-size’ as the
|
||||
// computed ‘inline-size’. We can assume the new inline size won’t be ‘auto’,
|
||||
// because a non-‘auto’ computed ‘inline-size’ always becomes the used value.
|
||||
// https://drafts.csswg.org/css2/#min-max-widths (step 3)
|
||||
if inline_size < min_size.inline {
|
||||
inline_axis =
|
||||
solve_inline_axis(LengthOrAuto::LengthPercentage(min_size.inline));
|
||||
inline_size = inline_axis.size.auto_is(|| unreachable!());
|
||||
}
|
||||
|
||||
struct Result {
|
||||
content_size: Vec2<CSSPixelLength>,
|
||||
fragments: Vec<Fragment>,
|
||||
}
|
||||
|
||||
// If we end up recalculating the block size and margins below, we also need
|
||||
// to relayout the children with a containing block of that size, otherwise
|
||||
// percentages may be resolved incorrectly.
|
||||
let mut try_layout = |size| {
|
||||
let containing_block_for_children = ContainingBlock {
|
||||
inline_size,
|
||||
block_size: size,
|
||||
style: &non_replaced.style,
|
||||
};
|
||||
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
|
||||
assert_eq!(
|
||||
containing_block.style.writing_mode,
|
||||
containing_block_for_children.style.writing_mode,
|
||||
"Mixed writing modes are not supported yet"
|
||||
);
|
||||
let dummy_tree_rank = 0;
|
||||
let independent_layout = non_replaced.layout(
|
||||
layout_context,
|
||||
&mut positioning_context,
|
||||
&containing_block_for_children,
|
||||
dummy_tree_rank,
|
||||
);
|
||||
let block_size = size.auto_is(|| independent_layout.content_block_size);
|
||||
Result {
|
||||
content_size: Vec2 {
|
||||
inline: inline_size,
|
||||
block: block_size,
|
||||
},
|
||||
fragments: independent_layout.fragments,
|
||||
}
|
||||
};
|
||||
fragments = independent_layout.fragments
|
||||
|
||||
let mut result = try_layout(block_axis.size);
|
||||
|
||||
// If the tentative used block size is greater than ‘max-block-size’,
|
||||
// recalculate the block size and margins with ‘max-block-size’ as the
|
||||
// computed ‘block-size’. We can assume the new block size won’t be ‘auto’,
|
||||
// because a non-‘auto’ computed ‘block-size’ always becomes the used value.
|
||||
// https://drafts.csswg.org/css2/#min-max-heights (step 2)
|
||||
if let Some(max) = max_size.block {
|
||||
if result.content_size.block > max {
|
||||
block_axis = solve_block_axis(LengthOrAuto::LengthPercentage(max));
|
||||
result = try_layout(LengthOrAuto::LengthPercentage(max));
|
||||
}
|
||||
}
|
||||
|
||||
// If the tentative used block size is less than ‘min-block-size’,
|
||||
// recalculate the block size and margins with ‘min-block-size’ as the
|
||||
// computed ‘block-size’. We can assume the new block size won’t be ‘auto’,
|
||||
// because a non-‘auto’ computed ‘block-size’ always becomes the used value.
|
||||
// https://drafts.csswg.org/css2/#min-max-heights (step 3)
|
||||
if result.content_size.block < min_size.block {
|
||||
block_axis =
|
||||
solve_block_axis(LengthOrAuto::LengthPercentage(min_size.block));
|
||||
result = try_layout(LengthOrAuto::LengthPercentage(min_size.block));
|
||||
}
|
||||
|
||||
content_size = result.content_size;
|
||||
fragments = result.fragments;
|
||||
},
|
||||
};
|
||||
|
||||
let margin = Sides {
|
||||
inline_start: inline_axis.margin_start,
|
||||
inline_end: inline_axis.margin_end,
|
||||
block_start: block_axis.margin_start,
|
||||
block_end: block_axis.margin_end,
|
||||
};
|
||||
|
||||
let pb = &pbm.padding + &pbm.border;
|
||||
let inline_start = match inline_axis.anchor {
|
||||
Anchor::Start(start) => start + pb.inline_start + margin.inline_start,
|
||||
|
@ -601,24 +680,24 @@ fn solve_axis(
|
|||
computed_margin_end: LengthOrAuto,
|
||||
avoid_negative_margin_start: bool,
|
||||
box_offsets: &AbsoluteBoxOffsets,
|
||||
size: LengthOrAuto,
|
||||
computed_size: LengthOrAuto,
|
||||
) -> AxisResult {
|
||||
match box_offsets {
|
||||
AbsoluteBoxOffsets::StaticStart { start } => AxisResult {
|
||||
anchor: Anchor::Start(*start),
|
||||
size,
|
||||
size: computed_size,
|
||||
margin_start: computed_margin_start.auto_is(Length::zero),
|
||||
margin_end: computed_margin_end.auto_is(Length::zero),
|
||||
},
|
||||
AbsoluteBoxOffsets::Start { start } => AxisResult {
|
||||
anchor: Anchor::Start(start.percentage_relative_to(containing_size)),
|
||||
size,
|
||||
size: computed_size,
|
||||
margin_start: computed_margin_start.auto_is(Length::zero),
|
||||
margin_end: computed_margin_end.auto_is(Length::zero),
|
||||
},
|
||||
AbsoluteBoxOffsets::End { end } => AxisResult {
|
||||
anchor: Anchor::End(end.percentage_relative_to(containing_size)),
|
||||
size,
|
||||
size: computed_size,
|
||||
margin_start: computed_margin_start.auto_is(Length::zero),
|
||||
margin_end: computed_margin_end.auto_is(Length::zero),
|
||||
},
|
||||
|
@ -629,7 +708,7 @@ fn solve_axis(
|
|||
let margin_start;
|
||||
let margin_end;
|
||||
let used_size;
|
||||
if let LengthOrAuto::LengthPercentage(s) = size {
|
||||
if let LengthOrAuto::LengthPercentage(s) = computed_size {
|
||||
used_size = s;
|
||||
let margins = containing_size - start - end - padding_border_sum - s;
|
||||
match (computed_margin_start, computed_margin_end) {
|
||||
|
@ -661,7 +740,9 @@ fn solve_axis(
|
|||
} else {
|
||||
margin_start = computed_margin_start.auto_is(Length::zero);
|
||||
margin_end = computed_margin_end.auto_is(Length::zero);
|
||||
// FIXME(nox): What happens if that is negative?
|
||||
|
||||
// This may be negative, but the caller will later effectively
|
||||
// clamp it to ‘min-inline-size’ or ‘min-block-size’.
|
||||
used_size =
|
||||
containing_size - start - end - padding_border_sum - margin_start - margin_end
|
||||
};
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-height-003.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-height-004.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-height-005.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-height-010.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-height-011.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-height-012.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-max-height-002.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-max-height-003.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-max-height-004.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-max-height-005.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-max-height-006.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-max-height-007.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-max-height-008.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-max-height-009.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-max-height-010.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-max-height-011.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-max-height-012.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-width-025.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[absolute-non-replaced-width-026.xht]
|
||||
expected: FAIL
|
|
@ -1,4 +0,0 @@
|
|||
[abspos-change-in-inline-block.html]
|
||||
[No crash]
|
||||
expected: FAIL
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
[abspos-width-change-inline-container-001.html]
|
||||
expected: FAIL
|
|
@ -1,4 +0,0 @@
|
|||
[detach-abspos-before-layout.html]
|
||||
[No crash or DCHECK failure]
|
||||
expected: FAIL
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
[relpos-percentage-left-in-scrollable-2.html]
|
||||
expected: ERROR
|
|
@ -1,2 +0,0 @@
|
|||
[relpos-percentage-left-in-scrollable.html]
|
||||
expected: ERROR
|
|
@ -1,2 +1,3 @@
|
|||
[relpos-percentage-top-in-scrollable.html]
|
||||
expected: ERROR
|
||||
[Top percentage resolved correctly for overflow contribution]
|
||||
expected: FAIL
|
||||
|
|
|
@ -91426,6 +91426,19 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"absolute-non-replaced-max-001.html": [
|
||||
"4b208c5831e1b36a3c503129562175457ef41c0b",
|
||||
[
|
||||
null,
|
||||
[
|
||||
[
|
||||
"/css/CSS2/positioning/absolute-non-replaced-max-001-ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
],
|
||||
"absolute-non-replaced-max-height-002.xht": [
|
||||
"7637068a9c4a2cc584acfa329d5aabb09e7101ab",
|
||||
[
|
||||
|
@ -91569,6 +91582,32 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"absolute-non-replaced-min-001.html": [
|
||||
"0e6d17661e39beb872f886eba06acf47be9a931c",
|
||||
[
|
||||
null,
|
||||
[
|
||||
[
|
||||
"/css/CSS2/positioning/absolute-non-replaced-min-max-001-ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
],
|
||||
"absolute-non-replaced-min-max-001.html": [
|
||||
"2e240b7509adc3bd8cb9cae67165e0dc42296e07",
|
||||
[
|
||||
null,
|
||||
[
|
||||
[
|
||||
"/css/CSS2/positioning/absolute-non-replaced-min-max-001-ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
],
|
||||
"absolute-non-replaced-width-001.xht": [
|
||||
"6d2b8a33a0db793d9e52e94b33aa1c95884a2dbe",
|
||||
[
|
||||
|
@ -359766,6 +359805,10 @@
|
|||
"51ec44e9cf55daa92b0822590933275a6706b9aa",
|
||||
[]
|
||||
],
|
||||
"absolute-non-replaced-max-001-ref.html": [
|
||||
"742b3063abd693a543ca4ce88bda161ae04d8668",
|
||||
[]
|
||||
],
|
||||
"absolute-non-replaced-max-height-002-ref.xht": [
|
||||
"75b0e54b442a868bfd5b86c671cbb8b67211c800",
|
||||
[]
|
||||
|
@ -359786,6 +359829,10 @@
|
|||
"aea00aaa43cbb10e905796731278e68f1033e428",
|
||||
[]
|
||||
],
|
||||
"absolute-non-replaced-min-max-001-ref.html": [
|
||||
"3bbba2be908c099806ffdaaca440cf4513e1e92b",
|
||||
[]
|
||||
],
|
||||
"absolute-non-replaced-width-002-ref.xht": [
|
||||
"e88ac8098391299e2e106a831fb02f22e7eff9c8",
|
||||
[]
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[absolute-non-replaced-max-001.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[absolute-non-replaced-min-001.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,7 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<link rel="author" name="Delan Azabani" href="mailto:dazabani@igalia.com">
|
||||
<p>Test passes if there is a green square containing a smaller black square below.
|
||||
<div style="width: 2em; height: 2em; background: green;">
|
||||
<div style="width: 1em; height: 1em; background: black;"></div>
|
||||
</div>
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Test: ‘max-width’ and ‘max-height’ affect percentage sizes in descendants</title>
|
||||
<link rel="author" name="Delan Azabani" href="mailto:dazabani@igalia.com">
|
||||
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-widths">
|
||||
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-heights">
|
||||
<link rel="match" href="absolute-non-replaced-max-001-ref.html">
|
||||
<meta name="assert" value="This test verifies that when the used ‘width’ and ‘height’ are affected by ‘max-width’ and ‘max-height’, the descendants are laid out with a containing block of the new size, so any percentage sizes are resolved against that new size.">
|
||||
<p>Test passes if there is a green square containing a smaller black square below.
|
||||
<div style="position: absolute; width: 4em; height: 4em; max-width: 2em; max-height: 2em; background: green;">
|
||||
<div style="width: 50%; height: 50%; background: black;"></div>
|
||||
</div>
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Test: ‘min-width’ and ‘min-height’ affect percentage sizes in descendants</title>
|
||||
<link rel="author" name="Delan Azabani" href="mailto:dazabani@igalia.com">
|
||||
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-widths">
|
||||
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-heights">
|
||||
<link rel="match" href="absolute-non-replaced-min-max-001-ref.html">
|
||||
<meta name="assert" value="This test verifies that when the used ‘width’ and ‘height’ are affected by ‘min-width’ and ‘min-height’, the descendants are laid out with a containing block of the new size, so any percentage sizes are resolved against that new size.">
|
||||
<p>Test passes if there is a green square below.
|
||||
<div style="position: absolute; width: 0; height: 0; min-width: 1em; min-height: 1em; background: black;">
|
||||
<div style="width: 100%; height: 100%; background: green;"></div>
|
||||
</div>
|
|
@ -0,0 +1,5 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<link rel="author" name="Delan Azabani" href="mailto:dazabani@igalia.com">
|
||||
<p>Test passes if there is a green square below.
|
||||
<div style="width: 1em; height: 1em; background: green;"></div>
|
|
@ -0,0 +1,10 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Test: ‘min-width’ and ‘min-height’ take precedence over ‘max-width’ and ‘max-height’ when contradictory</title>
|
||||
<link rel="author" name="Delan Azabani" href="mailto:dazabani@igalia.com">
|
||||
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-widths">
|
||||
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-heights">
|
||||
<link rel="match" href="absolute-non-replaced-min-max-001-ref.html">
|
||||
<meta name="assert" value="This test verifies that when ‘min-width’ is greater than ‘max-width’, or ‘min-height’ is greater than ‘max-height’, the minimums take precedence, because the steps that recalculate sizes and margins for the minimums (step 3) come after the steps for the maximums (step 2).">
|
||||
<p>Test passes if there is a green square below.
|
||||
<div style="position: absolute; width: 0; height: 0; min-width: 1em; min-height: 1em; max-width: 0; max-height: 0; background: green;"></div>
|
Loading…
Add table
Add a link
Reference in a new issue