script/dom/svg/
svgelement.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 stylo_dom::ElementState;
13
14use crate::dom::attr::Attr;
15use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
16use crate::dom::bindings::codegen::Bindings::SVGElementBinding::SVGElementMethods;
17use crate::dom::bindings::inheritance::Castable;
18use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
19use crate::dom::css::cssstyledeclaration::{
20    CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner,
21};
22use crate::dom::document::Document;
23use crate::dom::document::focus::FocusableArea;
24use crate::dom::element::{AttributeMutation, Element};
25use crate::dom::node::{Node, NodeTraits};
26use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement};
27use crate::dom::virtualmethods::VirtualMethods;
28use crate::script_runtime::CanGc;
29
30#[dom_struct]
31pub(crate) struct SVGElement {
32    element: Element,
33    style_decl: MutNullableDom<CSSStyleDeclaration>,
34}
35
36impl SVGElement {
37    fn new_inherited(
38        tag_name: LocalName,
39        prefix: Option<Prefix>,
40        document: &Document,
41    ) -> SVGElement {
42        SVGElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
43    }
44
45    pub(crate) fn new_inherited_with_state(
46        state: ElementState,
47        tag_name: LocalName,
48        prefix: Option<Prefix>,
49        document: &Document,
50    ) -> SVGElement {
51        SVGElement {
52            element: Element::new_inherited_with_state(state, tag_name, ns!(svg), prefix, document),
53            style_decl: Default::default(),
54        }
55    }
56
57    pub(crate) fn new(
58        cx: &mut js::context::JSContext,
59        tag_name: LocalName,
60        prefix: Option<Prefix>,
61        document: &Document,
62        proto: Option<HandleObject>,
63    ) -> DomRoot<SVGElement> {
64        Node::reflect_node_with_proto(
65            cx,
66            Box::new(SVGElement::new_inherited(tag_name, prefix, document)),
67            document,
68            proto,
69        )
70    }
71
72    fn as_element(&self) -> &Element {
73        self.upcast::<Element>()
74    }
75}
76
77impl VirtualMethods for SVGElement {
78    fn super_type(&self) -> Option<&dyn VirtualMethods> {
79        Some(self.as_element() as &dyn VirtualMethods)
80    }
81
82    fn attribute_mutated(
83        &self,
84        cx: &mut js::context::JSContext,
85        attr: &Attr,
86        mutation: AttributeMutation,
87    ) {
88        self.super_type()
89            .unwrap()
90            .attribute_mutated(cx, attr, mutation);
91        let element = self.as_element();
92        if let (&local_name!("nonce"), mutation) = (attr.local_name(), mutation) {
93            match mutation {
94                AttributeMutation::Set(..) => {
95                    let nonce = &**attr.value();
96                    element.update_nonce_internal_slot(nonce.to_owned());
97                },
98                AttributeMutation::Removed => {
99                    element.update_nonce_internal_slot(String::new());
100                },
101            }
102        }
103    }
104}
105
106impl SVGElementMethods<crate::DomTypeHolder> for SVGElement {
107    /// <https://html.spec.whatwg.org/multipage/#the-style-attribute>
108    fn Style(&self) -> DomRoot<CSSStyleDeclaration> {
109        self.style_decl.or_init(|| {
110            let global = self.owner_window();
111            CSSStyleDeclaration::new(
112                &global,
113                CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
114                None,
115                CSSModificationAccess::ReadWrite,
116                CanGc::deprecated_note(),
117            )
118        })
119    }
120
121    // https://html.spec.whatwg.org/multipage/#globaleventhandlers
122    global_event_handlers!();
123
124    /// <https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce>
125    fn Nonce(&self) -> DOMString {
126        self.as_element().nonce_value().into()
127    }
128
129    /// <https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce>
130    fn SetNonce(&self, _cx: &mut JSContext, value: DOMString) {
131        self.as_element()
132            .update_nonce_internal_slot(value.to_string())
133    }
134
135    /// <https://html.spec.whatwg.org/multipage/#dom-fe-autofocus>
136    fn Autofocus(&self) -> bool {
137        self.element.has_attribute(&local_name!("autofocus"))
138    }
139
140    /// <https://html.spec.whatwg.org/multipage/#dom-fe-autofocus>
141    fn SetAutofocus(&self, cx: &mut JSContext, autofocus: bool) {
142        self.element
143            .set_bool_attribute(&local_name!("autofocus"), autofocus, CanGc::from_cx(cx));
144    }
145
146    /// <https://html.spec.whatwg.org/multipage/#dom-focus>
147    fn Focus(&self, cx: &mut js::context::JSContext, options: &FocusOptions) {
148        // 1. If the allow focus steps given this's node document return false, then return.
149        // TODO: Implement this.
150
151        // 2. Run the focusing steps for this.
152        if !self.upcast::<Node>().run_the_focusing_steps(cx, None) {
153            // The specification seems to imply we should scroll into view even if this element
154            // is not a focusable area. No browser does this, so we return early in that case.
155            // See https://github.com/whatwg/html/issues/12231.
156            return;
157        }
158
159        // > 3. If options["focusVisible"] is true, or does not exist but in an
160        // >    implementation-defined  way the user agent determines it would be best to do so,
161        // >    then indicate focus. TODO: Implement this.
162        // TODO: Implement this.
163
164        // > 4. If options["preventScroll"] is false, then scroll a target into view given this,
165        // >    "auto", "center", and "center".
166        if !options.preventScroll {
167            let scroll_axis = ScrollAxisState {
168                position: ScrollLogicalPosition::Center,
169                requirement: ScrollRequirement::IfNotVisible,
170            };
171            self.upcast::<Element>().scroll_into_view_with_options(
172                ScrollBehavior::Smooth,
173                scroll_axis,
174                scroll_axis,
175                None,
176                None,
177            );
178        }
179    }
180
181    /// <https://html.spec.whatwg.org/multipage/#dom-blur>
182    fn Blur(&self, cx: &mut js::context::JSContext) {
183        // TODO: Run the unfocusing steps. Focus the top-level document, not
184        //       the current document.
185        if !self.as_element().focus_state() {
186            return;
187        }
188        // <https://html.spec.whatwg.org/multipage/#unfocusing-steps>
189        self.owner_document()
190            .focus_handler()
191            .focus(cx, FocusableArea::Viewport);
192    }
193
194    /// <https://html.spec.whatwg.org/multipage/#dom-tabindex>
195    fn TabIndex(&self) -> i32 {
196        self.element.tab_index()
197    }
198
199    /// <https://html.spec.whatwg.org/multipage/#dom-tabindex>
200    fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
201        self.element
202            .set_int_attribute(&local_name!("tabindex"), tab_index, CanGc::from_cx(cx));
203    }
204}