mirror of
https://github.com/lexmount/moli.git
synced 2026-10-06 08:00:59 +00:00
fix(css): apply object dimension attributes as presentation hints
This commit is contained in:
@@ -674,6 +674,51 @@ fn computed_style_resolves_valid_typed_css_math_and_rejects_invalid_unit_algebra
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_dimension_attributes_participate_in_the_css_cascade() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://object-dimension-presentation-hints.test/",
|
||||
r#"<!doctype html>
|
||||
<style>#author-rule { width: 70px; height: 80px; }</style>
|
||||
<object id="target" width="100" height="50"></object>
|
||||
<object id="author-rule" width="200" height="210"></object>"#,
|
||||
);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const target = document.getElementById('target');
|
||||
const authorRule = document.getElementById('author-rule');
|
||||
const size = element => {
|
||||
const computed = getComputedStyle(element);
|
||||
return [computed.width, computed.height];
|
||||
};
|
||||
const values = [...size(target)];
|
||||
|
||||
target.setAttribute('width', '12.5px');
|
||||
target.setAttribute('height', '25.%ignored');
|
||||
values.push(...size(target));
|
||||
|
||||
target.style.width = '9px';
|
||||
target.removeAttribute('width');
|
||||
values.push(...size(target));
|
||||
target.style.removeProperty('width');
|
||||
target.removeAttribute('height');
|
||||
values.push(...size(target));
|
||||
values.push(...size(authorRule));
|
||||
return JSON.stringify(values);
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("object dimension presentation hints should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"["100px","50px","12.5px","25%","9px","25%","auto","auto","70px","80px"]"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svg_generic_presentation_attributes_apply_to_every_svg_element() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
|
||||
@@ -163,6 +163,9 @@ impl QueryElement<'_> {
|
||||
);
|
||||
}
|
||||
} else if element.namespace() == HTML_NAMESPACE {
|
||||
if element.local_name() == "object" {
|
||||
append_html_object_dimension_declarations(element, &mut block);
|
||||
}
|
||||
append_html_table_presentation_declarations(*self, &mut block);
|
||||
}
|
||||
|
||||
@@ -658,6 +661,75 @@ fn append_html_table_cell_padding_declarations(
|
||||
}
|
||||
}
|
||||
|
||||
fn append_html_object_dimension_declarations(
|
||||
element: &Element,
|
||||
block: &mut PropertyDeclarationBlock,
|
||||
) {
|
||||
for (attribute, is_width) in [("width", true), ("height", false)] {
|
||||
let Some(value) = element.attribute(attribute) else {
|
||||
continue;
|
||||
};
|
||||
let Some(dimension) = parse_html_dimension_attribute(value) else {
|
||||
continue;
|
||||
};
|
||||
use style::values::generics::length::Size;
|
||||
let size = match dimension {
|
||||
HtmlDimension::Pixels(value) => LengthPercentage::Length(NoCalcLength::from_px(value)),
|
||||
HtmlDimension::Percentage(value) => {
|
||||
LengthPercentage::Percentage(NoCalcPercentage::new(value / 100.0))
|
||||
}
|
||||
};
|
||||
let size = Size::LengthPercentage(NonNegative(size));
|
||||
let declaration = if is_width {
|
||||
PropertyDeclaration::Width(size)
|
||||
} else {
|
||||
PropertyDeclaration::Height(size)
|
||||
};
|
||||
block.push(declaration, Importance::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum HtmlDimension {
|
||||
Pixels(f32),
|
||||
Percentage(f32),
|
||||
}
|
||||
|
||||
/// Parses an HTML dimension value as a non-negative CSS pixel length or
|
||||
/// percentage. HTML's legacy algorithm deliberately accepts trailing junk,
|
||||
/// but requires the value itself to start with an ASCII digit after leading
|
||||
/// ASCII whitespace.
|
||||
fn parse_html_dimension_attribute(value: &str) -> Option<HtmlDimension> {
|
||||
let value = value.trim_start_matches(['\t', '\n', '\u{000C}', '\r', ' ']);
|
||||
let bytes = value.as_bytes();
|
||||
if !bytes.first().is_some_and(u8::is_ascii_digit) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let integer_end = bytes
|
||||
.iter()
|
||||
.take_while(|byte| byte.is_ascii_digit())
|
||||
.count();
|
||||
let mut number_end = integer_end;
|
||||
if bytes.get(integer_end) == Some(&b'.') {
|
||||
number_end += 1;
|
||||
number_end += bytes[number_end..]
|
||||
.iter()
|
||||
.take_while(|byte| byte.is_ascii_digit())
|
||||
.count();
|
||||
}
|
||||
|
||||
let number = value[..number_end]
|
||||
.parse::<f32>()
|
||||
.ok()
|
||||
.filter(|number| number.is_finite())?;
|
||||
if bytes.get(number_end) == Some(&b'%') {
|
||||
Some(HtmlDimension::Percentage(number))
|
||||
} else {
|
||||
Some(HtmlDimension::Pixels(number))
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors Blink's legacy `cellpadding` state: an absent or exactly empty
|
||||
/// attribute keeps the historical 1px default; non-empty values use loose
|
||||
/// signed-integer parsing and are clamped to `uint16_t`.
|
||||
@@ -880,4 +952,48 @@ mod tests {
|
||||
assert_eq!(parse_html_legacy_color("transparent"), None);
|
||||
assert_eq!(parse_html_legacy_color(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_dimension_attribute_uses_legacy_dimension_parsing() {
|
||||
assert_eq!(
|
||||
parse_html_dimension_attribute(" 100"),
|
||||
Some(HtmlDimension::Pixels(100.0))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_html_dimension_attribute("12.5px"),
|
||||
Some(HtmlDimension::Pixels(12.5))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_html_dimension_attribute("25.5%ignored"),
|
||||
Some(HtmlDimension::Percentage(25.5))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_html_dimension_attribute("1.%"),
|
||||
Some(HtmlDimension::Percentage(1.0))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_html_dimension_attribute("1."),
|
||||
Some(HtmlDimension::Pixels(1.0))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_html_dimension_attribute("1.%ignored"),
|
||||
Some(HtmlDimension::Percentage(1.0))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_html_dimension_attribute("1.x%"),
|
||||
Some(HtmlDimension::Pixels(1.0))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_html_dimension_attribute("1 %"),
|
||||
Some(HtmlDimension::Pixels(1.0))
|
||||
);
|
||||
assert_eq!(parse_html_dimension_attribute("\u{a0}10"), None);
|
||||
assert_eq!(
|
||||
parse_html_dimension_attribute("0%"),
|
||||
Some(HtmlDimension::Percentage(0.0))
|
||||
);
|
||||
assert_eq!(parse_html_dimension_attribute("+10"), None);
|
||||
assert_eq!(parse_html_dimension_attribute(".5"), None);
|
||||
assert_eq!(parse_html_dimension_attribute("auto"), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user