Skip to main content

script/dom/mathml/
mathmlelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use dom_struct::dom_struct;
6use html5ever::{LocalName, Prefix, local_name, ns};
7use js::context::JSContext;
8use js::rust::HandleObject;
9use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
10use script_bindings::codegen::GenericBindings::WindowBinding::ScrollBehavior;
11use script_bindings::str::DOMString;
12use style::parser::ParserContext;
13use style::properties::{PropertyDeclaration, longhands};
14use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
15use style::values::specified::FontSize;
16use style_traits::ParsingMode;
17use stylo_dom::ElementState;
18
19use crate::dom::bindings::codegen::Bindings::HTMLOrSVGOrMathMLElementBinding::FocusOptions;
20use crate::dom::bindings::codegen::Bindings::MathMLElementBinding::MathMLElementMethods;
21use crate::dom::bindings::inheritance::Castable;
22use crate::dom::bindings::root::{DomRoot, LayoutDom};
23use crate::dom::document::Document;
24use crate::dom::document::focus::FocusableArea;
25use crate::dom::element::attributes::storage::AttrRef;
26use crate::dom::element::{AttributeMutation, Element};
27use crate::dom::node::focus::FocusTrigger;
28use crate::dom::node::virtualmethods::VirtualMethods;
29use crate::dom::node::{Node, NodeTraits};
30use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollRequirement};
31
32#[dom_struct]
33pub(crate) struct MathMLElement {
34    element: Element,
35}
36
37/// <https://w3c.github.io/mathml-core/#dom-mathmlelement>
38impl MathMLElement {
39    pub(crate) fn new_inherited(
40        tag_name: LocalName,
41        prefix: Option<Prefix>,
42        document: &Document,
43    ) -> MathMLElement {
44        MathMLElement {
45            element: Element::new_inherited_with_state(
46                ElementState::empty(),
47                tag_name,
48                ns!(mathml),
49                prefix,
50                document,
51            ),
52        }
53    }
54
55    pub(crate) fn new(
56        cx: &mut JSContext,
57        tag_name: LocalName,
58        prefix: Option<Prefix>,
59        document: &Document,
60        proto: Option<HandleObject>,
61    ) -> DomRoot<MathMLElement> {
62        Node::reflect_node_with_proto(
63            cx,
64            Box::new(MathMLElement::new_inherited(tag_name, prefix, document)),
65            document,
66            proto,
67        )
68    }
69
70    fn as_element(&self) -> &Element {
71        self.upcast::<Element>()
72    }
73}
74
75impl VirtualMethods for MathMLElement {
76    fn super_type(&self) -> Option<&dyn VirtualMethods> {
77        Some(self.as_element() as &dyn VirtualMethods)
78    }
79
80    fn attribute_mutated(
81        &self,
82        cx: &mut JSContext,
83        attr: AttrRef<'_>,
84        mutation: AttributeMutation,
85    ) {
86        self.super_type()
87            .unwrap()
88            .attribute_mutated(cx, attr, mutation);
89
90        let element = self.as_element();
91        if let (&local_name!("nonce"), mutation) = (attr.local_name(), mutation) {
92            match mutation {
93                AttributeMutation::Set(..) => {
94                    let nonce = &**attr.value();
95                    element.update_nonce_internal_slot(nonce.to_owned(), cx.no_gc());
96                },
97                AttributeMutation::Removed => {
98                    element.update_nonce_internal_slot(String::new(), cx.no_gc());
99                },
100            }
101        }
102    }
103
104    fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
105        // TODO(tannal) Add `displaystyle` and `scriptlevel`.
106        match attr.local_name() {
107            &local_name!("mathcolor") |
108            &local_name!("mathbackground") |
109            &local_name!("mathsize") |
110            &local_name!("dir") => true,
111            _ => self
112                .super_type()
113                .unwrap()
114                .attribute_affects_presentational_hints(attr),
115        }
116    }
117}
118
119impl<'dom> LayoutDom<'dom, MathMLElement> {
120    /// Synthesizes presentational hints for MathML attributes that map directly to CSS properties.
121    /// <https://w3c.github.io/mathml-core/#css-styling>
122    /// <https://w3c.github.io/mathml-core/#global-attributes>
123    pub(crate) fn synthesize_presentational_hints(
124        self,
125        document: LayoutDom<'dom, Document>,
126        push: &mut impl FnMut(PropertyDeclaration),
127    ) {
128        let url_data = UrlExtraData(document.url_for_layout().get_arc());
129        let parsing_mode =
130            ParsingMode::ALLOW_UNITLESS_LENGTH | ParsingMode::ALLOW_ALL_NUMERIC_VALUES;
131
132        let parser_context = ParserContext::new(
133            Origin::Author,
134            &url_data,
135            Some(CssRuleType::Style),
136            parsing_mode,
137            document.quirks_mode(),
138            Default::default(),
139            None,
140            None,
141            Default::default(),
142        );
143
144        // Standard MathML Core presentational hints
145        // TODO(tannal) Add `displaystyle` and `scriptlevel`.
146        // https://w3c.github.io/mathml-core/#dfn-mathcolor
147        // https://w3c.github.io/mathml-core/#dfn-mathbackground
148        // > In that case, the user agent is expected to treat
149        // > these attributes as a presentational hint setting the element's
150        // > color and background-color properties to the corresponding values.
151        self.parse_mathml_attribute(
152            &parser_context,
153            "mathcolor",
154            longhands::color::parse_declared,
155            push,
156        );
157        self.parse_mathml_attribute(
158            &parser_context,
159            "mathbackground",
160            longhands::background_color::parse_declared,
161            push,
162        );
163
164        // https://w3c.github.io/mathml-core/#dfn-mathsize
165        // > In that case, the user agent is expected to treat
166        // > the attribute as a presentational hint setting the element's
167        // > font-size property to the corresponding value.
168        // We should only accept <length-percentage> value.
169        self.parse_mathml_attribute_matching(
170            &parser_context,
171            "mathsize",
172            |decl| {
173                matches!(
174                    decl,
175                    PropertyDeclaration::FontSize(size)
176                        if matches!(size, FontSize::Length(..))
177                )
178            },
179            longhands::font_size::parse_declared,
180            push,
181        );
182        // https://w3c.github.io/mathml-core/#dfn-dir
183        // > if present, must be an ASCII case-insensitive match to ltr or rtl.
184        // > In that case, the user agent is expected to treat
185        // > the attribute as a presentational hint setting the element's
186        // > direction property to the corresponding value.
187        self.parse_mathml_attribute(
188            &parser_context,
189            "dir",
190            longhands::direction::parse_declared,
191            push,
192        );
193    }
194
195    fn parse_mathml_attribute<F>(
196        self,
197        parser_context: &ParserContext,
198        attr_name: &str,
199        parse: F,
200        push: &mut impl FnMut(PropertyDeclaration),
201    ) where
202        F: for<'i> FnOnce(
203            &ParserContext,
204            &mut cssparser::Parser<'i>,
205        ) -> Result<PropertyDeclaration, style_traits::ParseError>,
206    {
207        self.parse_mathml_attribute_matching(parser_context, attr_name, |_| true, parse, push)
208    }
209
210    fn parse_mathml_attribute_matching<P, F>(
211        self,
212        parser_context: &ParserContext,
213        attr_name: &str,
214        predicate: P,
215        parse: F,
216        push: &mut impl FnMut(PropertyDeclaration),
217    ) where
218        P: FnOnce(&PropertyDeclaration) -> bool,
219        F: for<'i> FnOnce(
220            &ParserContext,
221            &mut cssparser::Parser<'i>,
222        ) -> Result<PropertyDeclaration, style_traits::ParseError>,
223    {
224        let element = self.upcast::<Element>();
225        if let Some(value) = element.get_attr_val_for_layout(&ns!(), &LocalName::from(attr_name)) {
226            let mut parser = cssparser::Parser::new(value);
227            if let Ok(property) =
228                parser.parse_entirely(|parse_input| parse(parser_context, parse_input)) &&
229                predicate(&property)
230            {
231                push(property);
232            }
233        }
234    }
235}
236
237impl MathMLElementMethods<crate::DomTypeHolder> for MathMLElement {
238    global_event_handlers!();
239
240    /// <https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce>
241    fn Nonce(&self) -> DOMString {
242        self.as_element().nonce_value().into()
243    }
244
245    /// <https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce>
246    fn SetNonce(&self, cx: &mut JSContext, value: DOMString) {
247        self.as_element()
248            .update_nonce_internal_slot(String::from(value), cx.no_gc());
249    }
250
251    // <https://html.spec.whatwg.org/multipage/#dom-fe-autofocus>
252    make_bool_getter!(Autofocus, "autofocus");
253
254    // <https://html.spec.whatwg.org/multipage/#dom-fe-autofocus>
255    make_bool_setter!(SetAutofocus, "autofocus");
256
257    /// <https://html.spec.whatwg.org/multipage/#dom-focus>
258    fn Focus(&self, cx: &mut JSContext, options: &FocusOptions) {
259        if !self
260            .upcast::<Node>()
261            .run_the_focusing_steps(cx, None, FocusTrigger::Other)
262        {
263            return;
264        }
265
266        if !options.preventScroll {
267            let scroll_axis = ScrollAxisState {
268                position: ScrollLogicalPosition::Center,
269                requirement: ScrollRequirement::IfNotVisible,
270            };
271            self.upcast::<Element>().scroll_into_view_with_options(
272                cx,
273                ScrollBehavior::Smooth,
274                scroll_axis,
275                scroll_axis,
276                None,
277                None,
278            );
279        }
280    }
281
282    /// <https://html.spec.whatwg.org/multipage/#dom-blur>
283    fn Blur(&self, cx: &mut JSContext) {
284        if !self.as_element().focus_state() {
285            return;
286        }
287        self.owner_document()
288            .focus_handler()
289            .focus(cx, &FocusableArea::Viewport);
290    }
291
292    /// <https://html.spec.whatwg.org/multipage/#dom-tabindex>
293    fn TabIndex(&self) -> i32 {
294        self.element.tab_index()
295    }
296
297    /// <https://html.spec.whatwg.org/multipage/#dom-tabindex>
298    fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
299        self.element
300            .set_attribute(cx, &local_name!("tabindex"), tab_index.into());
301    }
302}