AbortSignal.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  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. for (auto& algorithm : m_abort_algorithms)
  83. visitor.visit(algorithm);
  84. for (auto& source_signal : m_source_signals)
  85. visitor.visit(source_signal);
  86. for (auto& dependent_signal : m_dependent_signals)
  87. visitor.visit(dependent_signal);
  88. }
  89. // https://dom.spec.whatwg.org/#dom-abortsignal-abort
  90. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::abort(JS::VM& vm, JS::Value reason)
  91. {
  92. // 1. Let signal be a new AbortSignal object.
  93. auto signal = TRY(construct_impl(*vm.current_realm()));
  94. // 2. Set signal’s abort reason to reason if it is given; otherwise to a new "AbortError" DOMException.
  95. if (reason.is_undefined())
  96. reason = WebIDL::AbortError::create(*vm.current_realm(), "Aborted without reason"_fly_string).ptr();
  97. signal->set_reason(reason);
  98. // 3. Return signal.
  99. return signal;
  100. }
  101. // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
  102. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::timeout(JS::VM& vm, WebIDL::UnsignedLongLong milliseconds)
  103. {
  104. auto& realm = *vm.current_realm();
  105. // 1. Let signal be a new AbortSignal object.
  106. auto signal = TRY(construct_impl(realm));
  107. // 2. Let global be signal’s relevant global object.
  108. auto& global = HTML::relevant_global_object(signal);
  109. auto* window_or_worker = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&global);
  110. VERIFY(window_or_worker);
  111. // 3. Run steps after a timeout given global, "AbortSignal-timeout", milliseconds, and the following step:
  112. window_or_worker->run_steps_after_a_timeout(milliseconds, [&realm, &global, strong_signal = JS::make_handle(signal)]() {
  113. // 1. Queue a global task on the timer task source given global to signal abort given signal and a new "TimeoutError" DOMException.
  114. HTML::queue_global_task(HTML::Task::Source::TimerTask, global, [&realm, &strong_signal]() mutable {
  115. auto reason = WebIDL::TimeoutError::create(realm, "Signal timed out"_fly_string);
  116. strong_signal->signal_abort(reason);
  117. });
  118. });
  119. // 4. Return signal.
  120. return signal;
  121. }
  122. // https://dom.spec.whatwg.org/#dom-abortsignal-any
  123. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::any(JS::VM& vm, JS::Value signals)
  124. {
  125. Vector<JS::Handle<AbortSignal>> signals_list;
  126. auto iterator_record = TRY(get_iterator(vm, signals, JS::IteratorHint::Sync));
  127. while (true) {
  128. auto next = TRY(iterator_step_value(vm, iterator_record));
  129. if (!next.has_value())
  130. break;
  131. auto value = next.release_value();
  132. if (!value.is_object() || !is<AbortSignal>(value.as_object()))
  133. return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "AbortSignal");
  134. auto& signal = static_cast<AbortSignal&>(value.as_object());
  135. signals_list.append(JS::make_handle(signal));
  136. }
  137. // 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.
  138. return create_dependent_abort_signal(*vm.current_realm(), signals_list);
  139. }
  140. // https://dom.spec.whatwg.org/#create-a-dependent-abort-signal
  141. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::create_dependent_abort_signal(JS::Realm& realm, Vector<JS::Handle<AbortSignal>> const& signals)
  142. {
  143. // 1. Let resultSignal be a new object implementing signalInterface using realm.
  144. auto result_signal = TRY(construct_impl(realm));
  145. // 2. For each signal of signals: if signal is aborted, then set resultSignal’s abort reason to signal’s abort reason and return resultSignal.
  146. for (auto const& signal : signals) {
  147. if (signal->aborted()) {
  148. result_signal->set_reason(signal->reason());
  149. return result_signal;
  150. }
  151. }
  152. // 3. Set resultSignal’s dependent to true.
  153. result_signal->set_dependent(true);
  154. // 4. For each signal of signals:
  155. for (auto const& signal : signals) {
  156. // 1. If signal’s dependent is false, then:
  157. if (!signal->dependent()) {
  158. // 1. Append signal to resultSignal’s source signals.
  159. result_signal->append_source_signal({ signal });
  160. // 2. Append resultSignal to signal’s dependent signals.
  161. signal->append_dependent_signal(result_signal);
  162. }
  163. // 2. Otherwise, for each sourceSignal of signal’s source signals:
  164. else {
  165. for (auto const& source_signal : signal->source_signals()) {
  166. // 1. Assert: sourceSignal is not aborted and not dependent.
  167. VERIFY(source_signal);
  168. VERIFY(!source_signal->aborted());
  169. VERIFY(!source_signal->dependent());
  170. // 2. Append sourceSignal to resultSignal’s source signals.
  171. result_signal->append_source_signal(source_signal);
  172. // 3. Append resultSignal to sourceSignal’s dependent signals.
  173. source_signal->append_dependent_signal(result_signal);
  174. }
  175. }
  176. }
  177. // 5. Return resultSignal
  178. return result_signal;
  179. }
  180. }