AbortSignal.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. /*
  2. * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
  3. * Copyright (c) 2024, Tim Ledbetter <timledbetter@gmail.com>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibWeb/Bindings/Intrinsics.h>
  8. #include <LibWeb/DOM/AbortSignal.h>
  9. #include <LibWeb/DOM/Document.h>
  10. #include <LibWeb/DOM/EventDispatcher.h>
  11. #include <LibWeb/HTML/EventHandler.h>
  12. #include <LibWeb/HTML/Window.h>
  13. #include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
  14. namespace Web::DOM {
  15. JS_DEFINE_ALLOCATOR(AbortSignal);
  16. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::construct_impl(JS::Realm& realm)
  17. {
  18. return realm.heap().allocate<AbortSignal>(realm, realm);
  19. }
  20. AbortSignal::AbortSignal(JS::Realm& realm)
  21. : EventTarget(realm)
  22. {
  23. }
  24. void AbortSignal::initialize(JS::Realm& realm)
  25. {
  26. Base::initialize(realm);
  27. WEB_SET_PROTOTYPE_FOR_INTERFACE(AbortSignal);
  28. }
  29. // https://dom.spec.whatwg.org/#abortsignal-add
  30. void AbortSignal::add_abort_algorithm(Function<void()> abort_algorithm)
  31. {
  32. // 1. If signal is aborted, then return.
  33. if (aborted())
  34. return;
  35. // 2. Append algorithm to signal’s abort algorithms.
  36. m_abort_algorithms.append(JS::create_heap_function(vm().heap(), move(abort_algorithm)));
  37. }
  38. // https://dom.spec.whatwg.org/#abortsignal-signal-abort
  39. void AbortSignal::signal_abort(JS::Value reason)
  40. {
  41. // 1. If signal is aborted, then return.
  42. if (aborted())
  43. return;
  44. // 2. Set signal’s abort reason to reason if it is given; otherwise to a new "AbortError" DOMException.
  45. if (!reason.is_undefined())
  46. m_abort_reason = reason;
  47. else
  48. m_abort_reason = WebIDL::AbortError::create(realm(), "Aborted without reason"_fly_string).ptr();
  49. // 3. For each algorithm in signal’s abort algorithms: run algorithm.
  50. for (auto& algorithm : m_abort_algorithms)
  51. algorithm->function()();
  52. // 4. Empty signal’s abort algorithms.
  53. m_abort_algorithms.clear();
  54. // 5. Fire an event named abort at signal.
  55. auto abort_event = Event::create(realm(), HTML::EventNames::abort);
  56. abort_event->set_is_trusted(true);
  57. dispatch_event(abort_event);
  58. // 6. For each dependentSignal of signal’s dependent signals, signal abort on dependentSignal with signal’s abort reason.
  59. for (auto const& dependent_signal : m_dependent_signals)
  60. dependent_signal->signal_abort(reason);
  61. }
  62. void AbortSignal::set_onabort(WebIDL::CallbackType* event_handler)
  63. {
  64. set_event_handler_attribute(HTML::EventNames::abort, event_handler);
  65. }
  66. WebIDL::CallbackType* AbortSignal::onabort()
  67. {
  68. return event_handler_attribute(HTML::EventNames::abort);
  69. }
  70. // https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted
  71. JS::ThrowCompletionOr<void> AbortSignal::throw_if_aborted() const
  72. {
  73. // The throwIfAborted() method steps are to throw this’s abort reason, if this is aborted.
  74. if (!aborted())
  75. return {};
  76. return JS::throw_completion(m_abort_reason);
  77. }
  78. void AbortSignal::visit_edges(JS::Cell::Visitor& visitor)
  79. {
  80. Base::visit_edges(visitor);
  81. visitor.visit(m_abort_reason);
  82. visitor.visit(m_abort_algorithms);
  83. visitor.visit(m_source_signals);
  84. visitor.visit(m_dependent_signals);
  85. }
  86. // https://dom.spec.whatwg.org/#dom-abortsignal-abort
  87. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::abort(JS::VM& vm, JS::Value reason)
  88. {
  89. // 1. Let signal be a new AbortSignal object.
  90. auto signal = TRY(construct_impl(*vm.current_realm()));
  91. // 2. Set signal’s abort reason to reason if it is given; otherwise to a new "AbortError" DOMException.
  92. if (reason.is_undefined())
  93. reason = WebIDL::AbortError::create(*vm.current_realm(), "Aborted without reason"_fly_string).ptr();
  94. signal->set_reason(reason);
  95. // 3. Return signal.
  96. return signal;
  97. }
  98. // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
  99. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::timeout(JS::VM& vm, WebIDL::UnsignedLongLong milliseconds)
  100. {
  101. auto& realm = *vm.current_realm();
  102. // 1. Let signal be a new AbortSignal object.
  103. auto signal = TRY(construct_impl(realm));
  104. // 2. Let global be signal’s relevant global object.
  105. auto& global = HTML::relevant_global_object(signal);
  106. auto* window_or_worker = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&global);
  107. VERIFY(window_or_worker);
  108. // 3. Run steps after a timeout given global, "AbortSignal-timeout", milliseconds, and the following step:
  109. window_or_worker->run_steps_after_a_timeout(milliseconds, [&realm, &global, signal]() {
  110. // 1. Queue a global task on the timer task source given global to signal abort given signal and a new "TimeoutError" DOMException.
  111. HTML::queue_global_task(HTML::Task::Source::TimerTask, global, JS::create_heap_function(realm.heap(), [&realm, signal]() mutable {
  112. auto reason = WebIDL::TimeoutError::create(realm, "Signal timed out"_fly_string);
  113. signal->signal_abort(reason);
  114. }));
  115. });
  116. // 4. Return signal.
  117. return signal;
  118. }
  119. // https://dom.spec.whatwg.org/#dom-abortsignal-any
  120. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::any(JS::VM& vm, JS::Value signals)
  121. {
  122. Vector<JS::Handle<AbortSignal>> signals_list;
  123. auto iterator_record = TRY(get_iterator(vm, signals, JS::IteratorHint::Sync));
  124. while (true) {
  125. auto next = TRY(iterator_step_value(vm, iterator_record));
  126. if (!next.has_value())
  127. break;
  128. auto value = next.release_value();
  129. if (!value.is_object() || !is<AbortSignal>(value.as_object()))
  130. return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "AbortSignal");
  131. auto& signal = static_cast<AbortSignal&>(value.as_object());
  132. signals_list.append(JS::make_handle(signal));
  133. }
  134. // 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.
  135. return create_dependent_abort_signal(*vm.current_realm(), signals_list);
  136. }
  137. // https://dom.spec.whatwg.org/#create-a-dependent-abort-signal
  138. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::create_dependent_abort_signal(JS::Realm& realm, Vector<JS::Handle<AbortSignal>> const& signals)
  139. {
  140. // 1. Let resultSignal be a new object implementing signalInterface using realm.
  141. auto result_signal = TRY(construct_impl(realm));
  142. // 2. For each signal of signals: if signal is aborted, then set resultSignal’s abort reason to signal’s abort reason and return resultSignal.
  143. for (auto const& signal : signals) {
  144. if (signal->aborted()) {
  145. result_signal->set_reason(signal->reason());
  146. return result_signal;
  147. }
  148. }
  149. // 3. Set resultSignal’s dependent to true.
  150. result_signal->set_dependent(true);
  151. // 4. For each signal of signals:
  152. for (auto const& signal : signals) {
  153. // 1. If signal’s dependent is false, then:
  154. if (!signal->dependent()) {
  155. // 1. Append signal to resultSignal’s source signals.
  156. result_signal->append_source_signal({ signal });
  157. // 2. Append resultSignal to signal’s dependent signals.
  158. signal->append_dependent_signal(result_signal);
  159. }
  160. // 2. Otherwise, for each sourceSignal of signal’s source signals:
  161. else {
  162. for (auto const& source_signal : signal->source_signals()) {
  163. // 1. Assert: sourceSignal is not aborted and not dependent.
  164. VERIFY(source_signal);
  165. VERIFY(!source_signal->aborted());
  166. VERIFY(!source_signal->dependent());
  167. // 2. Append sourceSignal to resultSignal’s source signals.
  168. result_signal->append_source_signal(source_signal);
  169. // 3. Append resultSignal to sourceSignal’s dependent signals.
  170. source_signal->append_dependent_signal(result_signal);
  171. }
  172. }
  173. }
  174. // 5. Return resultSignal
  175. return result_signal;
  176. }
  177. }