diff options
author | Simon Wülker <simon.wuelker@arcor.de> | 2025-02-21 17:28:12 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-21 16:28:12 +0000 |
commit | 085cd981aa61cd74d2ec017d41248a0feb5c99f6 (patch) | |
tree | 5519f8dd91fd4f563f2d5306c1bd303ce29b141c | |
parent | 6ccdf7d19fe49861deb5130c58337a2e2526e105 (diff) | |
download | servo-085cd981aa61cd74d2ec017d41248a0feb5c99f6.tar.gz servo-085cd981aa61cd74d2ec017d41248a0feb5c99f6.zip |
Support the `<meter>` element (#35524)
* Allow attaching UA shadow roots to any element
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Implement a UA shadow tree for the <meter> element
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Add UA styles for the meter element
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
* Include spec text when computing meter state
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
---------
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
-rw-r--r-- | Cargo.lock | 40 | ||||
-rw-r--r-- | components/script/dom/element.rs | 10 | ||||
-rw-r--r-- | components/script/dom/htmlmeterelement.rs | 153 | ||||
-rw-r--r-- | components/script/dom/virtualmethods.rs | 4 | ||||
-rw-r--r-- | components/script/layout_dom/element.rs | 3 | ||||
-rw-r--r-- | resources/servo.css | 24 |
6 files changed, 207 insertions, 27 deletions
diff --git a/Cargo.lock b/Cargo.lock index 591883b31d7..8bd2883fe7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1017,7 +1017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -1686,7 +1686,7 @@ dependencies = [ [[package]] name = "dom" version = "0.0.1" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" dependencies = [ "bitflags 2.8.0", "malloc_size_of", @@ -1967,7 +1967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2493,7 +2493,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3955,7 +3955,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4299,7 +4299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -4477,7 +4477,7 @@ dependencies = [ [[package]] name = "malloc_size_of" version = "0.0.1" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" dependencies = [ "app_units", "cssparser", @@ -6172,7 +6172,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6481,7 +6481,7 @@ dependencies = [ [[package]] name = "selectors" version = "0.26.0" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" dependencies = [ "bitflags 2.8.0", "cssparser", @@ -6766,7 +6766,7 @@ dependencies = [ [[package]] name = "servo_arc" version = "0.4.0" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" dependencies = [ "serde", "stable_deref_trait", @@ -6775,7 +6775,7 @@ dependencies = [ [[package]] name = "servo_atoms" version = "0.0.1" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" dependencies = [ "string_cache", "string_cache_codegen", @@ -7133,7 +7133,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "static_prefs" version = "0.1.0" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" [[package]] name = "strck" @@ -7213,7 +7213,7 @@ dependencies = [ [[package]] name = "style" version = "0.0.1" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" dependencies = [ "app_units", "arrayvec", @@ -7271,7 +7271,7 @@ dependencies = [ [[package]] name = "style_config" version = "0.0.1" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" dependencies = [ "lazy_static", ] @@ -7279,7 +7279,7 @@ dependencies = [ [[package]] name = "style_derive" version = "0.0.1" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" dependencies = [ "darling", "proc-macro2", @@ -7309,7 +7309,7 @@ dependencies = [ [[package]] name = "style_traits" version = "0.0.1" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" dependencies = [ "app_units", "bitflags 2.8.0", @@ -7474,7 +7474,7 @@ dependencies = [ "getrandom", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7703,7 +7703,7 @@ dependencies = [ [[package]] name = "to_shmem" version = "0.1.0" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" dependencies = [ "cssparser", "servo_arc", @@ -7716,7 +7716,7 @@ dependencies = [ [[package]] name = "to_shmem_derive" version = "0.1.0" -source = "git+https://github.com/servo/stylo?branch=2025-02-03#1977193ab231d89cf0f87ccc23c05e4ff67ee3fd" +source = "git+https://github.com/servo/stylo?branch=2025-02-03#7eaabe1687ea6afa84c6d7d6b8c1b3466aa56a36" dependencies = [ "darling", "proc-macro2", @@ -8763,7 +8763,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 3abc82e31a3..7a6936611df 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -526,10 +526,9 @@ impl Element { // If element’s local name is not a valid shadow host name, // then throw a "NotSupportedError" DOMException. if !is_valid_shadow_host_name(self.local_name()) { - match self.local_name() { - &local_name!("video") | &local_name!("audio") - if is_ua_widget == IsUserAgentWidget::Yes => {}, - _ => return Err(Error::NotSupported), + // UA shadow roots may be attached to anything + if is_ua_widget != IsUserAgentWidget::Yes { + return Err(Error::NotSupported); } } @@ -3975,6 +3974,9 @@ impl SelectorsElement for DomRoot<Element> { NonTSPseudoClass::Indeterminate | NonTSPseudoClass::Invalid | NonTSPseudoClass::Modal | + NonTSPseudoClass::MozMeterOptimum | + NonTSPseudoClass::MozMeterSubOptimum | + NonTSPseudoClass::MozMeterSubSubOptimum | NonTSPseudoClass::Optional | NonTSPseudoClass::OutOfRange | NonTSPseudoClass::PlaceholderShown | diff --git a/components/script/dom/htmlmeterelement.rs b/components/script/dom/htmlmeterelement.rs index 596a792f2b5..c1d942260f8 100644 --- a/components/script/dom/htmlmeterelement.rs +++ b/components/script/dom/htmlmeterelement.rs @@ -2,28 +2,47 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::cell::Ref; use std::ops::{Add, Div}; use dom_struct::dom_struct; use html5ever::{local_name, LocalName, Prefix}; use js::rust::HandleObject; +use style_dom::ElementState; +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::HTMLMeterElementBinding::HTMLMeterElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{ + ShadowRootMode, SlotAssignmentMode, +}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::num::Finite; -use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::document::Document; -use crate::dom::element::Element; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::htmldivelement::HTMLDivElement; use crate::dom::htmlelement::HTMLElement; -use crate::dom::node::Node; +use crate::dom::node::{BindContext, ChildrenMutation, Node, NodeTraits}; use crate::dom::nodelist::NodeList; +use crate::dom::shadowroot::IsUserAgentWidget; +use crate::dom::virtualmethods::VirtualMethods; use crate::script_runtime::CanGc; #[dom_struct] pub(crate) struct HTMLMeterElement { htmlelement: HTMLElement, labels_node_list: MutNullableDom<NodeList>, + shadow_tree: DomRefCell<Option<ShadowTree>>, +} + +/// Holds handles to all slots in the UA shadow tree +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct ShadowTree { + meter_value: Dom<HTMLDivElement>, } /// <https://html.spec.whatwg.org/multipage/#the-meter-element> @@ -36,6 +55,7 @@ impl HTMLMeterElement { HTMLMeterElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), labels_node_list: MutNullableDom::new(None), + shadow_tree: Default::default(), } } @@ -56,6 +76,98 @@ impl HTMLMeterElement { can_gc, ) } + + fn create_shadow_tree(&self, can_gc: CanGc) { + let document = self.owner_document(); + let root = self + .upcast::<Element>() + .attach_shadow( + IsUserAgentWidget::Yes, + ShadowRootMode::Closed, + false, + SlotAssignmentMode::Manual, + ) + .expect("Attaching UA shadow root failed"); + + let meter_value = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc); + root.upcast::<Node>() + .AppendChild(meter_value.upcast::<Node>()) + .unwrap(); + + let _ = self.shadow_tree.borrow_mut().insert(ShadowTree { + meter_value: meter_value.as_traced(), + }); + self.upcast::<Node>() + .dirty(crate::dom::node::NodeDamage::OtherNodeDamage); + } + + fn shadow_tree(&self, can_gc: CanGc) -> Ref<'_, ShadowTree> { + if !self.upcast::<Element>().is_shadow_host() { + self.create_shadow_tree(can_gc); + } + + Ref::filter_map(self.shadow_tree.borrow(), Option::as_ref) + .ok() + .expect("UA shadow tree was not created") + } + + fn update_state(&self, can_gc: CanGc) { + let value = *self.Value(); + let low = *self.Low(); + let high = *self.High(); + let min = *self.Min(); + let max = *self.Max(); + let optimum = *self.Optimum(); + + // If the optimum point is less than the low boundary, then the region between the minimum value and + // the low boundary must be treated as the optimum region, the region from the low boundary up to the + // high boundary must be treated as a suboptimal region, and the remaining region must be treated as + // an even less good region + let element_state = if optimum < low { + if value < low { + ElementState::OPTIMUM + } else if value <= high { + ElementState::SUB_OPTIMUM + } else { + ElementState::SUB_SUB_OPTIMUM + } + } + // If the optimum point is higher than the high boundary, then the situation is reversed; the region between + // the high boundary and the maximum value must be treated as the optimum region, the region from the high + // boundary down to the low boundary must be treated as a suboptimal region, and the remaining region must + // be treated as an even less good region. + else if optimum > high { + if value > high { + ElementState::OPTIMUM + } else if value >= low { + ElementState::SUB_OPTIMUM + } else { + ElementState::SUB_SUB_OPTIMUM + } + } + // If the optimum point is equal to the low boundary or the high boundary, or anywhere in between them, + // then the region between the low and high boundaries of the gauge must be treated as the optimum region, + // and the low and high parts, if any, must be treated as suboptimal. + else if (low..=high).contains(&value) { + ElementState::OPTIMUM + } else { + ElementState::SUB_OPTIMUM + }; + + // Set the correct pseudo class + self.upcast::<Element>() + .set_state(ElementState::METER_OPTIMUM_STATES, false); + self.upcast::<Element>().set_state(element_state, true); + + // Update the visual width of the meter + let shadow_tree = self.shadow_tree(can_gc); + let position = (value - min) / (max - min) * 100.0; + let style = format!("width: {position}%"); + shadow_tree + .meter_value + .upcast::<Element>() + .set_string_attribute(&local_name!("style"), style.into(), can_gc); + } } impl HTMLMeterElementMethods<crate::DomTypeHolder> for HTMLMeterElement { @@ -210,3 +322,38 @@ impl HTMLMeterElementMethods<crate::DomTypeHolder> for HTMLMeterElement { ); } } + +impl VirtualMethods for HTMLMeterElement { + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) + } + + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { + self.super_type().unwrap().attribute_mutated(attr, mutation); + + let is_important_attribute = matches!( + attr.local_name(), + &local_name!("high") | + &local_name!("low") | + &local_name!("min") | + &local_name!("max") | + &local_name!("optimum") | + &local_name!("value") + ); + if is_important_attribute { + self.update_state(CanGc::note()); + } + } + + fn children_changed(&self, mutation: &ChildrenMutation) { + self.super_type().unwrap().children_changed(mutation); + + self.update_state(CanGc::note()); + } + + fn bind_to_tree(&self, context: &BindContext) { + self.super_type().unwrap().bind_to_tree(context); + + self.update_state(CanGc::note()); + } +} diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index 6c2f8b646c6..3fb7a52899e 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -36,6 +36,7 @@ use crate::dom::htmllielement::HTMLLIElement; use crate::dom::htmllinkelement::HTMLLinkElement; use crate::dom::htmlmediaelement::HTMLMediaElement; use crate::dom::htmlmetaelement::HTMLMetaElement; +use crate::dom::htmlmeterelement::HTMLMeterElement; use crate::dom::htmlobjectelement::HTMLObjectElement; use crate::dom::htmloptgroupelement::HTMLOptGroupElement; use crate::dom::htmloptionelement::HTMLOptionElement; @@ -234,6 +235,9 @@ pub(crate) fn vtable_for(node: &Node) -> &dyn VirtualMethods { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLMetaElement)) => { node.downcast::<HTMLMetaElement>().unwrap() as &dyn VirtualMethods }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLMeterElement)) => { + node.downcast::<HTMLMeterElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement)) => { node.downcast::<HTMLObjectElement>().unwrap() as &dyn VirtualMethods }, diff --git a/components/script/layout_dom/element.rs b/components/script/layout_dom/element.rs index cdca829dca4..02862218e92 100644 --- a/components/script/layout_dom/element.rs +++ b/components/script/layout_dom/element.rs @@ -681,6 +681,9 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> { NonTSPseudoClass::Indeterminate | NonTSPseudoClass::Invalid | NonTSPseudoClass::Modal | + NonTSPseudoClass::MozMeterOptimum | + NonTSPseudoClass::MozMeterSubOptimum | + NonTSPseudoClass::MozMeterSubSubOptimum | NonTSPseudoClass::Optional | NonTSPseudoClass::OutOfRange | NonTSPseudoClass::PlaceholderShown | diff --git a/resources/servo.css b/resources/servo.css index 5a4297fd1d6..c7e891ec58a 100644 --- a/resources/servo.css +++ b/resources/servo.css @@ -304,3 +304,27 @@ svg > * { padding: 0; margin: 0; } + +meter { + display: inline-block; + width: 100px; + height: 12px; + border-radius: 6px; + background: linear-gradient(#e6e6e6, #e6e6e6, #eeeeee 20%, #cccccc 45%, #cccccc 55%); + overflow: clip; +} + +/* FIXME: These should use the ::-moz-meter-bar pseudo element */ +meter div { + height: 100%; +} + +meter:-moz-meter-optimum div { + background: linear-gradient(#ad7, #ad7, #cea 20%, #7a3 45%, #7a3 55%); +} +meter:-moz-meter-sub-optimum div { + background: linear-gradient(#fe7, #fe7, #ffc 20%, #db3 45%, #db3 55%); +} +meter:-moz-meter-sub-sub-optimum div { + background: linear-gradient(#f77, #f77, #fcc 20%, #d44 45%, #d44 55%); +}
\ No newline at end of file |