mirror of
https://github.com/lexmount/moli.git
synced 2026-10-20 16:01:27 +00:00
fix(html): apply hidden until-found styles
This commit is contained in:
@@ -313,6 +313,7 @@ const LIGHTWEIGHT_STYLE_PROPERTIES: &[&str] = &[
|
||||
"container-name",
|
||||
"container-type",
|
||||
"content",
|
||||
"content-visibility",
|
||||
"cursor",
|
||||
"direction",
|
||||
"display",
|
||||
|
||||
@@ -25,6 +25,7 @@ pub(in crate::native_bridge) use metrics::{
|
||||
node_scroll_top_getter_function, node_scroll_top_setter_function,
|
||||
node_scroll_width_getter_function,
|
||||
};
|
||||
pub(super) use mock::element_has_hidden_attribute;
|
||||
pub(crate) use mock::{
|
||||
compute_mock_client_rect, compute_mock_intersection_client_rect,
|
||||
compute_mock_intersection_scrollport_client_rect,
|
||||
|
||||
@@ -160,7 +160,10 @@ fn element_attribute(runtime: &JsContextHost, handle: DomHandle, name: &str) ->
|
||||
runtime.dom_host().get_attribute(handle, name)
|
||||
}
|
||||
|
||||
fn element_has_hidden_attribute(runtime: &JsContextHost, handle: DomHandle) -> bool {
|
||||
pub(in crate::native_bridge::element) fn element_has_hidden_attribute(
|
||||
runtime: &JsContextHost,
|
||||
handle: DomHandle,
|
||||
) -> bool {
|
||||
element_attribute(runtime, handle, "hidden").is_some()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod layout;
|
||||
mod queries;
|
||||
|
||||
pub(in crate::native_bridge::element) use layout::element_has_hidden_attribute;
|
||||
pub(crate) use layout::{
|
||||
compute_mock_client_rect, compute_mock_intersection_client_rect,
|
||||
compute_mock_intersection_scrollport_client_rect, compute_mock_scroll_adjusted_client_rect,
|
||||
|
||||
@@ -1802,6 +1802,12 @@ fn style_entries_affecting_property(entries: &[StyleEntry], property: &str) -> V
|
||||
|
||||
fn cssom_style_property_write_uses_pdb(name: &str, value: &str) -> bool {
|
||||
let name = canonical_style_property_name(name);
|
||||
// This Stylo build parses `content-visibility` but does not expose it
|
||||
// through the declaration-block mutation/query surface. Keep the value in
|
||||
// the validated side-entry path so CSSOM writes remain observable.
|
||||
if name == "content-visibility" {
|
||||
return false;
|
||||
}
|
||||
if moli_css_parse::is_cssom_custom_property_name(&name) {
|
||||
return !value.is_empty() && stylo_pdb_entries_for_property(&name, value, false).is_some();
|
||||
}
|
||||
@@ -2809,6 +2815,21 @@ pub(crate) fn parse_style_property_entries_with_base(
|
||||
});
|
||||
}
|
||||
|
||||
if name == "content-visibility" {
|
||||
let value = css_wide_keyword(value).or_else(|| {
|
||||
let value = value.trim().to_ascii_lowercase();
|
||||
matches!(value.as_str(), "visible" | "auto" | "hidden").then_some(value)
|
||||
})?;
|
||||
return Some(ParsedStylePropertyEntries {
|
||||
entries: vec![StyleEntry {
|
||||
name: name.clone(),
|
||||
value,
|
||||
priority,
|
||||
}],
|
||||
affected_names: vec![name],
|
||||
});
|
||||
}
|
||||
|
||||
if name == "width"
|
||||
&& value
|
||||
.trim_start()
|
||||
|
||||
@@ -12,8 +12,8 @@ use crate::{
|
||||
},
|
||||
document_runtime::DomHandle,
|
||||
native_bridge::element::geometry::{
|
||||
ClientRect, observable_bounding_client_rect, observable_bounding_client_rects,
|
||||
observable_used_grid_tracks,
|
||||
ClientRect, element_has_hidden_attribute, observable_bounding_client_rect,
|
||||
observable_bounding_client_rects, observable_used_grid_tracks,
|
||||
},
|
||||
style_engine::{
|
||||
ComputedDisplayKind, ComputedRenderedStyleFacts, FullStyleWorldSnapshot, StyleViewport,
|
||||
@@ -1383,6 +1383,10 @@ pub(super) fn computed_style_default_value(
|
||||
"line-height" => "normal".to_owned(),
|
||||
"link-parameters" => "none".to_owned(),
|
||||
"content" => "normal".to_owned(),
|
||||
"content-visibility" => match element_hidden_attribute_state(runtime, handle) {
|
||||
HiddenAttributeState::UntilFound => "hidden".to_owned(),
|
||||
HiddenAttributeState::Missing | HiddenAttributeState::Hidden => "visible".to_owned(),
|
||||
},
|
||||
"background-color" => "rgba(0, 0, 0, 0)".to_owned(),
|
||||
"background-attachment" => "scroll".to_owned(),
|
||||
"background-blend-mode" | "mix-blend-mode" => "normal".to_owned(),
|
||||
@@ -2329,7 +2333,9 @@ fn computed_style_property_value_after_style_update(
|
||||
if !computed_style_applies(runtime, handle) {
|
||||
return String::new();
|
||||
}
|
||||
if property == "display" && element_has_hidden_attribute(runtime, handle) {
|
||||
if property == "display"
|
||||
&& element_hidden_attribute_state(runtime, handle) == HiddenAttributeState::Hidden
|
||||
{
|
||||
return "none".to_owned();
|
||||
}
|
||||
let inputs = prepared_inputs;
|
||||
@@ -2565,6 +2571,13 @@ fn computed_style_property_value_from_moli(
|
||||
"link-parameters" => Some(computed_non_inherited_css_keyword_property_value(
|
||||
runtime, handle, property, "none",
|
||||
)),
|
||||
"content-visibility" => {
|
||||
inline_style_entry_for_inline_style(runtime, handle, property).map(|_| {
|
||||
computed_non_inherited_css_keyword_property_value(
|
||||
runtime, handle, property, "visible",
|
||||
)
|
||||
})
|
||||
}
|
||||
"text-size-adjust" => Some(computed_text_size_adjust_value(runtime, handle)),
|
||||
"transition-property" | "transition-behavior" => {
|
||||
inline_style_entry_for_inline_style(runtime, handle, property).map(|entry| entry.value)
|
||||
@@ -5108,12 +5121,29 @@ fn css_px_values_equal(left: f64, right: f64) -> bool {
|
||||
(left - right).abs() < 0.001
|
||||
}
|
||||
|
||||
fn element_has_hidden_attribute(runtime: &JsContextHost, handle: DomHandle) -> bool {
|
||||
runtime
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum HiddenAttributeState {
|
||||
Missing,
|
||||
Hidden,
|
||||
UntilFound,
|
||||
}
|
||||
|
||||
fn element_hidden_attribute_state(
|
||||
runtime: &JsContextHost,
|
||||
handle: DomHandle,
|
||||
) -> HiddenAttributeState {
|
||||
let value = runtime
|
||||
.dom_host()
|
||||
.node(handle)
|
||||
.and_then(Node::as_element)
|
||||
.is_some_and(|element| element.has_attribute("hidden"))
|
||||
.and_then(|element| element.attribute("hidden"));
|
||||
match value {
|
||||
None => HiddenAttributeState::Missing,
|
||||
Some(value) if value.eq_ignore_ascii_case("until-found") => {
|
||||
HiddenAttributeState::UntilFound
|
||||
}
|
||||
Some(_) => HiddenAttributeState::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_moli_computed_style_value(
|
||||
|
||||
@@ -6910,6 +6910,42 @@ fn computed_display_treats_hidden_attribute_as_none() {
|
||||
assert_eq!(result, "true|none|inline|0|0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn computed_style_distinguishes_hidden_and_until_found_states() {
|
||||
let mut vm = new_storage_test_vm("https://hidden-until-found-computed-style.test/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const target = document.createElement('div');
|
||||
(document.body || document.documentElement || document).appendChild(target);
|
||||
const read = () => [
|
||||
getComputedStyle(target).display,
|
||||
getComputedStyle(target).contentVisibility
|
||||
].join(':');
|
||||
const values = [read()];
|
||||
for (const value of ['', 'asdf', 'until-found', 'UNTIL-FOUND', 'UnTiL-FoUnD', '0']) {
|
||||
target.setAttribute('hidden', value);
|
||||
values.push(read());
|
||||
}
|
||||
target.removeAttribute('hidden');
|
||||
target.style.contentVisibility = 'hidden';
|
||||
values.push(`${target.style.contentVisibility}/${read()}`);
|
||||
target.style.contentVisibility = 'bogus';
|
||||
values.push(`${target.style.contentVisibility}/${read()}`);
|
||||
return values.join('|');
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("hidden presentation states should affect computed style");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
"block:visible|none:visible|none:visible|block:hidden|block:hidden|block:hidden|none:visible|hidden/block:hidden|hidden/block:hidden"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detached_nested_iframe_window_get_computed_style_uses_iframe_width() {
|
||||
let mut vm = new_storage_test_vm("https://nested-computed-width.test/");
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::{
|
||||
mod atoms;
|
||||
mod invalidation;
|
||||
mod presentation;
|
||||
mod presentational_hints;
|
||||
mod query;
|
||||
mod selector_parse;
|
||||
mod style_traversal;
|
||||
|
||||
@@ -26,7 +26,9 @@ use style_traits::{ParsingMode, ToCss};
|
||||
|
||||
use crate::dom::native::Element;
|
||||
|
||||
use super::query::QueryElement;
|
||||
use super::{
|
||||
presentational_hints::synthesize_hidden_until_found_presentational_hint, query::QueryElement,
|
||||
};
|
||||
|
||||
const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
|
||||
const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
|
||||
@@ -162,8 +164,6 @@ impl QueryElement<'_> {
|
||||
}
|
||||
} else if element.namespace() == HTML_NAMESPACE {
|
||||
append_html_table_presentation_declarations(*self, &mut block);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if !block.is_empty() {
|
||||
@@ -173,6 +173,12 @@ impl QueryElement<'_> {
|
||||
LayerOrder::root(),
|
||||
));
|
||||
}
|
||||
synthesize_hidden_until_found_presentational_hint(
|
||||
self.host(),
|
||||
self.handle(),
|
||||
self.shared_lock(),
|
||||
hints,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
use selectors::sink::Push;
|
||||
use style::{
|
||||
applicable_declarations::ApplicableDeclarationBlock,
|
||||
properties::{
|
||||
Importance, PropertyDeclaration, PropertyDeclarationBlock,
|
||||
longhands::content_visibility::SpecifiedValue as ContentVisibility,
|
||||
},
|
||||
rule_tree::{CascadeLevel, CascadeOrigin},
|
||||
servo_arc::Arc,
|
||||
shared_lock::SharedRwLock,
|
||||
stylesheets::layer_rule::LayerOrder,
|
||||
};
|
||||
|
||||
use crate::dom::{
|
||||
NodeId,
|
||||
native::{DomHost, Node},
|
||||
};
|
||||
|
||||
const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
pub(super) fn synthesize_hidden_until_found_presentational_hint<V>(
|
||||
host: &DomHost,
|
||||
handle: NodeId,
|
||||
shared_lock: &SharedRwLock,
|
||||
hints: &mut V,
|
||||
) where
|
||||
V: Push<ApplicableDeclarationBlock>,
|
||||
{
|
||||
let Some(element) = host.node(handle).and_then(Node::as_element) else {
|
||||
return;
|
||||
};
|
||||
if element.namespace() != HTML_NAMESPACE
|
||||
|| !element
|
||||
.attribute("hidden")
|
||||
.is_some_and(|value| value.eq_ignore_ascii_case("until-found"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let declarations = PropertyDeclarationBlock::with_one(
|
||||
PropertyDeclaration::ContentVisibility(ContentVisibility::Hidden),
|
||||
Importance::Normal,
|
||||
);
|
||||
hints.push(ApplicableDeclarationBlock::from_declarations(
|
||||
Arc::new(shared_lock.wrap(declarations)),
|
||||
CascadeLevel::new(CascadeOrigin::PresHints),
|
||||
LayerOrder::root(),
|
||||
));
|
||||
}
|
||||
Reference in New Issue
Block a user