/* This Source Code Form is subject to the terms of the Mozilla Public * 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/. */ //! Element nodes. use std::borrow::Cow; use std::cell::Cell; use std::default::Default; use std::ops::Deref; use std::rc::Rc; use std::str::FromStr; use std::{fmt, mem}; use cssparser::match_ignore_ascii_case; use devtools_traits::AttrInfo; use dom_struct::dom_struct; use embedder_traits::InputMethodType; use euclid::default::{Rect, Size2D}; use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode}; use html5ever::{ local_name, namespace_prefix, namespace_url, ns, LocalName, Namespace, Prefix, QualName, }; use js::jsapi::Heap; use js::jsval::JSVal; use js::rust::HandleObject; use net_traits::request::CorsSettings; use net_traits::ReferrerPolicy; use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; use selectors::bloom::{BloomFilter, BLOOM_HASH_MASK}; use selectors::matching::{ElementSelectorFlags, MatchingContext}; use selectors::sink::Push; use selectors::Element as SelectorsElement; use servo_arc::Arc; use servo_atoms::Atom; use style::applicable_declarations::ApplicableDeclarationBlock; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; use style::context::QuirksMode; use style::invalidation::element::restyle_hints::RestyleHint; use style::properties::longhands::{ self, background_image, border_spacing, font_family, font_size, }; use style::properties::{ parse_style_attribute, ComputedValues, Importance, PropertyDeclaration, PropertyDeclarationBlock, }; use style::rule_tree::CascadeLevel; use style::selector_parser::{ extended_filtering, NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser, }; use style::shared_lock::{Locked, SharedRwLock}; use style::stylesheets::layer_rule::LayerOrder; use style::stylesheets::{CssRuleType, UrlExtraData}; use style::values::generics::position::PreferredRatio; use style::values::generics::ratio::Ratio; use style::values::generics::NonNegative; use style::values::{computed, specified, AtomIdent, AtomString, CSSFloat}; use style::{dom_apis, thread_state, ArcSlice, CaseSensitivityExt}; use style_dom::ElementState; use xml5ever::serialize::TraversalScope::{ ChildrenOnly as XmlChildrenOnly, IncludeNode as XmlIncludeNode, }; use super::htmltablecolelement::{HTMLTableColElement, HTMLTableColElementLayoutHelpers}; use crate::dom::activation::Activatable; use crate::dom::attr::{Attr, AttrHelpersForLayout}; use crate::dom::bindings::cell::{ref_filter_map, DomRefCell, Ref, RefMut}; use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use crate::dom::bindings::codegen::Bindings::ElementBinding::{ElementMethods, ShadowRootInit}; use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{ ShadowRootMethods, ShadowRootMode, }; use crate::dom::bindings::codegen::Bindings::WindowBinding::{ ScrollBehavior, ScrollToOptions, WindowMethods, }; use crate::dom::bindings::codegen::UnionTypes::NodeOrString; use crate::dom::bindings::conversions::DerivedFrom; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::xmlname::XMLName::Invalid; use crate::dom::bindings::xmlname::{ namespace_from_domstring, validate_and_extract, xml_name_type, }; use crate::dom::characterdata::CharacterData; use crate::dom::create::create_element; use crate::dom::customelementregistry::{ CallbackReaction, CustomElementDefinition, CustomElementReaction, CustomElementState, }; use crate::dom::document::{ determine_policy_for_token, Document, LayoutDocumentHelpers, ReflowTriggerCondition, }; use crate::dom::documentfragment::DocumentFragment; use crate::dom::domrect::DOMRect; use crate::dom::domrectlist::DOMRectList; use crate::dom::domtokenlist::DOMTokenList; use crate::dom::elementinternals::ElementInternals; use crate::dom::eventtarget::EventTarget; use crate::dom::htmlanchorelement::HTMLAnchorElement; use crate::dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementLayoutHelpers}; use crate::dom::htmlbuttonelement::HTMLButtonElement; use crate::dom::htmlcollection::HTMLCollection; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; use crate::dom::htmlfontelement::{HTMLFontElement, HTMLFontElementLayoutHelpers}; use crate::dom::htmlformelement::FormControlElementHelpers; use crate::dom::htmlhrelement::{HTMLHRElement, HTMLHRLayoutHelpers}; use crate::dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods}; use crate::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers}; use crate::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers}; use crate::dom::htmllabelelement::HTMLLabelElement; use crate::dom::htmllegendelement::HTMLLegendElement; use crate::dom::htmllinkelement::HTMLLinkElement; use crate::dom::htmlobjectelement::HTMLObjectElement; use crate::dom::htmloptgroupelement::HTMLOptGroupElement; use crate::dom::htmloutputelement::HTMLOutputElement; use crate::dom::htmlselectelement::HTMLSelectElement; use crate::dom::htmlstyleelement::HTMLStyleElement; use crate::dom::htmltablecellelement::{HTMLTableCellElement, HTMLTableCellElementLayoutHelpers}; use crate::dom::htmltableelement::{HTMLTableElement, HTMLTableElementLayoutHelpers}; use crate::dom::htmltablerowelement::{HTMLTableRowElement, HTMLTableRowElementLayoutHelpers}; use crate::dom::htmltablesectionelement::{ HTMLTableSectionElement, HTMLTableSectionElementLayoutHelpers, }; use crate::dom::htmltemplateelement::HTMLTemplateElement; use crate::dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; use crate::dom::htmlvideoelement::{HTMLVideoElement, LayoutHTMLVideoElementHelpers}; use crate::dom::mutationobserver::{Mutation, MutationObserver}; use crate::dom::namednodemap::NamedNodeMap; use crate::dom::node::{ document_from_node, window_from_node, BindContext, ChildrenMutation, LayoutNodeHelpers, Node, NodeDamage, NodeFlags, ShadowIncluding, UnbindContext, }; use crate::dom::nodelist::NodeList; use crate::dom::promise::Promise; use crate::dom::raredata::ElementRareData; use crate::dom::servoparser::ServoParser; use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot}; use crate::dom::text::Text; use crate::dom::validation::Validatable; use crate::dom::validitystate::ValidationFlags; use crate::dom::virtualmethods::{vtable_for, VirtualMethods}; use crate::script_runtime::CanGc; use crate::script_thread::ScriptThread; use crate::stylesheet_loader::StylesheetOwner; use crate::task::TaskOnce; // TODO: Update focus state when the top-level browsing context gains or loses system focus, // and when the element enters or leaves a browsing context container. // https://html.spec.whatwg.org/multipage/#selector-focus #[dom_struct] pub struct Element { node: Node, #[no_trace] local_name: LocalName, tag_name: TagName, #[no_trace] namespace: Namespace, #[no_trace] prefix: DomRefCell>, attrs: DomRefCell>>, #[no_trace] id_attribute: DomRefCell>, #[no_trace] is: DomRefCell>, #[ignore_malloc_size_of = "Arc"] #[no_trace] style_attribute: DomRefCell>>>, attr_list: MutNullableDom, class_list: MutNullableDom, #[no_trace] state: Cell, /// These flags are set by the style system to indicate the that certain /// operations may require restyling this element or its descendants. The /// flags are not atomic, so the style system takes care of only set them /// when it has exclusive access to the element. #[ignore_malloc_size_of = "bitflags defined in rust-selectors"] #[no_trace] selector_flags: Cell, rare_data: DomRefCell>>, } impl fmt::Debug for Element { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "<{}", self.local_name)?; if let Some(ref id) = *self.id_attribute.borrow() { write!(f, " id={}", id)?; } write!(f, ">") } } impl fmt::Debug for DomRoot { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { (**self).fmt(f) } } #[derive(MallocSizeOf, PartialEq)] pub enum ElementCreator { ParserCreated(u64), ScriptCreated, } pub enum CustomElementCreationMode { Synchronous, Asynchronous, } impl ElementCreator { pub fn is_parser_created(&self) -> bool { match *self { ElementCreator::ParserCreated(_) => true, ElementCreator::ScriptCreated => false, } } pub fn return_line_number(&self) -> u64 { match *self { ElementCreator::ParserCreated(l) => l, ElementCreator::ScriptCreated => 1, } } } pub enum AdjacentPosition { BeforeBegin, AfterEnd, AfterBegin, BeforeEnd, } impl FromStr for AdjacentPosition { type Err = Error; fn from_str(position: &str) -> Result { match_ignore_ascii_case! { position, "beforebegin" => Ok(AdjacentPosition::BeforeBegin), "afterbegin" => Ok(AdjacentPosition::AfterBegin), "beforeend" => Ok(AdjacentPosition::BeforeEnd), "afterend" => Ok(AdjacentPosition::AfterEnd), _ => Err(Error::Syntax) } } } // // Element methods // impl Element { pub fn create( name: QualName, is: Option, document: &Document, creator: ElementCreator, mode: CustomElementCreationMode, proto: Option, can_gc: CanGc, ) -> DomRoot { create_element(name, is, document, creator, mode, proto, can_gc) } pub fn new_inherited( local_name: LocalName, namespace: Namespace, prefix: Option, document: &Document, ) -> Element { Element::new_inherited_with_state( ElementState::empty(), local_name, namespace, prefix, document, ) } pub fn new_inherited_with_state( state: ElementState, local_name: LocalName, namespace: Namespace, prefix: Option, document: &Document, ) -> Element { Element { node: Node::new_inherited(document), local_name, tag_name: TagName::new(), namespace, prefix: DomRefCell::new(prefix), attrs: DomRefCell::new(vec![]), id_attribute: DomRefCell::new(None), is: DomRefCell::new(None), style_attribute: DomRefCell::new(None), attr_list: Default::default(), class_list: Default::default(), state: Cell::new(state), selector_flags: Cell::new(ElementSelectorFlags::empty()), rare_data: Default::default(), } } pub fn new( local_name: LocalName, namespace: Namespace, prefix: Option, document: &Document, proto: Option, can_gc: CanGc, ) -> DomRoot { Node::reflect_node_with_proto( Box::new(Element::new_inherited( local_name, namespace, prefix, document, )), document, proto, can_gc, ) } impl_rare_data!(ElementRareData); pub fn restyle(&self, damage: NodeDamage) { let doc = self.node.owner_doc(); let mut restyle = doc.ensure_pending_restyle(self); // FIXME(bholley): I think we should probably only do this for // NodeStyleDamaged, but I'm preserving existing behavior. restyle.hint.insert(RestyleHint::RESTYLE_SELF); if damage == NodeDamage::OtherNodeDamage { doc.note_node_with_dirty_descendants(self.upcast()); restyle.damage = RestyleDamage::rebuild_and_reflow(); } } pub fn set_is(&self, is: LocalName) { *self.is.borrow_mut() = Some(is); } pub fn get_is(&self) -> Option { self.is.borrow().clone() } /// pub fn set_custom_element_state(&self, state: CustomElementState) { // no need to inflate rare data for uncustomized if state != CustomElementState::Uncustomized || self.rare_data().is_some() { self.ensure_rare_data().custom_element_state = state; } let in_defined_state = matches!( state, CustomElementState::Uncustomized | CustomElementState::Custom ); self.set_state(ElementState::DEFINED, in_defined_state) } pub fn get_custom_element_state(&self) -> CustomElementState { if let Some(rare_data) = self.rare_data().as_ref() { return rare_data.custom_element_state; } CustomElementState::Uncustomized } pub fn set_custom_element_definition(&self, definition: Rc) { self.ensure_rare_data().custom_element_definition = Some(definition); } pub fn get_custom_element_definition(&self) -> Option> { self.rare_data().as_ref()?.custom_element_definition.clone() } pub fn clear_custom_element_definition(&self) { self.ensure_rare_data().custom_element_definition = None; } pub fn push_callback_reaction(&self, function: Rc, args: Box<[Heap]>) { self.ensure_rare_data() .custom_element_reaction_queue .push(CustomElementReaction::Callback(function, args)); } pub fn push_upgrade_reaction(&self, definition: Rc) { self.ensure_rare_data() .custom_element_reaction_queue .push(CustomElementReaction::Upgrade(definition)); } pub fn clear_reaction_queue(&self) { if let Some(ref mut rare_data) = *self.rare_data_mut() { rare_data.custom_element_reaction_queue.clear(); } } pub fn invoke_reactions(&self, can_gc: CanGc) { loop { rooted_vec!(let mut reactions); match *self.rare_data_mut() { Some(ref mut data) => { mem::swap(&mut *reactions, &mut data.custom_element_reaction_queue) }, None => break, }; if reactions.is_empty() { break; } for reaction in reactions.iter() { reaction.invoke(self, can_gc); } reactions.clear(); } } /// style will be `None` for elements in a `display: none` subtree. otherwise, the element has a /// layout box iff it doesn't have `display: none`. pub fn style(&self, can_gc: CanGc) -> Option> { self.upcast::().style(can_gc) } // https://drafts.csswg.org/cssom-view/#css-layout-box pub fn has_css_layout_box(&self, can_gc: CanGc) -> bool { self.style(can_gc) .is_some_and(|s| !s.get_box().clone_display().is_none()) } // https://drafts.csswg.org/cssom-view/#potentially-scrollable fn is_potentially_scrollable_body(&self, can_gc: CanGc) -> bool { let node = self.upcast::(); debug_assert!( node.owner_doc().GetBody().as_deref() == self.downcast::(), "Called is_potentially_scrollable_body on element that is not the " ); // "An element body (which will be the body element) is potentially // scrollable if all of the following conditions are true: // - body has an associated box." if !self.has_css_layout_box(can_gc) { return false; } // " - body’s parent element’s computed value of the overflow-x or // overflow-y properties is neither visible nor clip." if let Some(parent) = node.GetParentElement() { if let Some(style) = parent.style(can_gc) { if !style.get_box().clone_overflow_x().is_scrollable() && !style.get_box().clone_overflow_y().is_scrollable() { return false; } }; } // " - body’s computed value of the overflow-x or overflow-y properties // is neither visible nor clip." if let Some(style) = self.style(can_gc) { if !style.get_box().clone_overflow_x().is_scrollable() && !style.get_box().clone_overflow_y().is_scrollable() { return false; } }; true } // https://drafts.csswg.org/cssom-view/#scrolling-box fn has_scrolling_box(&self, can_gc: CanGc) -> bool { // TODO: scrolling mechanism, such as scrollbar (We don't have scrollbar yet) // self.has_scrolling_mechanism() self.style(can_gc).is_some_and(|style| { style.get_box().clone_overflow_x().is_scrollable() || style.get_box().clone_overflow_y().is_scrollable() }) } fn has_overflow(&self, can_gc: CanGc) -> bool { self.ScrollHeight(can_gc) > self.ClientHeight(can_gc) || self.ScrollWidth(can_gc) > self.ClientWidth(can_gc) } pub(crate) fn shadow_root(&self) -> Option> { self.rare_data() .as_ref()? .shadow_root .as_ref() .map(|sr| DomRoot::from_ref(&**sr)) } pub fn is_shadow_host(&self) -> bool { self.shadow_root().is_some() } /// pub fn attach_shadow( &self, // TODO: remove is_ua_widget argument is_ua_widget: IsUserAgentWidget, mode: ShadowRootMode, clonable: bool, ) -> Fallible> { // Step 1. if self.namespace != ns!(html) { return Err(Error::NotSupported); } // Step 2. match self.local_name() { &local_name!("article") | &local_name!("aside") | &local_name!("blockquote") | &local_name!("body") | &local_name!("div") | &local_name!("footer") | &local_name!("h1") | &local_name!("h2") | &local_name!("h3") | &local_name!("h4") | &local_name!("h5") | &local_name!("h6") | &local_name!("header") | &local_name!("main") | &local_name!("nav") | &local_name!("p") | &local_name!("section") | &local_name!("span") => {}, &local_name!("video") | &local_name!("audio") if is_ua_widget == IsUserAgentWidget::Yes => {}, _ => return Err(Error::NotSupported), }; // Step 3. if self.is_shadow_host() { return Err(Error::InvalidState); } // Steps 4, 5 and 6. let shadow_root = ShadowRoot::new(self, &self.node.owner_doc(), mode, clonable); self.ensure_rare_data().shadow_root = Some(Dom::from_ref(&*shadow_root)); shadow_root .upcast::() .set_containing_shadow_root(Some(&shadow_root)); let bind_context = BindContext { tree_connected: self.upcast::().is_connected(), tree_in_doc: self.upcast::().is_in_doc(), }; shadow_root.bind_to_tree(&bind_context); self.upcast::().dirty(NodeDamage::OtherNodeDamage); Ok(shadow_root) } pub fn detach_shadow(&self) { if let Some(ref shadow_root) = self.shadow_root() { self.upcast::().note_dirty_descendants(); shadow_root.detach(); self.ensure_rare_data().shadow_root = None; } else { debug_assert!(false, "Trying to detach a non-attached shadow root"); } } // https://html.spec.whatwg.org/multipage/#translation-mode pub fn is_translate_enabled(&self) -> bool { let name = &html5ever::local_name!("translate"); if self.has_attribute(name) { match_ignore_ascii_case! { &*self.get_string_attribute(name), "yes" | "" => return true, "no" => return false, _ => {}, } } if let Some(parent) = self.upcast::().GetParentNode() { if let Some(elem) = parent.downcast::() { return elem.is_translate_enabled(); } } true } // https://html.spec.whatwg.org/multipage/#the-directionality pub fn directionality(&self) -> String { self.downcast::() .and_then(|html_element| html_element.directionality()) .unwrap_or_else(|| { let node = self.upcast::(); node.parent_directionality() }) } pub(crate) fn is_root(&self) -> bool { match self.node.GetParentNode() { None => false, Some(node) => node.is::(), } } } #[inline] pub fn get_attr_for_layout<'dom>( elem: LayoutDom<'dom, Element>, namespace: &Namespace, name: &LocalName, ) -> Option> { elem.attrs() .iter() .find(|attr| name == attr.local_name() && namespace == attr.namespace()) .cloned() } pub trait LayoutElementHelpers<'dom> { fn attrs(self) -> &'dom [LayoutDom<'dom, Attr>]; fn has_class_for_layout(self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool; fn get_classes_for_layout(self) -> Option<&'dom [Atom]>; fn synthesize_presentational_hints_for_legacy_attributes(self, hints: &mut V) where V: Push; fn get_span(self) -> Option; fn get_colspan(self) -> Option; fn get_rowspan(self) -> Option; fn is_html_element(&self) -> bool; fn id_attribute(self) -> *const Option; fn style_attribute(self) -> *const Option>>; fn local_name(self) -> &'dom LocalName; fn namespace(self) -> &'dom Namespace; fn get_lang_for_layout(self) -> String; fn get_state_for_layout(self) -> ElementState; fn insert_selector_flags(self, flags: ElementSelectorFlags); fn get_selector_flags(self) -> ElementSelectorFlags; /// The shadow root this element is a host of. fn get_shadow_root_for_layout(self) -> Option>; fn get_attr_for_layout( self, namespace: &Namespace, name: &LocalName, ) -> Option<&'dom AttrValue>; fn get_attr_val_for_layout(self, namespace: &Namespace, name: &LocalName) -> Option<&'dom str>; fn get_attr_vals_for_layout(self, name: &LocalName) -> Vec<&'dom AttrValue>; } impl<'dom> LayoutDom<'dom, Element> { pub(super) fn focus_state(self) -> bool { self.unsafe_get().state.get().contains(ElementState::FOCUS) } } impl<'dom> LayoutElementHelpers<'dom> for LayoutDom<'dom, Element> { #[allow(unsafe_code)] #[inline] fn attrs(self) -> &'dom [LayoutDom<'dom, Attr>] { unsafe { LayoutDom::to_layout_slice(self.unsafe_get().attrs.borrow_for_layout()) } } #[inline] fn has_class_for_layout(self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { get_attr_for_layout(self, &ns!(), &local_name!("class")).is_some_and(|attr| { attr.to_tokens() .unwrap() .iter() .any(|atom| case_sensitivity.eq_atom(atom, name)) }) } #[inline] fn get_classes_for_layout(self) -> Option<&'dom [Atom]> { get_attr_for_layout(self, &ns!(), &local_name!("class")) .map(|attr| attr.to_tokens().unwrap()) } fn synthesize_presentational_hints_for_legacy_attributes(self, hints: &mut V) where V: Push, { // FIXME(emilio): Just a single PDB should be enough. #[inline] fn from_declaration( shared_lock: &SharedRwLock, declaration: PropertyDeclaration, ) -> ApplicableDeclarationBlock { ApplicableDeclarationBlock::from_declarations( Arc::new(shared_lock.wrap(PropertyDeclarationBlock::with_one( declaration, Importance::Normal, ))), CascadeLevel::PresHints, LayerOrder::root(), ) } let document = self.upcast::().owner_doc_for_layout(); let shared_lock = document.style_shared_lock(); let bgcolor = if let Some(this) = self.downcast::() { this.get_background_color() } else if let Some(this) = self.downcast::() { this.get_background_color() } else if let Some(this) = self.downcast::() { this.get_background_color() } else if let Some(this) = self.downcast::() { this.get_background_color() } else if let Some(this) = self.downcast::() { this.get_background_color() } else { None }; if let Some(color) = bgcolor { hints.push(from_declaration( shared_lock, PropertyDeclaration::BackgroundColor(specified::Color::from_absolute_color(color)), )); } let background = if let Some(this) = self.downcast::() { this.get_background() } else { None }; if let Some(url) = background { hints.push(from_declaration( shared_lock, PropertyDeclaration::BackgroundImage(background_image::SpecifiedValue( vec![specified::Image::for_cascade(url.get_arc())].into(), )), )); } let color = if let Some(this) = self.downcast::() { this.get_color() } else if let Some(this) = self.downcast::() { // https://html.spec.whatwg.org/multipage/#the-page:the-body-element-20 this.get_color() } else if let Some(this) = self.downcast::() { // https://html.spec.whatwg.org/multipage/#the-hr-element-2:presentational-hints-5 this.get_color() } else { None }; if let Some(color) = color { hints.push(from_declaration( shared_lock, PropertyDeclaration::Color(longhands::color::SpecifiedValue( specified::Color::from_absolute_color(color), )), )); } let font_face = if let Some(this) = self.downcast::() { this.get_face() } else { None }; if let Some(font_face) = font_face { hints.push(from_declaration( shared_lock, PropertyDeclaration::FontFamily(font_family::SpecifiedValue::Values( computed::font::FontFamilyList { list: ArcSlice::from_iter( HTMLFontElement::parse_face_attribute(font_face).into_iter(), ), }, )), )); } let font_size = self .downcast::() .and_then(|this| this.get_size()); if let Some(font_size) = font_size { hints.push(from_declaration( shared_lock, PropertyDeclaration::FontSize(font_size::SpecifiedValue::from_html_size( font_size as u8, )), )) } let cellspacing = if let Some(this) = self.downcast::() { this.get_cellspacing() } else { None }; if let Some(cellspacing) = cellspacing { let width_value = specified::Length::from_px(cellspacing as f32); hints.push(from_declaration( shared_lock, PropertyDeclaration::BorderSpacing(Box::new(border_spacing::SpecifiedValue::new( width_value.clone().into(), width_value.into(), ))), )); } let size = if let Some(this) = self.downcast::() { // FIXME(pcwalton): More use of atoms, please! match self.get_attr_val_for_layout(&ns!(), &local_name!("type")) { // Not text entry widget Some("hidden") | Some("date") | Some("month") | Some("week") | Some("time") | Some("datetime-local") | Some("number") | Some("range") | Some("color") | Some("checkbox") | Some("radio") | Some("file") | Some("submit") | Some("image") | Some("reset") | Some("button") => None, // Others _ => match this.size_for_layout() { 0 => None, s => Some(s as i32), }, } } else { None }; if let Some(size) = size { let value = specified::NoCalcLength::ServoCharacterWidth(specified::CharacterWidth(size)); hints.push(from_declaration( shared_lock, PropertyDeclaration::Width(specified::Size::LengthPercentage(NonNegative( specified::LengthPercentage::Length(value), ))), )); } let width = if let Some(this) = self.downcast::() { this.get_width() } else if let Some(this) = self.downcast::() { this.get_width() } else if let Some(this) = self.downcast::() { this.get_width() } else if let Some(this) = self.downcast::() { this.get_width() } else if let Some(this) = self.downcast::() { this.get_width() } else if let Some(this) = self.downcast::() { this.get_width() } else if let Some(this) = self.downcast::() { // https://html.spec.whatwg.org/multipage/#the-hr-element-2:attr-hr-width this.get_width() } else { LengthOrPercentageOrAuto::Auto }; // FIXME(emilio): Use from_computed value here and below. match width { LengthOrPercentageOrAuto::Auto => {}, LengthOrPercentageOrAuto::Percentage(percentage) => { let width_value = specified::Size::LengthPercentage(NonNegative( specified::LengthPercentage::Percentage(computed::Percentage(percentage)), )); hints.push(from_declaration( shared_lock, PropertyDeclaration::Width(width_value), )); }, LengthOrPercentageOrAuto::Length(length) => { let width_value = specified::Size::LengthPercentage(NonNegative( specified::LengthPercentage::Length(specified::NoCalcLength::Absolute( specified::AbsoluteLength::Px(length.to_f32_px()), )), )); hints.push(from_declaration( shared_lock, PropertyDeclaration::Width(width_value), )); }, } let height = if let Some(this) = self.downcast::() { this.get_height() } else if let Some(this) = self.downcast::() { this.get_height() } else if let Some(this) = self.downcast::() { this.get_height() } else if let Some(this) = self.downcast::() { this.get_height() } else if let Some(this) = self.downcast::() { this.get_height() } else if let Some(this) = self.downcast::() { this.get_height() } else if let Some(this) = self.downcast::() { this.get_height() } else { LengthOrPercentageOrAuto::Auto }; match height { LengthOrPercentageOrAuto::Auto => {}, LengthOrPercentageOrAuto::Percentage(percentage) => { let height_value = specified::Size::LengthPercentage(NonNegative( specified::LengthPercentage::Percentage(computed::Percentage(percentage)), )); hints.push(from_declaration( shared_lock, PropertyDeclaration::Height(height_value), )); }, LengthOrPercentageOrAuto::Length(length) => { let height_value = specified::Size::LengthPercentage(NonNegative( specified::LengthPercentage::Length(specified::NoCalcLength::Absolute( specified::AbsoluteLength::Px(length.to_f32_px()), )), )); hints.push(from_declaration( shared_lock, PropertyDeclaration::Height(height_value), )); }, } // Aspect ratio when providing both width and height. // https://html.spec.whatwg.org/multipage/#attributes-for-embedded-content-and-images if self.downcast::().is_some() || self.downcast::().is_some() { if let LengthOrPercentageOrAuto::Length(width) = width { if let LengthOrPercentageOrAuto::Length(height) = height { let width_value = NonNegative(specified::Number::new(width.to_f32_px())); let height_value = NonNegative(specified::Number::new(height.to_f32_px())); let aspect_ratio = specified::position::AspectRatio { auto: true, ratio: PreferredRatio::Ratio(Ratio(width_value, height_value)), }; hints.push(from_declaration( shared_lock, PropertyDeclaration::AspectRatio(aspect_ratio), )); } } } let cols = if let Some(this) = self.downcast::() { match this.get_cols() { 0 => None, c => Some(c as i32), } } else { None }; if let Some(cols) = cols { // TODO(mttr) ServoCharacterWidth uses the size math for , but // the math for