Coverage Report

Created: 2025-12-18 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}