AbortSignal.cpp 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. /*
  2. * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/Bindings/Intrinsics.h>
  7. #include <LibWeb/DOM/AbortSignal.h>
  8. #include <LibWeb/DOM/Document.h>
  9. #include <LibWeb/DOM/EventDispatcher.h>
  10. #include <LibWeb/HTML/EventHandler.h>
  11. #include <LibWeb/HTML/Window.h>
  12. #include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
  13. namespace Web::DOM {
  14. JS_DEFINE_ALLOCATOR(AbortSignal);
  15. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::construct_impl(JS::Realm& realm)
  16. {
  17. return realm.heap().allocate<AbortSignal>(realm, realm);
  18. }
  19. AbortSignal::AbortSignal(JS::Realm& realm)
  20. : EventTarget(realm)
  21. {
  22. }
  23. void AbortSignal::initialize(JS::Realm& realm)
  24. {
  25. Base::initialize(realm);
  26. set_prototype(&Bindings::ensure_web_prototype<Bindings::AbortSignalPrototype>(realm, "AbortSignal"_fly_string));
  27. }
  28. // https://dom.spec.whatwg.org/#abortsignal-add
  29. void AbortSignal::add_abort_algorithm(Function<void()> abort_algorithm)
  30. {
  31. // 1. If signal is aborted, then return.
  32. if (aborted())
  33. return;
  34. // 2. Append algorithm to signal’s abort algorithms.
  35. m_abort_algorithms.append(JS::create_heap_function(vm().heap(), move(abort_algorithm)));
  36. }
  37. // https://dom.spec.whatwg.org/#abortsignal-signal-abort
  38. void AbortSignal::signal_abort(JS::Value reason)
  39. {
  40. // 1. If signal is aborted, then return.
  41. if (aborted())
  42. return;
  43. // 2. Set signal’s abort reason to reason if it is given; otherwise to a new "AbortError" DOMException.
  44. if (!reason.is_undefined())
  45. m_abort_reason = reason;
  46. else
  47. m_abort_reason = WebIDL::AbortError::create(realm(), "Aborted without reason"_fly_string).ptr();
  48. // 3. For each algorithm in signal’s abort algorithms: run algorithm.
  49. for (auto& algorithm : m_abort_algorithms)
  50. algorithm->function()();
  51. // 4. Empty signal’s abort algorithms.
  52. m_abort_algorithms.clear();
  53. // 5. Fire an event named abort at signal.
  54. dispatch_event(Event::create(realm(), HTML::EventNames::abort));
  55. }
  56. void AbortSignal::set_onabort(WebIDL::CallbackType* event_handler)
  57. {
  58. set_event_handler_attribute(HTML::EventNames::abort, event_handler);
  59. }
  60. WebIDL::CallbackType* AbortSignal::onabort()
  61. {
  62. return event_handler_attribute(HTML::EventNames::abort);
  63. }
  64. // https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted
  65. JS::ThrowCompletionOr<void> AbortSignal::throw_if_aborted() const
  66. {
  67. // The throwIfAborted() method steps are to throw this’s abort reason, if this is aborted.
  68. if (!aborted())
  69. return {};
  70. return JS::throw_completion(m_abort_reason);
  71. }
  72. void AbortSignal::visit_edges(JS::Cell::Visitor& visitor)
  73. {
  74. Base::visit_edges(visitor);
  75. visitor.visit(m_abort_reason);
  76. for (auto& algorithm : m_abort_algorithms)
  77. visitor.visit(algorithm);
  78. }
  79. // https://dom.spec.whatwg.org/#abortsignal-follow
  80. void AbortSignal::follow(JS::NonnullGCPtr<AbortSignal> parent_signal)
  81. {
  82. // A followingSignal (an AbortSignal) is made to follow a parentSignal (an AbortSignal) by running these steps:
  83. // 1. If followingSignal is aborted, then return.
  84. if (aborted())
  85. return;
  86. // 2. If parentSignal is aborted, then signal abort on followingSignal with parentSignal’s abort reason.
  87. if (parent_signal->aborted()) {
  88. signal_abort(parent_signal->reason());
  89. return;
  90. }
  91. // 3. Otherwise, add the following abort steps to parentSignal:
  92. // NOTE: `this` and `parent_signal` are protected by AbortSignal using JS::SafeFunction.
  93. parent_signal->add_abort_algorithm([this, parent_signal] {
  94. // 1. Signal abort on followingSignal with parentSignal’s abort reason.
  95. signal_abort(parent_signal->reason());
  96. });
  97. }
  98. // https://dom.spec.whatwg.org/#dom-abortsignal-abort
  99. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::abort(JS::VM& vm, JS::Value reason)
  100. {
  101. // 1. Let signal be a new AbortSignal object.
  102. auto signal = TRY(construct_impl(*vm.current_realm()));
  103. // 2. Set signal’s abort reason to reason if it is given; otherwise to a new "AbortError" DOMException.
  104. if (reason.is_undefined())
  105. reason = WebIDL::AbortError::create(*vm.current_realm(), "Aborted without reason"_fly_string).ptr();
  106. signal->set_reason(reason);
  107. // 3. Return signal.
  108. return signal;
  109. }
  110. // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
  111. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::timeout(JS::VM& vm, WebIDL::UnsignedLongLong milliseconds)
  112. {
  113. auto& realm = *vm.current_realm();
  114. // 1. Let signal be a new AbortSignal object.
  115. auto signal = TRY(construct_impl(realm));
  116. // 2. Let global be signal’s relevant global object.
  117. auto& global = HTML::relevant_global_object(signal);
  118. auto* window_or_worker = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&global);
  119. VERIFY(window_or_worker);
  120. // 3. Run steps after a timeout given global, "AbortSignal-timeout", milliseconds, and the following step:
  121. window_or_worker->run_steps_after_a_timeout(milliseconds, [&realm, &global, strong_signal = JS::make_handle(signal)]() {
  122. // 1. Queue a global task on the timer task source given global to signal abort given signal and a new "TimeoutError" DOMException.
  123. HTML::queue_global_task(HTML::Task::Source::TimerTask, global, [&realm, &strong_signal]() mutable {
  124. auto reason = WebIDL::TimeoutError::create(realm, "Signal timed out"_fly_string);
  125. strong_signal->signal_abort(reason);
  126. });
  127. });
  128. // 4. Return signal.
  129. return signal;
  130. }
  131. }