Skip to main content

script/dom/closewatcher/
internalclosewatcher.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 std::cell::Cell;
6use std::ptr;
7use std::rc::Rc;
8
9use js::context::{JSContext, NoGC};
10use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
11use script_bindings::inheritance::Castable;
12use script_bindings::root::Dom;
13
14use crate::dom::CloseWatcher;
15use crate::dom::bindings::reflector::DomGlobal;
16use crate::dom::event::{Event, EventBubbles, EventCancelable};
17use crate::dom::eventtarget::EventTarget;
18use crate::dom::htmldialogelement::HTMLDialogElement;
19use crate::dom::window::Window;
20
21/// <https://html.spec.whatwg.org/multipage/#close-watcher>
22#[derive(JSTraceable, MallocSizeOf, PartialEq)]
23#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
24#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
25pub(crate) struct InternalCloseWatcher {
26    /// <https://html.spec.whatwg.org/multipage/#close-watcher-window>
27    window: Dom<Window>,
28    /// <https://html.spec.whatwg.org/multipage/#close-watcher-is-running-cancel>
29    is_running_cancel_action: Cell<bool>,
30
31    /// <https://html.spec.whatwg.org/multipage/#close-watcher-cancel-action>
32    /// <https://html.spec.whatwg.org/multipage/#close-watcher-close-action>
33    /// <https://html.spec.whatwg.org/multipage/#close-watcher-get-enabled-state>
34    owner: InternalCloseWatcherOwner,
35}
36
37impl InternalCloseWatcher {
38    pub(crate) fn new(window: &Window, owner: &InternalCloseWatcherOwner) -> Self {
39        Self {
40            window: Dom::from_ref(window),
41            is_running_cancel_action: Cell::new(false),
42            owner: owner.clone(),
43        }
44    }
45
46    /// <https://html.spec.whatwg.org/multipage/#close-watcher-active>
47    pub(crate) fn is_active(&self) -> bool {
48        // > A close watcher closeWatcher is active if closeWatcher's window's close watcher manager
49        // > contains any list which contains closeWatcher.
50        self.window
51            .close_watcher_manager()
52            .groups()
53            .iter()
54            .flatten()
55            .any(|watcher| ptr::eq(Rc::as_ptr(watcher), self))
56    }
57
58    /// <https://html.spec.whatwg.org/multipage/#establish-a-close-watcher>
59    pub(crate) fn establish(
60        no_gc: &NoGC,
61        window: &Window,
62        owner: &InternalCloseWatcherOwner,
63    ) -> Rc<InternalCloseWatcher> {
64        // Step 1. Assert: window's associated Document is fully active.
65        assert!(window.Document().is_fully_active());
66
67        // Step 2. Let closeWatcher be a new close watcher, with
68        // - window: window
69        // - cancel action: cancelAction
70        // - close action: closeAction
71        // - is running cancel action: false
72        // - get enabled state: getEnabledState
73        let close_watcher = Rc::new(Self::new(window, owner));
74
75        // Step 3. Let manager be window's close watcher manager.
76        let manager = window.close_watcher_manager();
77        let mut groups = manager.groups_mut(no_gc);
78
79        // Step 4. If manager's groups's size is less than manager's allowed number of groups, then
80        // append « closeWatcher » to manager's groups.
81        if groups.len() < manager.allowed_number_of_groups() {
82            groups.push(vec![close_watcher.clone()])
83        }
84        // Step 5. Otherwise:
85        else {
86            // Step 5.1. Assert: manager's groups's size is at least 1 in this branch, since
87            // manager's allowed number of groups is always at least 1.
88            assert!(!groups.is_empty());
89
90            // Step 5.2. Append closeWatcher to manager's groups's last item.
91            groups
92                .last_mut()
93                .expect("There is at least one group at this point")
94                .push(close_watcher.clone());
95        }
96
97        // Step 6. Set manager's next user interaction allows a new group to true.
98        manager.set_next_user_interaction_allows_a_new_group(true);
99
100        // Step 7. Return closeWatcher.
101        close_watcher
102    }
103
104    /// <https://html.spec.whatwg.org/multipage/#close-watcher-request-close>
105    pub(crate) fn request_to_close(
106        &self,
107        cx: &mut JSContext,
108        require_history_action_activation: bool,
109    ) -> bool {
110        // Step 1. If closeWatcher is not active, then return true.
111        if !self.is_active() {
112            return true;
113        }
114
115        // Step 2. If the result of running closeWatcher's get enabled state is false, then return
116        // true.
117        if !self.get_enabled_state() {
118            return true;
119        }
120
121        // Step 3. If closeWatcher's is running cancel action is true, then return true.
122        if self.is_running_cancel_action.get() {
123            return true;
124        }
125
126        // Step 4. Let window be closeWatcher's window.
127        let window = &self.window;
128
129        // Step 5. If window's associated Document is not fully active, then return true.
130        if !window.Document().is_fully_active() {
131            return true;
132        }
133
134        // Step 6. Let canPreventClose be true if requireHistoryActionActivation is false, or if
135        // window's close watcher manager's groups's size is less than window's close watcher
136        // manager's allowed number of groups, and window has history-action activation;
137        // otherwise false.
138        let can_prevent_close = !require_history_action_activation || {
139            let manager = window.close_watcher_manager();
140            manager.groups().len() < manager.allowed_number_of_groups() &&
141                window.has_history_action_activation()
142        };
143
144        // Step 7. Set closeWatcher's is running cancel action to true.
145        self.is_running_cancel_action.set(true);
146
147        // Step 8. Let shouldContinue be the result of running closeWatcher's cancel action given
148        // canPreventClose.
149        let should_continue = self.cancel_action(cx, can_prevent_close);
150
151        // Step 9. Set closeWatcher's is running cancel action to false.
152        self.is_running_cancel_action.set(false);
153
154        // Step 10. If shouldContinue is false, then:
155        if !should_continue {
156            // Step 10.1. Assert: canPreventClose is true.
157            assert!(can_prevent_close);
158
159            // Step 10.2. Consume history-action user activation given window.
160            window.consume_history_action_user_activation();
161
162            // Step 10.3. Return false.
163            return false;
164        }
165
166        // Step 11. Close closeWatcher.
167        self.close(cx);
168
169        // Step 12. Return true.
170        true
171    }
172
173    /// <https://html.spec.whatwg.org/multipage/#close-watcher-close>
174    pub(crate) fn close(&self, cx: &mut JSContext) {
175        // Step 1. If closeWatcher is not active, then return.
176        if !self.is_active() {
177            return;
178        }
179
180        // Step 2. If the result of running closeWatcher's get enabled state is false, then return.
181        if !self.get_enabled_state() {
182            return;
183        }
184
185        // Step 3. If closeWatcher's window's associated Document is not fully active, then return.
186        if !self.window.Document().is_fully_active() {
187            return;
188        }
189
190        // Step 4. Destroy closeWatcher.
191        self.destroy(cx.no_gc());
192
193        // Step 5. Run closeWatcher's close action.
194        self.close_action(cx)
195    }
196
197    /// <https://html.spec.whatwg.org/multipage/#close-watcher-destroy>
198    pub(crate) fn destroy(&self, no_gc: &NoGC) {
199        // Step 1. Let manager be closeWatcher's window's close watcher manager.
200        let manager = self.window.close_watcher_manager();
201        let mut groups = manager.groups_mut(no_gc);
202
203        // Step 2. For each group of manager's groups: remove closeWatcher from group.
204        for group in groups.iter_mut() {
205            group.retain(|watcher| !ptr::eq(Rc::as_ptr(watcher), self))
206        }
207
208        // Step 3. Remove any item from manager's groups that is empty.
209        groups.retain(|group| !group.is_empty())
210    }
211
212    /// <https://html.spec.whatwg.org/multipage/#create-close-watcher-cancelaction>
213    pub(crate) fn cancel_action(&self, cx: &mut JSContext, can_prevent_close: bool) -> bool {
214        match &self.owner {
215            InternalCloseWatcherOwner::CloseWatcher(close_watcher) => {
216                // https://html.spec.whatwg.org/multipage/#the-closewatcher-interface:create-close-watcher-cancelaction
217                // > cancelAction given canPreventClose being to return the result of firing an event
218                // > named cancel at this, with the cancelable attribute initialized to
219                // > canPreventClose
220                let event = Event::new(
221                    cx,
222                    &self.window.global(),
223                    atom!("cancel"),
224                    EventBubbles::DoesNotBubble,
225                    if can_prevent_close {
226                        EventCancelable::Cancelable
227                    } else {
228                        EventCancelable::NotCancelable
229                    },
230                );
231                event.fire(cx, close_watcher.upcast::<EventTarget>())
232            },
233            InternalCloseWatcherOwner::Dialog(dialog) => {
234                // https://html.spec.whatwg.org/multipage/#the-dialog-element:create-close-watcher-cancelaction
235                // > cancelAction given canPreventClose being to return the result of firing an event
236                // > named cancel at dialog, with the cancelable attribute initialized to
237                // > canPreventClose.
238                let event = Event::new(
239                    cx,
240                    &self.window.global(),
241                    atom!("cancel"),
242                    EventBubbles::DoesNotBubble,
243                    if can_prevent_close {
244                        EventCancelable::Cancelable
245                    } else {
246                        EventCancelable::NotCancelable
247                    },
248                );
249                event.fire(cx, dialog.upcast::<EventTarget>())
250            },
251        }
252    }
253
254    /// <https://html.spec.whatwg.org/multipage/#create-close-watcher-closeaction>
255    pub(crate) fn close_action(&self, cx: &mut JSContext) {
256        match &self.owner {
257            InternalCloseWatcherOwner::CloseWatcher(close_watcher) => {
258                // https://html.spec.whatwg.org/multipage/#the-closewatcher-interface:create-close-watcher-closeaction
259                // > closeAction being to fire an event named close at this.
260                let event = Event::new(
261                    cx,
262                    &self.window.global(),
263                    atom!("close"),
264                    EventBubbles::DoesNotBubble,
265                    EventCancelable::NotCancelable,
266                );
267                event.fire(cx, close_watcher.upcast::<EventTarget>());
268            },
269            InternalCloseWatcherOwner::Dialog(dialog) => {
270                // https://html.spec.whatwg.org/multipage/#the-dialog-element:create-close-watcher-closeaction
271                // > closeAction being to close the dialog given dialog, dialog's request close
272                // > return value, and dialog's request close source element.
273                dialog.close_watcher_close(cx)
274            },
275        }
276    }
277
278    /// <https://html.spec.whatwg.org/multipage/#close-watcher-get-enabled-state>
279    pub(crate) fn get_enabled_state(&self) -> bool {
280        match &self.owner {
281            // https://html.spec.whatwg.org/multipage/#the-dialog-element:create-close-watcher-getenabledstate
282            // > getEnabledState being to return true if dialog's enable close watcher
283            // > for request close is true or dialog's computed closed-by state is not None; otherwise false.
284            InternalCloseWatcherOwner::Dialog(dialog) => dialog.close_watcher_enabled_state(),
285            // https://html.spec.whatwg.org/multipage/#the-closewatcher-interface:close-watcher-get-enabled-state
286            // > getEnabledState being to return true.
287            InternalCloseWatcherOwner::CloseWatcher(_) => true,
288        }
289    }
290}
291
292#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
293#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
294pub(crate) enum InternalCloseWatcherOwner {
295    /// <https://html.spec.whatwg.org/multipage/#dom-closewatcher>
296    CloseWatcher(Dom<CloseWatcher>),
297    /// <https://html.spec.whatwg.org/multipage/#set-the-dialog-close-watcher>
298    Dialog(Dom<HTMLDialogElement>),
299}