Coverage Report

Created: 2026-11-13 08:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/HTML/CloseWatcherManager.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2024, the Ladybird developers.
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/TypeCasts.h>
8
#include <LibWeb/DOM/Document.h>
9
#include <LibWeb/DOM/EventDispatcher.h>
10
#include <LibWeb/DOM/IDLEventListener.h>
11
#include <LibWeb/HTML/CloseWatcher.h>
12
#include <LibWeb/HTML/CloseWatcherManager.h>
13
#include <LibWeb/HTML/Window.h>
14
15
namespace Web::HTML {
16
17
JS_DEFINE_ALLOCATOR(CloseWatcherManager);
18
19
JS::NonnullGCPtr<CloseWatcherManager> CloseWatcherManager::create(JS::Realm& realm)
20
0
{
21
0
    return realm.heap().allocate<CloseWatcherManager>(realm, realm);
22
0
}
23
24
CloseWatcherManager::CloseWatcherManager(JS::Realm& realm)
25
0
    : PlatformObject(realm)
26
0
{
27
0
}
28
29
void CloseWatcherManager::add(JS::NonnullGCPtr<CloseWatcher> close_watcher)
30
0
{
31
    // If manager's groups's size is less than manager's allowed number of groups
32
0
    if (m_groups.size() < m_allowed_number_of_groups) {
33
        // then append « closeWatcher » to manager's groups.
34
0
        JS::MarkedVector<JS::NonnullGCPtr<CloseWatcher>> new_group(realm().heap());
35
0
        new_group.append(close_watcher);
36
0
        m_groups.append(move(new_group));
37
0
    } else {
38
        // Assert: manager's groups's size is at least 1 in this branch, since manager's allowed number of groups is always at least 1.
39
0
        VERIFY(!m_groups.is_empty());
40
        // Append closeWatcher to manager's groups's last item.
41
0
        m_groups.last().append(close_watcher);
42
0
    }
43
44
    // Set manager's next user interaction allows a new group to true.
45
0
    m_next_user_interaction_allows_a_new_group = true;
46
0
}
47
48
void CloseWatcherManager::remove(CloseWatcher const& close_watcher)
49
0
{
50
    // 2. For each group of manager's groups: remove closeWatcher from group
51
0
    for (auto& group : m_groups) {
52
0
        group.remove_first_matching([&close_watcher](JS::NonnullGCPtr<CloseWatcher>& entry) {
53
0
            return entry.ptr() == &close_watcher;
54
0
        });
55
0
    }
56
    // 3. Remove any item from manager's group that is empty
57
0
    m_groups.remove_all_matching([](auto& group) { return group.is_empty(); });
58
0
}
59
60
// https://html.spec.whatwg.org/multipage/interaction.html#process-close-watchers
61
bool CloseWatcherManager::process_close_watchers()
62
0
{
63
    // 1. Let processedACloseWatcher be false.
64
0
    bool processed_a_close_watcher = false;
65
    // 2. If window's close watcher manager's groups is not empty:
66
0
    if (!m_groups.is_empty()) {
67
        // 2.1 Let group be the last item in window's close watcher manager's groups.
68
0
        auto& group = m_groups.last();
69
        // Ambiguous spec wording. We copy the groups to avoid modifying the original while iterating.
70
        // See https://github.com/whatwg/html/issues/10240
71
0
        JS::MarkedVector<JS::NonnullGCPtr<CloseWatcher>> group_copy(realm().heap());
72
0
        group_copy.ensure_capacity(group.size());
73
0
        for (auto& close_watcher : group) {
74
0
            group_copy.append(close_watcher);
75
0
        }
76
        // 2.2 For each closeWatcher of group, in reverse order:
77
0
        for (auto it = group_copy.rbegin(); it != group_copy.rend(); ++it) {
78
            // 2.1.1 Set processedACloseWatcher to true.
79
0
            processed_a_close_watcher = true;
80
            // 2.1.2 Let shouldProceed be the result of requesting to close closeWatcher.
81
0
            bool should_proceed = (*it)->request_close();
82
            // 2.1.3 If shouldProceed is false, then break;
83
0
            if (!should_proceed)
84
0
                break;
85
0
        }
86
0
    }
87
    // 3. If window's close watcher manager's allowed number of groups is greater than 1, decrement it by 1.
88
0
    if (m_allowed_number_of_groups > 1)
89
0
        m_allowed_number_of_groups--;
90
91
    // 4. Return processedACloseWatcher.
92
0
    return processed_a_close_watcher;
93
0
}
94
95
// https://html.spec.whatwg.org/multipage/interaction.html#notify-the-close-watcher-manager-about-user-activation
96
void CloseWatcherManager::notify_about_user_activation()
97
0
{
98
    // 1. Let manager be window's close watcher manager.
99
    // 2. If manager's next user interaction allows a new group is true, then increment manager's allowed number of groups.
100
0
    if (m_next_user_interaction_allows_a_new_group)
101
0
        m_allowed_number_of_groups++;
102
    // 3. Set manager's next user interaction allows a new group to false.
103
0
    m_next_user_interaction_allows_a_new_group = false;
104
0
}
105
106
// https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-request-close
107
bool CloseWatcherManager::can_prevent_close()
108
0
{
109
    // 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...
110
0
    return m_groups.size() < m_allowed_number_of_groups;
111
0
}
112
113
void CloseWatcherManager::visit_edges(JS::Cell::Visitor& visitor)
114
0
{
115
0
    Base::visit_edges(visitor);
116
117
0
    visitor.visit(m_groups);
118
0
}
119
120
}