AbortSignal.cpp 8.9 KB

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