script/dom/html/
htmldialogelement.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/. */
4use std::cell::Cell;
5
6use dom_struct::dom_struct;
7use html5ever::{LocalName, Prefix, local_name, ns};
8use js::rust::HandleObject;
9use script_bindings::error::{Error, ErrorResult};
10
11use crate::dom::bindings::cell::DomRefCell;
12use crate::dom::bindings::codegen::Bindings::HTMLDialogElementBinding::HTMLDialogElementMethods;
13use crate::dom::bindings::inheritance::Castable;
14use crate::dom::bindings::root::DomRoot;
15use crate::dom::bindings::str::DOMString;
16use crate::dom::document::Document;
17use crate::dom::element::Element;
18use crate::dom::event::{Event, EventBubbles, EventCancelable};
19use crate::dom::eventtarget::EventTarget;
20use crate::dom::html::htmlelement::HTMLElement;
21use crate::dom::node::{Node, NodeTraits};
22use crate::dom::toggleevent::ToggleEvent;
23use crate::script_runtime::CanGc;
24
25#[dom_struct]
26pub(crate) struct HTMLDialogElement {
27    htmlelement: HTMLElement,
28    return_value: DomRefCell<DOMString>,
29    is_modal: Cell<bool>,
30}
31
32impl HTMLDialogElement {
33    fn new_inherited(
34        local_name: LocalName,
35        prefix: Option<Prefix>,
36        document: &Document,
37    ) -> HTMLDialogElement {
38        HTMLDialogElement {
39            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
40            return_value: DomRefCell::new(DOMString::new()),
41            is_modal: Cell::new(false),
42        }
43    }
44
45    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
46    pub(crate) fn new(
47        local_name: LocalName,
48        prefix: Option<Prefix>,
49        document: &Document,
50        proto: Option<HandleObject>,
51        can_gc: CanGc,
52    ) -> DomRoot<HTMLDialogElement> {
53        Node::reflect_node_with_proto(
54            Box::new(HTMLDialogElement::new_inherited(
55                local_name, prefix, document,
56            )),
57            document,
58            proto,
59            can_gc,
60        )
61    }
62
63    /// <https://html.spec.whatwg.org/multipage/#show-a-modal-dialog>
64    pub fn show_a_modal(&self, source: Option<DomRoot<Element>>, can_gc: CanGc) -> ErrorResult {
65        let subject = self.upcast::<Element>();
66        // Step 1. If subject has an open attribute and is modal of subject is true, then return.
67        if subject.has_attribute(&local_name!("open")) && self.is_modal.get() {
68            return Ok(());
69        }
70
71        // Step 2. If subject has an open attribute, then throw an "InvalidStateError" DOMException.
72        if subject.has_attribute(&local_name!("open")) {
73            return Err(Error::InvalidState(None));
74        }
75
76        // Step 3. If subject's node document is not fully active, then throw an "InvalidStateError" DOMException.
77        if !subject.owner_document().is_fully_active() {
78            return Err(Error::InvalidState(None));
79        }
80
81        // Step 4. If subject is not connected, then throw an "InvalidStateError" DOMException.
82        if !subject.is_connected() {
83            return Err(Error::InvalidState(None));
84        }
85
86        // TODO: Step 5. If subject is in the popover showing state, then throw an "InvalidStateError" DOMException.
87
88        // Step 6. If the result of firing an event named beforetoggle, using ToggleEvent, with the cancelable attribute initialized to true, the oldState attribute initialized to "closed", the newState attribute initialized to "open", and the source attribute initialized to source at subject is false, then return.
89        let event = ToggleEvent::new(
90            &self.owner_window(),
91            atom!("beforetoggle"),
92            EventBubbles::DoesNotBubble,
93            EventCancelable::Cancelable,
94            DOMString::from("closed"),
95            DOMString::from("open"),
96            source,
97            can_gc,
98        );
99        let event = event.upcast::<Event>();
100        if !event.fire(self.upcast::<EventTarget>(), can_gc) {
101            return Ok(());
102        }
103
104        // Step 7. If subject has an open attribute, then return.
105        if subject.has_attribute(&local_name!("open")) {
106            return Ok(());
107        }
108
109        // Step 8. If subject is not connected, then return.
110        if !subject.is_connected() {
111            return Ok(());
112        }
113
114        // TODO: Step 9. If subject is in the popover showing state, then return.
115
116        // TODO: Step 10. Queue a dialog toggle event task given subject, "closed", "open", and source.
117
118        // Step 11. Add an open attribute to subject, whose value is the empty string.
119        subject.set_bool_attribute(&local_name!("open"), true, can_gc);
120
121        // TODO: Step 12. Assert: subject's close watcher is not null.
122
123        // Step 13. Set is modal of subject to true.
124        self.is_modal.set(true);
125
126        // TODO: Step 14. Set subject's node document to be blocked by the modal dialog subject.
127
128        // TODO: Step 15. If subject's node document's top layer does not already contain subject, then add an element to the top layer given subject.
129
130        // TODO: Step 16. Set subject's previously focused element to the focused element.
131
132        // TODO: Step 17. Let document be subject's node document.
133
134        // TODO: Step 18. Let hideUntil be the result of running topmost popover ancestor given subject, document's showing hint popover list, null, and false.
135
136        // TODO: Step 19. If hideUntil is null, then set hideUntil to the result of running topmost popover ancestor given subject, document's showing auto popover list, null, and false.
137
138        // TODO: Step 20. If hideUntil is null, then set hideUntil to document.
139
140        // TODO: Step 21. Run hide all popovers until given hideUntil, false, and true.
141
142        // TODO(Issue #32702): Step 22. Run the dialog focusing steps given subject.
143        Ok(())
144    }
145}
146
147impl HTMLDialogElementMethods<crate::DomTypeHolder> for HTMLDialogElement {
148    // https://html.spec.whatwg.org/multipage/#dom-dialog-open
149    make_bool_getter!(Open, "open");
150
151    // https://html.spec.whatwg.org/multipage/#dom-dialog-open
152    make_bool_setter!(SetOpen, "open");
153
154    /// <https://html.spec.whatwg.org/multipage/#dom-dialog-returnvalue>
155    fn ReturnValue(&self) -> DOMString {
156        let return_value = self.return_value.borrow();
157        return_value.clone()
158    }
159
160    /// <https://html.spec.whatwg.org/multipage/#dom-dialog-returnvalue>
161    fn SetReturnValue(&self, return_value: DOMString) {
162        *self.return_value.borrow_mut() = return_value;
163    }
164
165    /// <https://html.spec.whatwg.org/multipage/#dom-dialog-show>
166    fn Show(&self, can_gc: CanGc) -> ErrorResult {
167        let element = self.upcast::<Element>();
168        // Step 1. If this has an open attribute and is modal of this is false, then return.
169        if element.has_attribute(&local_name!("open")) && !self.is_modal.get() {
170            return Ok(());
171        }
172
173        // Step 2. If this has an open attribute, then throw an "InvalidStateError" DOMException.
174        if element.has_attribute(&local_name!("open")) {
175            return Err(Error::InvalidState(None));
176        }
177
178        // Step 3. If the result of firing an event named beforetoggle, using ToggleEvent, with the cancelable attribute initialized to true, the oldState attribute initialized to "closed", and the newState attribute initialized to "open" at this is false, then return.
179        let event = ToggleEvent::new(
180            &self.owner_window(),
181            atom!("beforetoggle"),
182            EventBubbles::DoesNotBubble,
183            EventCancelable::Cancelable,
184            DOMString::from("closed"),
185            DOMString::from("open"),
186            None,
187            can_gc,
188        );
189        let event = event.upcast::<Event>();
190        if !event.fire(self.upcast::<EventTarget>(), can_gc) {
191            return Ok(());
192        }
193
194        // Step 4. If this has an open attribute, then return.
195        if element.has_attribute(&local_name!("open")) {
196            return Ok(());
197        }
198
199        // TODO: Step 5. Queue a dialog toggle event task given this, "closed", "open", and null.
200
201        // Step 6. Add an open attribute to this, whose value is the empty string.
202        element.set_bool_attribute(&local_name!("open"), true, can_gc);
203
204        // TODO: Step 7. Set this's previously focused element to the focused element.
205
206        // TODO: Step 8. Let document be this's node document.
207
208        // TODO: Step 9. Let hideUntil be the result of running topmost popover ancestor given this, document's showing hint popover list, null, and false.
209
210        // TODO: Step 10. If hideUntil is null, then set hideUntil to the result of running topmost popover ancestor given this, document's showing auto popover list, null, and false.
211
212        // TODO: Step 11. If hideUntil is null, then set hideUntil to document.
213
214        // TODO: Step 12. Run hide all popovers until given hideUntil, false, and true.
215
216        // TODO(Issue #32702): Step 13. Run the dialog focusing steps given this.
217
218        Ok(())
219    }
220
221    /// <https://html.spec.whatwg.org/multipage/#dom-dialog-showmodal>
222    fn ShowModal(&self, can_gc: CanGc) -> ErrorResult {
223        // The showModal() method steps are to show a modal dialog given this and null.
224        self.show_a_modal(None, can_gc)
225    }
226
227    /// <https://html.spec.whatwg.org/multipage/#dom-dialog-close>
228    fn Close(&self, return_value: Option<DOMString>, can_gc: CanGc) {
229        let element = self.upcast::<Element>();
230        let target = self.upcast::<EventTarget>();
231
232        // Step 1 & 2
233        if element
234            .remove_attribute(&ns!(), &local_name!("open"), can_gc)
235            .is_none()
236        {
237            return;
238        }
239
240        // Step 8. Set is modal of subject to false.
241        self.is_modal.set(false);
242
243        // Step 9. If result is not null, then set subject's returnValue attribute to result.
244        if let Some(new_value) = return_value {
245            *self.return_value.borrow_mut() = new_value;
246        }
247
248        // TODO: Step 4 implement pending dialog stack removal
249
250        // Step 13. Queue an element task on the user interaction task source given the subject element to fire an event named close at subject.
251        self.owner_global()
252            .task_manager()
253            .dom_manipulation_task_source()
254            .queue_simple_event(target, atom!("close"));
255    }
256}