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