AbortSignal.cpp 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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. auto abort_event = Event::create(realm(), HTML::EventNames::abort);
  55. abort_event->set_is_trusted(true);
  56. dispatch_event(abort_event);
  57. }
  58. void AbortSignal::set_onabort(WebIDL::CallbackType* event_handler)
  59. {
  60. set_event_handler_attribute(HTML::EventNames::abort, event_handler);
  61. }
  62. WebIDL::CallbackType* AbortSignal::onabort()
  63. {
  64. return event_handler_attribute(HTML::EventNames::abort);
  65. }
  66. // https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted
  67. JS::ThrowCompletionOr<void> AbortSignal::throw_if_aborted() const
  68. {
  69. // The throwIfAborted() method steps are to throw this’s abort reason, if this is aborted.
  70. if (!aborted())
  71. return {};
  72. return JS::throw_completion(m_abort_reason);
  73. }
  74. void AbortSignal::visit_edges(JS::Cell::Visitor& visitor)
  75. {
  76. Base::visit_edges(visitor);
  77. visitor.visit(m_abort_reason);
  78. for (auto& algorithm : m_abort_algorithms)
  79. visitor.visit(algorithm);
  80. }
  81. // https://dom.spec.whatwg.org/#abortsignal-follow
  82. void AbortSignal::follow(JS::NonnullGCPtr<AbortSignal> parent_signal)
  83. {
  84. // A followingSignal (an AbortSignal) is made to follow a parentSignal (an AbortSignal) by running these steps:
  85. // 1. If followingSignal is aborted, then return.
  86. if (aborted())
  87. return;
  88. // 2. If parentSignal is aborted, then signal abort on followingSignal with parentSignal’s abort reason.
  89. if (parent_signal->aborted()) {
  90. signal_abort(parent_signal->reason());
  91. return;
  92. }
  93. // 3. Otherwise, add the following abort steps to parentSignal:
  94. // NOTE: `this` and `parent_signal` are protected by AbortSignal using JS::SafeFunction.
  95. parent_signal->add_abort_algorithm([this, parent_signal] {
  96. // 1. Signal abort on followingSignal with parentSignal’s abort reason.
  97. signal_abort(parent_signal->reason());
  98. });
  99. }
  100. // https://dom.spec.whatwg.org/#dom-abortsignal-abort
  101. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::abort(JS::VM& vm, JS::Value reason)
  102. {
  103. // 1. Let signal be a new AbortSignal object.
  104. auto signal = TRY(construct_impl(*vm.current_realm()));
  105. // 2. Set signal’s abort reason to reason if it is given; otherwise to a new "AbortError" DOMException.
  106. if (reason.is_undefined())
  107. reason = WebIDL::AbortError::create(*vm.current_realm(), "Aborted without reason"_fly_string).ptr();
  108. signal->set_reason(reason);
  109. // 3. Return signal.
  110. return signal;
  111. }
  112. // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
  113. WebIDL::ExceptionOr<JS::NonnullGCPtr<AbortSignal>> AbortSignal::timeout(JS::VM& vm, WebIDL::UnsignedLongLong milliseconds)
  114. {
  115. auto& realm = *vm.current_realm();
  116. // 1. Let signal be a new AbortSignal object.
  117. auto signal = TRY(construct_impl(realm));
  118. // 2. Let global be signal’s relevant global object.
  119. auto& global = HTML::relevant_global_object(signal);
  120. auto* window_or_worker = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&global);
  121. VERIFY(window_or_worker);
  122. // 3. Run steps after a timeout given global, "AbortSignal-timeout", milliseconds, and the following step:
  123. window_or_worker->run_steps_after_a_timeout(milliseconds, [&realm, &global, strong_signal = JS::make_handle(signal)]() {
  124. // 1. Queue a global task on the timer task source given global to signal abort given signal and a new "TimeoutError" DOMException.
  125. HTML::queue_global_task(HTML::Task::Source::TimerTask, global, [&realm, &strong_signal]() mutable {
  126. auto reason = WebIDL::TimeoutError::create(realm, "Signal timed out"_fly_string);
  127. strong_signal->signal_abort(reason);
  128. });
  129. });
  130. // 4. Return signal.
  131. return signal;
  132. }
  133. }