Fix ordinary inline used sizing
This commit is contained in:
1 parent
42d3c1191b
commit
61d61feb50
6 files changed
+335
-12
No files matched your search
@@ -2214,6 +2214,98 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "render")]
|
||||||
|
#[test]
|
||||||
|
fn ordinary_inline_keeps_computed_sizes_but_uses_content_geometry() {
|
||||||
|
let dom = parse_html(
|
||||||
|
r#"<html><head><style>
|
||||||
|
html,body,p { margin:0 }
|
||||||
|
#host { width:300px; font-size:16px; line-height:20px }
|
||||||
|
#token {
|
||||||
|
position:relative;
|
||||||
|
width:100%; height:100px;
|
||||||
|
min-width:100%; min-height:100px;
|
||||||
|
max-width:100%; max-height:100px;
|
||||||
|
padding:0 5px; background:red
|
||||||
|
}
|
||||||
|
#after { position:relative }
|
||||||
|
#atomic {
|
||||||
|
display:inline-block; box-sizing:border-box;
|
||||||
|
width:80px; height:30px; padding:0; border:0
|
||||||
|
}
|
||||||
|
#replaced {
|
||||||
|
display:inline; box-sizing:border-box;
|
||||||
|
width:80px; height:30px; min-width:0; min-height:0;
|
||||||
|
max-width:none; max-height:none; padding:0; border:0
|
||||||
|
}
|
||||||
|
#items { display:flex }
|
||||||
|
#item {
|
||||||
|
display:inline; box-sizing:border-box; flex:none;
|
||||||
|
width:90px; height:25px; padding:0; border:0
|
||||||
|
}
|
||||||
|
</style></head><body>
|
||||||
|
<p id="host">A <code id="token">token</code> <span id="after">after</span></p>
|
||||||
|
<span id="atomic"></span>
|
||||||
|
<input id="replaced">
|
||||||
|
<div id="items"><span id="item"></span></div>
|
||||||
|
</body></html>"#,
|
||||||
|
);
|
||||||
|
let mut rt = ObscuraJsRuntime::new();
|
||||||
|
rt.set_dom(dom);
|
||||||
|
rt.set_viewport(400.0, 240.0);
|
||||||
|
rt.run_page_init();
|
||||||
|
|
||||||
|
let result = rt
|
||||||
|
.evaluate(
|
||||||
|
r#"
|
||||||
|
const token = document.getElementById("token");
|
||||||
|
const after = document.getElementById("after");
|
||||||
|
const computed = getComputedStyle(token);
|
||||||
|
const rect = token.getBoundingClientRect();
|
||||||
|
const afterRect = after.getBoundingClientRect();
|
||||||
|
const atomic = document.getElementById("atomic").getBoundingClientRect();
|
||||||
|
const replaced = document.getElementById("replaced").getBoundingClientRect();
|
||||||
|
const item = document.getElementById("item").getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
computed: [
|
||||||
|
computed.width, computed.height,
|
||||||
|
computed.minWidth, computed.minHeight,
|
||||||
|
computed.maxWidth, computed.maxHeight
|
||||||
|
],
|
||||||
|
rect: [rect.x, rect.y, rect.width, rect.height],
|
||||||
|
after: [afterRect.x, afterRect.y],
|
||||||
|
client: [token.clientWidth, token.clientHeight],
|
||||||
|
atomic: [atomic.width, atomic.height],
|
||||||
|
replaced: [replaced.width, replaced.height],
|
||||||
|
item: [item.width, item.height, getComputedStyle(item).display]
|
||||||
|
};
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result["computed"],
|
||||||
|
serde_json::json!(["100%", "100px", "100%", "100px", "100%", "100px"])
|
||||||
|
);
|
||||||
|
let rect = result["rect"].as_array().unwrap();
|
||||||
|
let token_x = rect[0].as_f64().unwrap();
|
||||||
|
let token_y = rect[1].as_f64().unwrap();
|
||||||
|
let token_width = rect[2].as_f64().unwrap();
|
||||||
|
let token_height = rect[3].as_f64().unwrap();
|
||||||
|
assert!(
|
||||||
|
token_width > 20.0 && token_width < 100.0,
|
||||||
|
"ordinary inline should hug text and padding: {rect:?}"
|
||||||
|
);
|
||||||
|
assert!(token_height < 40.0, "ignored block size leaked into geometry");
|
||||||
|
assert_eq!(result["client"], serde_json::json!([0, 0]));
|
||||||
|
let after = result["after"].as_array().unwrap();
|
||||||
|
assert!(after[0].as_f64().unwrap() >= token_x + token_width - 0.01);
|
||||||
|
assert!((after[1].as_f64().unwrap() - token_y).abs() < 0.01);
|
||||||
|
assert_eq!(result["atomic"], serde_json::json!([80, 30]));
|
||||||
|
assert_eq!(result["replaced"], serde_json::json!([80, 30]));
|
||||||
|
assert_eq!(result["item"], serde_json::json!([90, 25, "block"]));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
#[test]
|
#[test]
|
||||||
fn computed_style_uses_renderer_stylesheet_cascade_and_invalidates() {
|
fn computed_style_uses_renderer_stylesheet_cascade_and_invalidates() {
|
||||||
|
|||||||
@@ -894,6 +894,7 @@ fn resolve_atomic_percentage_heights(
|
|||||||
.is_some_and(|node| node.parent == Some(parent_id));
|
.is_some_and(|node| node.parent == Some(parent_id));
|
||||||
let child_percent = styles.get(&dom_id).and_then(|style| {
|
let child_percent = styles.get(&dom_id).and_then(|style| {
|
||||||
(style.size_expressions[1].is_none()
|
(style.size_expressions[1].is_none()
|
||||||
|
&& !style.ignores_used_box_sizes()
|
||||||
&& !matches!(style.position, Some(taffy::Position::Absolute)))
|
&& !matches!(style.position, Some(taffy::Position::Absolute)))
|
||||||
.then_some(style.height)
|
.then_some(style.height)
|
||||||
.and_then(|height| match height {
|
.and_then(|height| match height {
|
||||||
@@ -1381,6 +1382,7 @@ fn cascade_walk(
|
|||||||
.filter(|value| value.is_finite() && *value >= 0.0);
|
.filter(|value| value.is_finite() && *value >= 0.0);
|
||||||
}
|
}
|
||||||
let mut style = crate::style::ua_style(elem.local.as_ref());
|
let mut style = crate::style::ua_style(elem.local.as_ref());
|
||||||
|
style.is_replaced_box = crate::inline::is_replaced(elem.local.as_ref());
|
||||||
style.color_scheme_dark = inherited_color_scheme_dark;
|
style.color_scheme_dark = inherited_color_scheme_dark;
|
||||||
if matches!(elem.local.as_ref(), "td" | "th") {
|
if matches!(elem.local.as_ref(), "td" | "th") {
|
||||||
if let Some(padding) = inherited_cell_padding {
|
if let Some(padding) = inherited_cell_padding {
|
||||||
@@ -1480,7 +1482,8 @@ fn cascade_walk(
|
|||||||
this_props = std::rc::Rc::new(m);
|
this_props = std::rc::Rc::new(m);
|
||||||
}
|
}
|
||||||
custom_properties.insert(id, this_props.clone());
|
custom_properties.insert(id, this_props.clone());
|
||||||
let (before_pseudo, after_pseudo) =
|
style.is_replaced_box |= style.content_image.is_some();
|
||||||
|
let (mut before_pseudo, mut after_pseudo) =
|
||||||
if let Some(evaluator) = container_evaluator.as_deref_mut() {
|
if let Some(evaluator) = container_evaluator.as_deref_mut() {
|
||||||
sheet.pseudo_styles_with_container_queries(
|
sheet.pseudo_styles_with_container_queries(
|
||||||
tree,
|
tree,
|
||||||
@@ -1493,6 +1496,12 @@ fn cascade_walk(
|
|||||||
} else {
|
} else {
|
||||||
sheet.pseudo_styles(tree, matcher, id, &this_props, &style)
|
sheet.pseudo_styles(tree, matcher, id, &this_props, &style)
|
||||||
};
|
};
|
||||||
|
for pseudo in [&mut before_pseudo, &mut after_pseudo]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
|
pseudo.is_replaced_box = pseudo.content_image.is_some();
|
||||||
|
}
|
||||||
style.before_content = before_pseudo
|
style.before_content = before_pseudo
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.filter(|pseudo| pseudo.position != Some(taffy::Position::Absolute))
|
.filter(|pseudo| pseudo.position != Some(taffy::Position::Absolute))
|
||||||
@@ -2073,6 +2082,7 @@ fn layout_dom_once(
|
|||||||
opacity_product: f32,
|
opacity_product: f32,
|
||||||
list_style: crate::ListStyle,
|
list_style: crate::ListStyle,
|
||||||
line_height: crate::LineHeight,
|
line_height: crate::LineHeight,
|
||||||
|
white_space: crate::WhiteSpace,
|
||||||
text_wrap_style: crate::TextWrapStyle,
|
text_wrap_style: crate::TextWrapStyle,
|
||||||
text_transform: crate::TextTransform,
|
text_transform: crate::TextTransform,
|
||||||
italic: bool,
|
italic: bool,
|
||||||
@@ -2121,6 +2131,7 @@ fn layout_dom_once(
|
|||||||
// CSS initial value of list-style-type.
|
// CSS initial value of list-style-type.
|
||||||
list_style: crate::ListStyle::Disc,
|
list_style: crate::ListStyle::Disc,
|
||||||
line_height: crate::LineHeight::Normal,
|
line_height: crate::LineHeight::Normal,
|
||||||
|
white_space: crate::WhiteSpace::Normal,
|
||||||
text_wrap_style: crate::TextWrapStyle::Auto,
|
text_wrap_style: crate::TextWrapStyle::Auto,
|
||||||
text_transform: crate::TextTransform::None,
|
text_transform: crate::TextTransform::None,
|
||||||
italic: false,
|
italic: false,
|
||||||
@@ -2501,6 +2512,7 @@ fn layout_dom_once(
|
|||||||
inh.visibility_hidden || inh.opacity_product <= 0.0;
|
inh.visibility_hidden || inh.opacity_product <= 0.0;
|
||||||
match style.list_style { Some(v) => inh.list_style = v, None => style.list_style = Some(inh.list_style) }
|
match style.list_style { Some(v) => inh.list_style = v, None => style.list_style = Some(inh.list_style) }
|
||||||
match style.line_height { Some(v) => inh.line_height = v, None => style.line_height = Some(inh.line_height) }
|
match style.line_height { Some(v) => inh.line_height = v, None => style.line_height = Some(inh.line_height) }
|
||||||
|
match style.white_space { Some(v) => inh.white_space = v, None => style.white_space = Some(inh.white_space) }
|
||||||
match style.text_wrap_style { Some(v) => inh.text_wrap_style = v, None => style.text_wrap_style = Some(inh.text_wrap_style) }
|
match style.text_wrap_style { Some(v) => inh.text_wrap_style = v, None => style.text_wrap_style = Some(inh.text_wrap_style) }
|
||||||
match style.text_transform { Some(v) => inh.text_transform = v, None => style.text_transform = Some(inh.text_transform) }
|
match style.text_transform { Some(v) => inh.text_transform = v, None => style.text_transform = Some(inh.text_transform) }
|
||||||
match style.font_style_italic { Some(v) => inh.italic = v, None => style.font_style_italic = Some(inh.italic) }
|
match style.font_style_italic { Some(v) => inh.italic = v, None => style.font_style_italic = Some(inh.italic) }
|
||||||
@@ -2621,6 +2633,7 @@ fn layout_dom_once(
|
|||||||
let host_optical_sizing = style.font_optical_sizing;
|
let host_optical_sizing = style.font_optical_sizing;
|
||||||
let host_variation_settings = style.font_variation_settings.clone();
|
let host_variation_settings = style.font_variation_settings.clone();
|
||||||
let host_line_height = style.line_height;
|
let host_line_height = style.line_height;
|
||||||
|
let host_white_space = style.white_space;
|
||||||
let host_text_wrap_style = style.text_wrap_style;
|
let host_text_wrap_style = style.text_wrap_style;
|
||||||
let host_transform = style.text_transform;
|
let host_transform = style.text_transform;
|
||||||
let host_italic = style.font_style_italic;
|
let host_italic = style.font_style_italic;
|
||||||
@@ -2835,6 +2848,9 @@ fn layout_dom_once(
|
|||||||
} else if pseudo.line_height.is_none() {
|
} else if pseudo.line_height.is_none() {
|
||||||
pseudo.line_height = host_line_height;
|
pseudo.line_height = host_line_height;
|
||||||
}
|
}
|
||||||
|
if pseudo.white_space.is_none() {
|
||||||
|
pseudo.white_space = host_white_space;
|
||||||
|
}
|
||||||
if pseudo.text_wrap_style.is_none() {
|
if pseudo.text_wrap_style.is_none() {
|
||||||
pseudo.text_wrap_style = host_text_wrap_style;
|
pseudo.text_wrap_style = host_text_wrap_style;
|
||||||
}
|
}
|
||||||
@@ -3210,6 +3226,7 @@ fn layout_dom_once(
|
|||||||
crate::blockify_outer_display(style);
|
crate::blockify_outer_display(style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
blockify_generated_pseudos(&mut styles);
|
||||||
|
|
||||||
// The initial containing block is modelled by the root taffy node's
|
// The initial containing block is modelled by the root taffy node's
|
||||||
// definite viewport height (set at build), which is what `html,body{
|
// definite viewport height (set at build), which is what `html,body{
|
||||||
@@ -5671,6 +5688,30 @@ fn blockify_layout_children(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generated `::before`/`::after` boxes participate in the same display
|
||||||
|
/// fixups as real elements. They are stored inside their host style rather
|
||||||
|
/// than in the DOM map, so the normal root/item/float/positioned loops above
|
||||||
|
/// cannot reach them.
|
||||||
|
fn blockify_generated_pseudos(styles: &mut HashMap<NodeId, crate::LayoutStyle>) {
|
||||||
|
for host in styles.values_mut() {
|
||||||
|
let host_is_item_container = matches!(
|
||||||
|
host.display,
|
||||||
|
crate::Display::Flex | crate::Display::Grid
|
||||||
|
) && !host.internal_flex_container;
|
||||||
|
for pseudo in [host.before_pseudo.as_mut(), host.after_pseudo.as_mut()]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
|
if host_is_item_container
|
||||||
|
|| matches!(pseudo.position, Some(taffy::Position::Absolute))
|
||||||
|
|| pseudo.float.is_some()
|
||||||
|
{
|
||||||
|
crate::blockify_outer_display(pseudo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Build whichever of an element or a text node `id` is, returning every
|
/// Build whichever of an element or a text node `id` is, returning every
|
||||||
/// taffy node it produced (usually one, but a text node fans out to one leaf
|
/// taffy node it produced (usually one, but a text node fans out to one leaf
|
||||||
/// per word; see `build_text_words`). Callers collecting a container's
|
/// per word; see `build_text_words`). Callers collecting a container's
|
||||||
@@ -5914,8 +5955,6 @@ fn is_flattenable_inline(tree: &DomTree, id: NodeId, styles: &HashMap<NodeId, cr
|
|||||||
&& style.background_image.is_none()
|
&& style.background_image.is_none()
|
||||||
&& style.mask_image.is_none()
|
&& style.mask_image.is_none()
|
||||||
&& style.border == crate::Edges::default()
|
&& style.border == crate::Edges::default()
|
||||||
&& style.width == crate::Dimension::Auto
|
|
||||||
&& style.height == crate::Dimension::Auto
|
|
||||||
&& style.position.is_none()
|
&& style.position.is_none()
|
||||||
&& !style.overflow_hidden
|
&& !style.overflow_hidden
|
||||||
&& style.float.is_none()
|
&& style.float.is_none()
|
||||||
@@ -6085,12 +6124,6 @@ fn pseudo_requires_generated_box(style: &crate::LayoutStyle, content: Option<&st
|
|||||||
|| style.display != crate::Display::Inline
|
|| style.display != crate::Display::Inline
|
||||||
|| style.is_inline_block
|
|| style.is_inline_block
|
||||||
|| style.position.is_some()
|
|| style.position.is_some()
|
||||||
|| style.width != crate::Dimension::Auto
|
|
||||||
|| style.height != crate::Dimension::Auto
|
|
||||||
|| style.min_width != crate::Dimension::Auto
|
|
||||||
|| style.min_height != crate::Dimension::Auto
|
|
||||||
|| style.max_width != crate::Dimension::Auto
|
|
||||||
|| style.max_height != crate::Dimension::Auto
|
|
||||||
|| style.margin != crate::Edges::default()
|
|| style.margin != crate::Edges::default()
|
||||||
|| style.margin_auto.iter().any(|value| *value)
|
|| style.margin_auto.iter().any(|value| *value)
|
||||||
|| style.padding != crate::Edges::default()
|
|| style.padding != crate::Edges::default()
|
||||||
@@ -6374,7 +6407,7 @@ where
|
|||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(&node, &dom)| {
|
.filter_map(|(&node, &dom)| {
|
||||||
let style = styles.get(&dom)?;
|
let style = styles.get(&dom)?;
|
||||||
if !style.width_fit_content {
|
if !style.width_fit_content || style.ignores_used_box_sizes() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let layout = taffy_tree.layout(node).ok()?;
|
let layout = taffy_tree.layout(node).ok()?;
|
||||||
@@ -7017,6 +7050,7 @@ fn defer_cyclic_flex_inline_sizes(
|
|||||||
) -> Vec<DeferredCyclicInlineSize> {
|
) -> Vec<DeferredCyclicInlineSize> {
|
||||||
let candidates: Vec<(NodeId, usize, String)> = styles
|
let candidates: Vec<(NodeId, usize, String)> = styles
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|(_, style)| !style.ignores_used_box_sizes())
|
||||||
.flat_map(|(&id, style)| {
|
.flat_map(|(&id, style)| {
|
||||||
[0usize, 2, 4].into_iter().filter_map(move |slot| {
|
[0usize, 2, 4].into_iter().filter_map(move |slot| {
|
||||||
style.size_expressions[slot]
|
style.size_expressions[slot]
|
||||||
@@ -10833,6 +10867,124 @@ mod tests {
|
|||||||
assert_eq!(width("border-cell"), 100.0);
|
assert_eq!(width("border-cell"), 100.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn white_space_inherits_into_a_block_descendant_inline_context() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"<style>
|
||||||
|
html, body, pre { margin:0 }
|
||||||
|
pre { width:120px; white-space:pre; font-size:16px; line-height:20px }
|
||||||
|
code { display:flex; flex-direction:column }
|
||||||
|
.line { display:block }
|
||||||
|
#normal { white-space:normal }
|
||||||
|
#inherited::before { content:""; display:none }
|
||||||
|
</style>
|
||||||
|
<pre><code>
|
||||||
|
<span id="inherited" class="line">alpha beta gamma delta</span>
|
||||||
|
<span id="normal" class="line">alpha beta gamma delta</span>
|
||||||
|
</code></pre>"#,
|
||||||
|
);
|
||||||
|
let laid = layout_dom(&tree, (400.0, 200.0));
|
||||||
|
let inherited = tree.get_element_by_id("inherited").unwrap();
|
||||||
|
let normal = tree.get_element_by_id("normal").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
laid.styles[&inherited].white_space,
|
||||||
|
Some(crate::WhiteSpace::Pre),
|
||||||
|
"a block descendant must inherit the preformatted wrapping mode"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
laid.styles[&normal].white_space,
|
||||||
|
Some(crate::WhiteSpace::Normal),
|
||||||
|
"an explicit descendant value must override inherited pre"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
laid.styles[&inherited]
|
||||||
|
.before_pseudo
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.white_space,
|
||||||
|
Some(crate::WhiteSpace::Pre),
|
||||||
|
"a pseudo-element must inherit the originating element's computed wrapping mode"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
laid.rects[&inherited].height, 20.0,
|
||||||
|
"the inherited pre line must overflow horizontally without wrapping"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
laid.rects[&normal].height > 20.0,
|
||||||
|
"the explicit normal line should still wrap at the containing width"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generated_inline_boxes_apply_sizes_only_after_display_blockification() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"<style>
|
||||||
|
html,body { margin:0 }
|
||||||
|
#flex { display:flex }
|
||||||
|
#flex::before {
|
||||||
|
content:""; display:inline;
|
||||||
|
width:100px; height:70px;
|
||||||
|
min-width:250px; min-height:120px;
|
||||||
|
max-width:50px; max-height:40px;
|
||||||
|
background:red
|
||||||
|
}
|
||||||
|
#float::before {
|
||||||
|
content:""; display:inline; float:left;
|
||||||
|
width:80px; height:40px; background:blue
|
||||||
|
}
|
||||||
|
#relative::before {
|
||||||
|
content:""; display:inline; position:relative;
|
||||||
|
width:80px; height:40px; background:green
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="flex"></div>
|
||||||
|
<div id="float"></div>
|
||||||
|
<div id="relative"></div>"#,
|
||||||
|
);
|
||||||
|
let laid = layout_dom(&tree, (500.0, 300.0));
|
||||||
|
let flex = tree.get_element_by_id("flex").unwrap();
|
||||||
|
let float = tree.get_element_by_id("float").unwrap();
|
||||||
|
let relative = tree.get_element_by_id("relative").unwrap();
|
||||||
|
|
||||||
|
let pseudo = |host| {
|
||||||
|
laid.generated_boxes
|
||||||
|
.iter()
|
||||||
|
.find(|generated| {
|
||||||
|
generated.host == host && generated.kind == GeneratedBoxKind::Before
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.rect
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
laid.styles[&flex].before_pseudo.as_ref().unwrap().display,
|
||||||
|
crate::Display::Block,
|
||||||
|
"a generated flex item must be blockified"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
laid.styles[&float].before_pseudo.as_ref().unwrap().display,
|
||||||
|
crate::Display::Block,
|
||||||
|
"a floated generated box must be blockified"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
laid.styles[&relative]
|
||||||
|
.before_pseudo
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.display,
|
||||||
|
crate::Display::Inline,
|
||||||
|
"relative positioning does not make an ordinary inline atomic"
|
||||||
|
);
|
||||||
|
assert_eq!(pseudo(flex).width, 250.0);
|
||||||
|
assert_eq!(pseudo(flex).height, 120.0);
|
||||||
|
assert_eq!(pseudo(float).width, 80.0);
|
||||||
|
assert_eq!(pseudo(float).height, 40.0);
|
||||||
|
assert!(
|
||||||
|
pseudo(relative).width < 80.0 && pseudo(relative).height < 40.0,
|
||||||
|
"an ordinary inline pseudo must ignore authored box sizes"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn positioned_pseudo_inherits_the_hosts_computed_font_metrics() {
|
fn positioned_pseudo_inherits_the_hosts_computed_font_metrics() {
|
||||||
let tree = parse_html(
|
let tree = parse_html(
|
||||||
|
|||||||
@@ -1812,7 +1812,7 @@ fn is_pure_text_ifc(
|
|||||||
/// contains one is never a pure-text IFC (it keeps the general build path,
|
/// contains one is never a pure-text IFC (it keeps the general build path,
|
||||||
/// where the element gets a real taffy box). This is by tag, not display, so a
|
/// where the element gets a real taffy box). This is by tag, not display, so a
|
||||||
/// stylesheet setting `img{display:inline}` cannot trick us into folding it.
|
/// stylesheet setting `img{display:inline}` cannot trick us into folding it.
|
||||||
fn is_replaced(local: &str) -> bool {
|
pub(crate) fn is_replaced(local: &str) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
local,
|
local,
|
||||||
"img" | "svg" | "canvas" | "video" | "audio" | "iframe" | "embed"
|
"img" | "svg" | "canvas" | "video" | "audio" | "iframe" | "embed"
|
||||||
|
|||||||
@@ -512,6 +512,14 @@ pub struct LayoutStyle {
|
|||||||
/// (`<img>` or its selected `<picture><source>`), rather than authored
|
/// (`<img>` or its selected `<picture><source>`), rather than authored
|
||||||
/// CSS. A decoded image's natural ratio replaces this provisional ratio.
|
/// CSS. A decoded image's natural ratio replaces this provisional ratio.
|
||||||
pub aspect_ratio_is_mapped: bool,
|
pub aspect_ratio_is_mapped: bool,
|
||||||
|
/// Whether this element generates a replaced box.
|
||||||
|
///
|
||||||
|
/// Replaced elements with `display:inline` are atomic and therefore keep
|
||||||
|
/// authored width/height/min/max sizing. Ordinary inline boxes compute
|
||||||
|
/// those properties but ignore them for used layout. This provenance
|
||||||
|
/// cannot be inferred from intrinsic dimensions because an unloaded or
|
||||||
|
/// broken image remains replaced.
|
||||||
|
pub(crate) is_replaced_box: bool,
|
||||||
pub margin: Edges,
|
pub margin: Edges,
|
||||||
/// Which margin sides are `auto` (top, right, bottom, left). `margin: 0
|
/// Which margin sides are `auto` (top, right, bottom, left). `margin: 0
|
||||||
/// auto` / `margin-inline: auto` centering needs a real Auto margin, which
|
/// auto` / `margin-inline: auto` centering needs a real Auto margin, which
|
||||||
@@ -994,6 +1002,13 @@ pub(crate) const CB_TRIGGER_CONTENT_VISIBILITY: u16 = 1 << 6;
|
|||||||
pub(crate) const CB_TRIGGER_TRANSLATE: u16 = 1 << 7;
|
pub(crate) const CB_TRIGGER_TRANSLATE: u16 = 1 << 7;
|
||||||
|
|
||||||
impl LayoutStyle {
|
impl LayoutStyle {
|
||||||
|
/// Whether CSS box sizes compute normally but do not apply to this box's
|
||||||
|
/// used geometry. The display value here is post-blockification, so roots,
|
||||||
|
/// flex/grid items, floats, and out-of-flow boxes retain applicable sizes.
|
||||||
|
pub(crate) fn ignores_used_box_sizes(&self) -> bool {
|
||||||
|
self.display == Display::Inline && !self.is_inline_block && !self.is_replaced_box
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn establishes_positioning_containing_block(&self) -> bool {
|
pub(crate) fn establishes_positioning_containing_block(&self) -> bool {
|
||||||
self.containing_block_triggers != 0
|
self.containing_block_triggers != 0
|
||||||
}
|
}
|
||||||
@@ -1347,6 +1362,21 @@ pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style {
|
|||||||
s.aspect_ratio = Some(ar);
|
s.aspect_ratio = Some(ar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if style.ignores_used_box_sizes() {
|
||||||
|
s.size = taffy::Size {
|
||||||
|
width: taffy::Dimension::auto(),
|
||||||
|
height: taffy::Dimension::auto(),
|
||||||
|
};
|
||||||
|
s.min_size = taffy::Size {
|
||||||
|
width: taffy::Dimension::auto(),
|
||||||
|
height: taffy::Dimension::auto(),
|
||||||
|
};
|
||||||
|
s.max_size = taffy::Size {
|
||||||
|
width: taffy::Dimension::auto(),
|
||||||
|
height: taffy::Dimension::auto(),
|
||||||
|
};
|
||||||
|
s.aspect_ratio = None;
|
||||||
|
}
|
||||||
if style.display != Display::Block {
|
if style.display != Display::Block {
|
||||||
if let Some(ai) = style.align_items {
|
if let Some(ai) = style.align_items {
|
||||||
s.align_items = Some(ai);
|
s.align_items = Some(ai);
|
||||||
|
|||||||
@@ -391,6 +391,9 @@ impl PreparedRender {
|
|||||||
) -> Option<(f32, f32)> {
|
) -> Option<(f32, f32)> {
|
||||||
let rect = self.layout.rects.get(&id)?;
|
let rect = self.layout.rects.get(&id)?;
|
||||||
let style = self.layout.styles.get(&id)?;
|
let style = self.layout.styles.get(&id)?;
|
||||||
|
if style.ignores_used_box_sizes() {
|
||||||
|
return Some((0.0, 0.0));
|
||||||
|
}
|
||||||
Some((
|
Some((
|
||||||
(rect.width - style.border.left - style.border.right).max(0.0),
|
(rect.width - style.border.left - style.border.right).max(0.0),
|
||||||
(rect.height - style.border.top - style.border.bottom).max(0.0),
|
(rect.height - style.border.top - style.border.bottom).max(0.0),
|
||||||
@@ -482,7 +485,10 @@ impl PreparedRender {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(rect) = rect {
|
if style.ignores_used_box_sizes() {
|
||||||
|
out.insert("width", dimension_css(style.width, "auto"));
|
||||||
|
out.insert("height", dimension_css(style.height, "auto"));
|
||||||
|
} else if let Some(rect) = rect {
|
||||||
let horizontal_non_content =
|
let horizontal_non_content =
|
||||||
style.border.left + style.border.right + style.padding.left + style.padding.right;
|
style.border.left + style.border.right + style.padding.left + style.padding.right;
|
||||||
let vertical_non_content =
|
let vertical_non_content =
|
||||||
|
|||||||
@@ -1272,6 +1272,49 @@ fn text_alignment_does_not_shrink_block_children_or_wrap_inline_block_rows() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_replaced_inline_ignores_used_width_height_and_min_max_sizes() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"<style>
|
||||||
|
html,body,p{margin:0}
|
||||||
|
#host{width:300px;font-size:16px;line-height:20px}
|
||||||
|
#token{
|
||||||
|
position:relative;
|
||||||
|
width:100%;height:100px;
|
||||||
|
min-width:100%;min-height:100px;
|
||||||
|
max-width:100%;max-height:100px;
|
||||||
|
padding:0 5px;
|
||||||
|
background:red
|
||||||
|
}
|
||||||
|
#after{position:relative;background:blue}
|
||||||
|
</style>
|
||||||
|
<p id="host">A <code id="token">token</code> <span id="after">after</span></p>"#,
|
||||||
|
);
|
||||||
|
let laid = layout_dom(&tree, (400.0, 200.0));
|
||||||
|
let token_id = tree.get_element_by_id("token").unwrap();
|
||||||
|
let after_id = tree.get_element_by_id("after").unwrap();
|
||||||
|
let token = laid.rects[&token_id];
|
||||||
|
let after = laid.rects[&after_id];
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
laid.styles[&token_id].width,
|
||||||
|
obscura_render::Dimension::Percent(1.0),
|
||||||
|
"computed style must retain the authored percentage"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
token.width > 20.0 && token.width < 100.0,
|
||||||
|
"a non-replaced inline's used width must hug its text and padding: {token:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
token.height < 40.0,
|
||||||
|
"height/min-height/max-height do not apply to a non-replaced inline: {token:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
after.x >= token.x + token.width - 0.01 && (after.y - token.y).abs() < 0.01,
|
||||||
|
"the following inline must remain on the same line: {token:?} {after:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn inline_svg_derives_auto_height_from_view_box() {
|
fn inline_svg_derives_auto_height_from_view_box() {
|
||||||
let tree = parse_html(
|
let tree = parse_html(
|
||||||
|
|||||||
Reference in new issue
Block a user