render: size native input controls
This commit is contained in:
1 parent
eb133de419
commit
40a34639ca
2 files changed
+179
-2
No files matched your search
@@ -222,6 +222,7 @@ fn cascade_walk(
|
|||||||
matcher: &mut obscura_dom::selector::Matcher,
|
matcher: &mut obscura_dom::selector::Matcher,
|
||||||
styles: &mut HashMap<NodeId, crate::LayoutStyle>,
|
styles: &mut HashMap<NodeId, crate::LayoutStyle>,
|
||||||
parent_props: &std::rc::Rc<HashMap<String, String>>,
|
parent_props: &std::rc::Rc<HashMap<String, String>>,
|
||||||
|
quirks_mode: bool,
|
||||||
) {
|
) {
|
||||||
let Some(node) = tree.get_node(id) else { return };
|
let Some(node) = tree.get_node(id) else { return };
|
||||||
let is_element = node.is_element();
|
let is_element = node.is_element();
|
||||||
@@ -230,6 +231,45 @@ fn cascade_walk(
|
|||||||
let mut this_props = parent_props.clone();
|
let mut this_props = parent_props.clone();
|
||||||
if let Some(elem) = node.as_element() {
|
if let Some(elem) = node.as_element() {
|
||||||
let mut style = crate::style::ua_style(elem.local.as_ref());
|
let mut style = crate::style::ua_style(elem.local.as_ref());
|
||||||
|
if quirks_mode && elem.local.as_ref() == "form" {
|
||||||
|
// Legacy HTML/quirks rendering keeps one em after forms. Standards
|
||||||
|
// mode does not; Hacker News and many old document templates omit
|
||||||
|
// a doctype and rely on this spacing.
|
||||||
|
style.margin_relative[2] = Some(crate::Dimension::Em(1.0));
|
||||||
|
}
|
||||||
|
if elem.local.as_ref() == "input" {
|
||||||
|
let input_type = node
|
||||||
|
.get_attribute("type")
|
||||||
|
.unwrap_or("text")
|
||||||
|
.trim()
|
||||||
|
.to_ascii_lowercase();
|
||||||
|
if quirks_mode {
|
||||||
|
style.box_sizing = crate::BoxSizing::BorderBox;
|
||||||
|
}
|
||||||
|
match input_type.as_str() {
|
||||||
|
"checkbox" | "radio" => {
|
||||||
|
style.margin = crate::Edges {
|
||||||
|
top: 3.0,
|
||||||
|
right: 3.0,
|
||||||
|
bottom: 3.0,
|
||||||
|
left: 4.0,
|
||||||
|
};
|
||||||
|
style.padding = crate::Edges::default();
|
||||||
|
style.border = crate::Edges::default();
|
||||||
|
}
|
||||||
|
"range" | "color" => {
|
||||||
|
style.margin = crate::Edges {
|
||||||
|
top: 2.0,
|
||||||
|
right: 2.0,
|
||||||
|
bottom: 2.0,
|
||||||
|
left: 2.0,
|
||||||
|
};
|
||||||
|
style.padding = crate::Edges::default();
|
||||||
|
style.border = crate::Edges::default();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
// UA rule `[hidden]:not([hidden=until-found]) { display: none }`.
|
// UA rule `[hidden]:not([hidden=until-found]) { display: none }`.
|
||||||
// Applied before the author cascade so a matching author `display`
|
// Applied before the author cascade so a matching author `display`
|
||||||
// still wins (UA origin, per the HTML rendering spec). Sites ship
|
// still wins (UA origin, per the HTML rendering spec). Sites ship
|
||||||
@@ -271,7 +311,15 @@ fn cascade_walk(
|
|||||||
matcher.push_ancestor(tree, id);
|
matcher.push_ancestor(tree, id);
|
||||||
}
|
}
|
||||||
for cid in tree.children(id) {
|
for cid in tree.children(id) {
|
||||||
cascade_walk(tree, cid, sheet, matcher, styles, &this_props);
|
cascade_walk(
|
||||||
|
tree,
|
||||||
|
cid,
|
||||||
|
sheet,
|
||||||
|
matcher,
|
||||||
|
styles,
|
||||||
|
&this_props,
|
||||||
|
quirks_mode,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if is_element {
|
if is_element {
|
||||||
matcher.pop_ancestor();
|
matcher.pop_ancestor();
|
||||||
@@ -320,7 +368,23 @@ pub fn layout_dom_with_images(
|
|||||||
// into children, pop on the way back out. This is what lets descendant
|
// into children, pop on the way back out. This is what lets descendant
|
||||||
// combinators (".mw-body .firstHeading") fast-reject via the filter
|
// combinators (".mw-body .firstHeading") fast-reject via the filter
|
||||||
// instead of falling back to the always-true "can't reject" case.
|
// instead of falling back to the always-true "can't reject" case.
|
||||||
cascade_walk(tree, tree.document(), &sheet, &mut matcher, &mut styles, &root_props);
|
let quirks_mode = !tree
|
||||||
|
.descendants(tree.document())
|
||||||
|
.into_iter()
|
||||||
|
.any(|id| {
|
||||||
|
tree.get_node(id).map_or(false, |node| {
|
||||||
|
matches!(node.data, obscura_dom::tree::NodeData::Doctype { .. })
|
||||||
|
})
|
||||||
|
});
|
||||||
|
cascade_walk(
|
||||||
|
tree,
|
||||||
|
tree.document(),
|
||||||
|
&sheet,
|
||||||
|
&mut matcher,
|
||||||
|
&mut styles,
|
||||||
|
&root_props,
|
||||||
|
quirks_mode,
|
||||||
|
);
|
||||||
if timing {
|
if timing {
|
||||||
let (r, i, c, l, u) = sheet.debug_stats();
|
let (r, i, c, l, u) = sheet.debug_stats();
|
||||||
eprintln!("[timing] parse+index={:?} cascade={:?} rules={} id_keys={} class_keys={} local_keys={} universal={}", t_parse, t1.elapsed(), r, i, c, l, u);
|
eprintln!("[timing] parse+index={:?} cascade={:?} rules={} id_keys={} class_keys={} local_keys={} universal={}", t_parse, t1.elapsed(), r, i, c, l, u);
|
||||||
@@ -638,6 +702,93 @@ pub fn layout_dom_with_images(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve native input-control intrinsic border-box geometry after
|
||||||
|
// inheritance and author cascading. Text-like inputs use the HTML
|
||||||
|
// `size` attribute (20 by default) and the control's own computed
|
||||||
|
// font; author CSS widths/heights remain authoritative. Without this
|
||||||
|
// replaced-control sizing, every input is an empty auto-sized leaf
|
||||||
|
// (0px tall and often stretched to its container).
|
||||||
|
for (&id, style) in styles.iter_mut() {
|
||||||
|
let Some(node) = tree.get_node(id) else { continue };
|
||||||
|
let Some(element) = node.as_element() else { continue };
|
||||||
|
if element.local.as_ref() != "input" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let input_type = node
|
||||||
|
.get_attribute("type")
|
||||||
|
.unwrap_or("text")
|
||||||
|
.trim()
|
||||||
|
.to_ascii_lowercase();
|
||||||
|
if input_type == "hidden" {
|
||||||
|
style.display = crate::Display::None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let font_size = style.font_size.unwrap_or(13.333_333).max(1.0);
|
||||||
|
let horizontal_edges = style.padding.left
|
||||||
|
+ style.padding.right
|
||||||
|
+ style.border.left
|
||||||
|
+ style.border.right;
|
||||||
|
let vertical_edges = style.padding.top
|
||||||
|
+ style.padding.bottom
|
||||||
|
+ style.border.top
|
||||||
|
+ style.border.bottom;
|
||||||
|
let default_height =
|
||||||
|
crate::inline::used_line_height(style).max(1.0) + vertical_edges;
|
||||||
|
|
||||||
|
let (intrinsic_width, intrinsic_height) = match input_type.as_str() {
|
||||||
|
"checkbox" | "radio" => (13.0, 13.0),
|
||||||
|
"range" => (129.0, 16.0),
|
||||||
|
"color" => (50.0, 27.0),
|
||||||
|
"file" => (253.0, default_height.max(22.0)),
|
||||||
|
"submit" | "reset" | "button" => {
|
||||||
|
let fallback = match input_type.as_str() {
|
||||||
|
"reset" => "Reset",
|
||||||
|
"button" => "",
|
||||||
|
_ => "Submit Query",
|
||||||
|
};
|
||||||
|
let label = node.get_attribute("value").unwrap_or(fallback);
|
||||||
|
(
|
||||||
|
(label.chars().count() as f32 * font_size * 0.55
|
||||||
|
+ font_size * 1.5
|
||||||
|
+ horizontal_edges)
|
||||||
|
.max(20.0),
|
||||||
|
default_height,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"image" => (horizontal_edges, vertical_edges),
|
||||||
|
_ => {
|
||||||
|
let size = node
|
||||||
|
.get_attribute("size")
|
||||||
|
.and_then(|value| value.parse::<u32>().ok())
|
||||||
|
.filter(|&value| value > 0)
|
||||||
|
.unwrap_or(20) as f32;
|
||||||
|
(
|
||||||
|
size * font_size * 0.6
|
||||||
|
+ font_size * 0.675
|
||||||
|
+ horizontal_edges,
|
||||||
|
default_height,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if style.width == crate::Dimension::Auto {
|
||||||
|
let declared_width = if style.box_sizing == crate::BoxSizing::ContentBox {
|
||||||
|
(intrinsic_width - horizontal_edges).max(0.0)
|
||||||
|
} else {
|
||||||
|
intrinsic_width
|
||||||
|
};
|
||||||
|
style.width = crate::Dimension::Px(declared_width);
|
||||||
|
}
|
||||||
|
if style.height == crate::Dimension::Auto {
|
||||||
|
let declared_height = if style.box_sizing == crate::BoxSizing::ContentBox {
|
||||||
|
(intrinsic_height - vertical_edges).max(0.0)
|
||||||
|
} else {
|
||||||
|
intrinsic_height
|
||||||
|
};
|
||||||
|
style.height = crate::Dimension::Px(declared_height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resolve_grid_areas(tree, root_id, &mut styles);
|
resolve_grid_areas(tree, root_id, &mut styles);
|
||||||
|
|
||||||
// Blockify grid items (CSS Display 3): a direct child of a grid
|
// Blockify grid items (CSS Display 3): a direct child of a grid
|
||||||
|
|||||||
@@ -107,6 +107,32 @@ pub fn ua_style(tag: &str) -> LayoutStyle {
|
|||||||
} else if tag == "a" {
|
} else if tag == "a" {
|
||||||
style.color = Some([0, 0, 238, 255]); // blue
|
style.color = Some([0, 0, 238, 255]); // blue
|
||||||
style.underline = Some(true); // UA default: links are underlined
|
style.underline = Some(true); // UA default: links are underlined
|
||||||
|
} else if tag == "input" {
|
||||||
|
// Native text controls are atomic inline-level boxes with their own
|
||||||
|
// platform font and intrinsic border-box dimensions; they do not
|
||||||
|
// inherit the page's font shorthand by default. The declared CSS box
|
||||||
|
// remains content-box in standards mode and switches in quirks mode.
|
||||||
|
// Size-dependent geometry is resolved after cascading, once the input
|
||||||
|
// type and `size` attribute are available (dom::layout_dom).
|
||||||
|
style.display = Display::Inline;
|
||||||
|
style.is_inline_block = true;
|
||||||
|
style.font_size = Some(13.333_333);
|
||||||
|
style.font_family = Some("arial".to_string());
|
||||||
|
style.line_height = Some(crate::LineHeight::Normal);
|
||||||
|
style.padding = Edges {
|
||||||
|
top: 1.0,
|
||||||
|
right: 2.0,
|
||||||
|
bottom: 1.0,
|
||||||
|
left: 2.0,
|
||||||
|
};
|
||||||
|
style.border = Edges {
|
||||||
|
top: 2.0,
|
||||||
|
right: 2.0,
|
||||||
|
bottom: 2.0,
|
||||||
|
left: 2.0,
|
||||||
|
};
|
||||||
|
style.border_color = Some([118, 118, 118, 255]);
|
||||||
|
style.background_color = Some([255, 255, 255, 255]);
|
||||||
} else if tag == "table" || tag == "tbody" {
|
} else if tag == "table" || tag == "tbody" {
|
||||||
style.display = Display::Flex;
|
style.display = Display::Flex;
|
||||||
style.internal_flex_container = true;
|
style.internal_flex_container = true;
|
||||||
|
|||||||
Reference in new issue
Block a user