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}