Coverage Report

Created: 2025-09-05 06:52

/src/serenity/Userland/Libraries/LibWeb/DOM/AbortSignal.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
3
 * Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <LibWeb/Bindings/AbortSignalPrototype.h>
9
#include <LibWeb/Bindings/Intrinsics.h>
10
#include <LibWeb/DOM/AbortSignal.h>
11
#include <LibWeb/DOM/Document.h>
12
#include <LibWeb/DOM/EventDispatcher.h>
13
#include <LibWeb/HTML/EventHandler.h>
14
#include <LibWeb/HTML/Window.h>
15
#include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
16
17
namespace Web::DOM {
18
19
JS_DEFINE_ALLOCATOR(AbortSignal);
20
21
WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::construct_impl(JS::Realm& realm)
22
0
{
23
0
    return realm.heap().allocate<AbortSignal>(realm, realm);
24
0
}
25
26
AbortSignal::AbortSignal(JS::Realm& realm)
27
0
    : EventTarget(realm)
28
0
{
29
0
}
30
31
void AbortSignal::initialize(JS::Realm& realm)
32
0
{
33
0
    Base::initialize(realm);
34
0
    WEB_SET_PROTOTYPE_FOR_INTERFACE(AbortSignal);
35
0
}
36
37
// https://dom.spec.whatwg.org/#abortsignal-add
38
void AbortSignal::add_abort_algorithm(Function<void()> abort_algorithm)
39
0
{
40
    // 1. If signal is aborted, then return.
41
0
    if (aborted())
42
0
        return;
43
44
    // 2. Append algorithm to signal’s abort algorithms.
45
0
    m_abort_algorithms.append(JS::create_heap_function(vm().heap(), move(abort_algorithm)));
46
0
}
47
48
// https://dom.spec.whatwg.org/#abortsignal-signal-abort
49
void AbortSignal::signal_abort(JS::Value reason)
50
0
{
51
    // 1. If signal is aborted, then return.
52
0
    if (aborted())
53
0
        return;
54
55
    // 2. Set signal’s abort reason to reason if it is given; otherwise to a new "AbortError" DOMException.
56
0
    if (!reason.is_undefined())
57
0
        m_abort_reason = reason;
58
0
    else
59
0
        m_abort_reason = WebIDL::AbortError::create(realm(), "Aborted without reason"_string).ptr();
60
61
    // 3. Let dependentSignalsToAbort be a new list.
62
0
    Vector<JS::Handle<AbortSignal>> dependent_signals_to_abort;
63
64
    // 4. For each dependentSignal of signal’s dependent signals:
65
0
    for (auto const& dependent_signal : m_dependent_signals) {
66
        // 1. If dependentSignal is not aborted, then:
67
0
        if (!dependent_signal->aborted()) {
68
            // 1. Set dependentSignal’s abort reason to signal’s abort reason.
69
0
            dependent_signal->set_reason(m_abort_reason);
70
71
            // 2. Append dependentSignal to dependentSignalsToAbort.
72
0
            dependent_signals_to_abort.append(*dependent_signal);
73
0
        }
74
0
    }
75
76
    // https://dom.spec.whatwg.org/#run-the-abort-steps
77
0
    auto run_the_abort_steps = [](auto& signal) {
78
        // 1. For each algorithm in signal’s abort algorithms: run algorithm.
79
0
        for (auto& algorithm : signal.m_abort_algorithms)
80
0
            algorithm->function()();
81
82
        // 2. Empty signal’s abort algorithms.
83
0
        signal.m_abort_algorithms.clear();
84
85
        // 3. Fire an event named abort at signal.
86
0
        auto abort_event = Event::create(signal.realm(), HTML::EventNames::abort);
87
0
        abort_event->set_is_trusted(true);
88
0
        signal.dispatch_event(abort_event);
89
0
    };
90
91
    // 5. Run the abort steps for signal.
92
0
    run_the_abort_steps(*this);
93
94
    // 6. For each dependentSignal of dependentSignalsToAbort, run the abort steps for dependentSignal.
95
0
    for (auto const& dependent_signal : dependent_signals_to_abort)
96
0
        run_the_abort_steps(*dependent_signal);
97
0
}
98
99
void AbortSignal::set_onabort(WebIDL::CallbackType* event_handler)
100
0
{
101
0
    set_event_handler_attribute(HTML::EventNames::abort, event_handler);
102
0
}
103
104
WebIDL::CallbackType* AbortSignal::onabort()
105
0
{
106
0
    return event_handler_attribute(HTML::EventNames::abort);
107
0
}
108
109
// https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted
110
JS::ThrowCompletionOr<void> AbortSignal::throw_if_aborted() const
111
0
{
112
    // The throwIfAborted() method steps are to throw this’s abort reason, if this is aborted.
113
0
    if (!aborted())
114
0
        return {};
115
116
0
    return JS::throw_completion(m_abort_reason);
117
0
}
118
119
void AbortSignal::visit_edges(JS::Cell::Visitor& visitor)
120
0
{
121
0
    Base::visit_edges(visitor);
122
0
    visitor.visit(m_abort_reason);
123
0
    visitor.visit(m_abort_algorithms);
124
0
    visitor.visit(m_source_signals);
125
0
    visitor.visit(m_dependent_signals);
126
0
}
127
128
// https://dom.spec.whatwg.org/#dom-abortsignal-abort
129
WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::abort(JS::VM& vm, JS::Value reason)
130
0
{
131
    // 1. Let signal be a new AbortSignal object.
132
0
    auto signal = TRY(construct_impl(*vm.current_realm()));
133
134
    // 2. Set signal’s abort reason to reason if it is given; otherwise to a new "AbortError" DOMException.
135
0
    if (reason.is_undefined())
136
0
        reason = WebIDL::AbortError::create(*vm.current_realm(), "Aborted without reason"_string).ptr();
137
138
0
    signal->set_reason(reason);
139
140
    // 3. Return signal.
141
0
    return signal;
142
0
}
143
144
// https://dom.spec.whatwg.org/#dom-abortsignal-timeout
145
WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::timeout(JS::VM& vm, WebIDL::UnsignedLongLong milliseconds)
146
0
{
147
0
    auto& realm = *vm.current_realm();
148
149
    // 1. Let signal be a new AbortSignal object.
150
0
    auto signal = TRY(construct_impl(realm));
151
152
    // 2. Let global be signal’s relevant global object.
153
0
    auto& global = HTML::relevant_global_object(signal);
154
0
    auto* window_or_worker = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&global);
155
0
    VERIFY(window_or_worker);
156
157
    // 3. Run steps after a timeout given global, "AbortSignal-timeout", milliseconds, and the following step:
158
0
    window_or_worker->run_steps_after_a_timeout(milliseconds, [&realm, &global, signal]() {
159
        // 1. Queue a global task on the timer task source given global to signal abort given signal and a new "TimeoutError" DOMException.
160
0
        HTML::queue_global_task(HTML::Task::Source::TimerTask, global, JS::create_heap_function(realm.heap(), [&realm, signal]() mutable {
161
0
            auto reason = WebIDL::TimeoutError::create(realm, "Signal timed out"_string);
162
0
            signal->signal_abort(reason);
163
0
        }));
164
0
    });
165
166
    // 4. Return signal.
167
0
    return signal;
168
0
}
169
170
// https://dom.spec.whatwg.org/#dom-abortsignal-any
171
WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::any(JS::VM& vm, Vector<JS::Handle<AbortSignal>> const& signals)
172
0
{
173
    // The static any(signals) method steps are to return the result of creating a dependent abort signal from signals using AbortSignal and the current realm.
174
0
    return create_dependent_abort_signal(*vm.current_realm(), signals);
175
0
}
176
177
// https://dom.spec.whatwg.org/#create-a-dependent-abort-signal
178
WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::create_dependent_abort_signal(JS::Realm& realm, Vector<JS::Handle<AbortSignal>> const& signals)
179
0
{
180
    // 1. Let resultSignal be a new object implementing signalInterface using realm.
181
0
    auto result_signal = TRY(construct_impl(realm));
182
183
    // 2. For each signal of signals: if signal is aborted, then set resultSignal’s abort reason to signal’s abort reason and return resultSignal.
184
0
    for (auto const& signal : signals) {
185
0
        if (signal->aborted()) {
186
0
            result_signal->set_reason(signal->reason());
187
0
            return result_signal;
188
0
        }
189
0
    }
190
191
    // 3. Set resultSignal’s dependent to true.
192
0
    result_signal->set_dependent(true);
193
194
    // 4. For each signal of signals:
195
0
    for (auto const& signal : signals) {
196
        // 1. If signal’s dependent is false, then:
197
0
        if (!signal->dependent()) {
198
            // 1. Append signal to resultSignal’s source signals.
199
0
            result_signal->append_source_signal({ signal });
200
201
            // 2. Append resultSignal to signal’s dependent signals.
202
0
            signal->append_dependent_signal(result_signal);
203
0
        }
204
        // 2. Otherwise, for each sourceSignal of signal’s source signals:
205
0
        else {
206
0
            for (auto const& source_signal : signal->source_signals()) {
207
                // 1. Assert: sourceSignal is not aborted and not dependent.
208
0
                VERIFY(source_signal);
209
0
                VERIFY(!source_signal->aborted());
210
0
                VERIFY(!source_signal->dependent());
211
212
                // 2. Append sourceSignal to resultSignal’s source signals.
213
0
                result_signal->append_source_signal(source_signal);
214
215
                // 3. Append resultSignal to sourceSignal’s dependent signals.
216
0
                source_signal->append_dependent_signal(result_signal);
217
0
            }
218
0
        }
219
0
    }
220
221
    // 5. Return resultSignal
222
0
    return result_signal;
223
0
}
224
225
}