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 /components/script | |
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>
Diffstat (limited to 'components/script')
-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 |
4 files changed, 163 insertions, 7 deletions
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 | |