/src/serenity/Userland/Libraries/LibWeb/HTML/CloseWatcher.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2024, the Ladybird developers. |
3 | | * Copyright (c) 2024, Felipe Muñoz Mazur <felipe.munoz.mazur@protonmail.com> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/TypeCasts.h> |
9 | | #include <LibWeb/Bindings/CloseWatcherPrototype.h> |
10 | | #include <LibWeb/Bindings/Intrinsics.h> |
11 | | #include <LibWeb/DOM/Document.h> |
12 | | #include <LibWeb/DOM/EventDispatcher.h> |
13 | | #include <LibWeb/DOM/IDLEventListener.h> |
14 | | #include <LibWeb/HTML/CloseWatcher.h> |
15 | | #include <LibWeb/HTML/CloseWatcherManager.h> |
16 | | #include <LibWeb/HTML/EventHandler.h> |
17 | | #include <LibWeb/HTML/HTMLIFrameElement.h> |
18 | | #include <LibWeb/HTML/Window.h> |
19 | | |
20 | | namespace Web::HTML { |
21 | | |
22 | | JS_DEFINE_ALLOCATOR(CloseWatcher); |
23 | | |
24 | | // https://html.spec.whatwg.org/multipage/interaction.html#establish-a-close-watcher |
25 | | JS::NonnullGCPtr<CloseWatcher> CloseWatcher::establish(HTML::Window& window) |
26 | 0 | { |
27 | | // 1. Assert: window's associated Document is fully active. |
28 | 0 | VERIFY(window.associated_document().is_fully_active()); |
29 | | |
30 | | // 2. Let closeWatcher be a new close watcher |
31 | 0 | auto close_watcher = window.heap().allocate<CloseWatcher>(window.realm(), window.realm()); |
32 | | |
33 | | // 3. Let manager be window's associated close watcher manager |
34 | 0 | auto manager = window.close_watcher_manager(); |
35 | | |
36 | | // 4 - 6. Moved to CloseWatcherManager::add |
37 | 0 | manager->add(close_watcher); |
38 | | |
39 | | // 7. Return close_watcher. |
40 | 0 | return close_watcher; |
41 | 0 | } |
42 | | |
43 | | // https://html.spec.whatwg.org/multipage/interaction.html#dom-closewatcher |
44 | | WebIDL::ExceptionOr<JS::NonnullGCPtr<CloseWatcher>> CloseWatcher::construct_impl(JS::Realm& realm, CloseWatcherOptions const& options) |
45 | 0 | { |
46 | 0 | auto& window = verify_cast<HTML::Window>(realm.global_object()); |
47 | | |
48 | | // NOTE: Not in spec explicitly, but this should account for detached iframes too. See /close-watcher/frame-removal.html WPT. |
49 | 0 | auto navigable = window.navigable(); |
50 | 0 | if (navigable && navigable->has_been_destroyed()) |
51 | 0 | return WebIDL::InvalidStateError::create(realm, "The iframe has been detached"_string); |
52 | | |
53 | | // 1. If this's relevant global object's associated Document is not fully active, then return an "InvalidStateError" DOMException. |
54 | 0 | if (!window.associated_document().is_fully_active()) |
55 | 0 | return WebIDL::InvalidStateError::create(realm, "The document is not fully active."_string); |
56 | | |
57 | | // 2. Let close_watcher be the result of establishing a close watcher |
58 | 0 | auto close_watcher = establish(window); |
59 | | |
60 | | // 3. If options["signal"] exists, then: |
61 | 0 | if (auto signal = options.signal) { |
62 | | // 3.1 If options["signal"]'s aborted, then destroy closeWatcher. |
63 | 0 | if (signal->aborted()) { |
64 | 0 | close_watcher->destroy(); |
65 | 0 | } |
66 | | |
67 | | // 3.2 Add the following steps to options["signal"]: |
68 | 0 | signal->add_abort_algorithm([close_watcher] { |
69 | | // 3.2.1 Destroy closeWatcher. |
70 | 0 | close_watcher->destroy(); |
71 | 0 | }); |
72 | 0 | } |
73 | |
|
74 | 0 | return close_watcher; |
75 | 0 | } |
76 | | |
77 | | CloseWatcher::CloseWatcher(JS::Realm& realm) |
78 | 0 | : DOM::EventTarget(realm) |
79 | 0 | { |
80 | 0 | } |
81 | | |
82 | | // https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-request-close |
83 | | bool CloseWatcher::request_close() |
84 | 0 | { |
85 | | // 1. If closeWatcher is not active, then return. |
86 | 0 | if (!m_is_active) |
87 | 0 | return true; |
88 | | |
89 | | // 2. If closeWatcher's is running cancel action is true, then return true. |
90 | 0 | if (m_is_running_cancel_action) |
91 | 0 | return true; |
92 | | |
93 | | // 3. Let window be closeWatcher's window. |
94 | 0 | auto& window = verify_cast<HTML::Window>(realm().global_object()); |
95 | | |
96 | | // 4. If window's associated Document is not fully active, then return true. |
97 | 0 | if (!window.associated_document().is_fully_active()) |
98 | 0 | return true; |
99 | | |
100 | | // 5. Let canPreventClose be true if window's close watcher manager's groups's size is less than window's close watcher manager's allowed number of groups, |
101 | | // and window has history-action activation; otherwise false. |
102 | 0 | auto manager = window.close_watcher_manager(); |
103 | 0 | bool can_prevent_close = manager->can_prevent_close() && window.has_history_action_activation(); |
104 | | // 6. Set closeWatcher's is running cancel action to true. |
105 | 0 | m_is_running_cancel_action = true; |
106 | | // 7. Let shouldContinue be the result of running closeWatcher's cancel action given canPreventClose. |
107 | 0 | bool should_continue = dispatch_event(DOM::Event::create(realm(), HTML::EventNames::cancel, { .cancelable = can_prevent_close })); |
108 | | // 8. Set closeWatcher's is running cancel action to false. |
109 | 0 | m_is_running_cancel_action = false; |
110 | | // 9. If shouldContinue is false, then: |
111 | 0 | if (!should_continue) { |
112 | | // 9.1 Assert: canPreventClose is true. |
113 | 0 | VERIFY(can_prevent_close); |
114 | | // 9.2 Consume history-action user activation given window. |
115 | 0 | window.consume_history_action_user_activation(); |
116 | 0 | return false; |
117 | 0 | } |
118 | | |
119 | | // 10. Close closeWatcher. |
120 | 0 | close(); |
121 | | |
122 | | // 11. Return true. |
123 | 0 | return true; |
124 | 0 | } |
125 | | |
126 | | // https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-close |
127 | | void CloseWatcher::close() |
128 | 0 | { |
129 | | // 1. If closeWatcher is not active, then return. |
130 | 0 | if (!m_is_active) |
131 | 0 | return; |
132 | | |
133 | | // 2. If closeWatcher's window's associated Document is not fully active, then return. |
134 | 0 | if (!verify_cast<HTML::Window>(realm().global_object()).associated_document().is_fully_active()) |
135 | 0 | return; |
136 | | |
137 | | // 3. Destroy closeWatcher. |
138 | 0 | destroy(); |
139 | | |
140 | | // 4. Run closeWatcher's close action. |
141 | 0 | dispatch_event(DOM::Event::create(realm(), HTML::EventNames::close)); |
142 | 0 | } |
143 | | |
144 | | // https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-destroy |
145 | | void CloseWatcher::destroy() |
146 | 0 | { |
147 | | // 1. Let manager be closeWatcher's window's close watcher manager. |
148 | 0 | auto manager = verify_cast<HTML::Window>(realm().global_object()).close_watcher_manager(); |
149 | | |
150 | | // 2-3. Moved to CloseWatcherManager::remove |
151 | 0 | manager->remove(*this); |
152 | |
|
153 | 0 | m_is_active = false; |
154 | 0 | } |
155 | | |
156 | | void CloseWatcher::initialize(JS::Realm& realm) |
157 | 0 | { |
158 | 0 | Base::initialize(realm); |
159 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(CloseWatcher); |
160 | 0 | } |
161 | | |
162 | | void CloseWatcher::set_oncancel(WebIDL::CallbackType* event_handler) |
163 | 0 | { |
164 | 0 | set_event_handler_attribute(HTML::EventNames::cancel, event_handler); |
165 | 0 | } |
166 | | |
167 | | WebIDL::CallbackType* CloseWatcher::oncancel() |
168 | 0 | { |
169 | 0 | return event_handler_attribute(HTML::EventNames::cancel); |
170 | 0 | } |
171 | | |
172 | | void CloseWatcher::set_onclose(WebIDL::CallbackType* event_handler) |
173 | 0 | { |
174 | 0 | set_event_handler_attribute(HTML::EventNames::close, event_handler); |
175 | 0 | } |
176 | | |
177 | | WebIDL::CallbackType* CloseWatcher::onclose() |
178 | 0 | { |
179 | 0 | return event_handler_attribute(HTML::EventNames::close); |
180 | 0 | } |
181 | | |
182 | | } |