EventSource.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. /*
  2. * Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/ScopeGuard.h>
  7. #include <LibCore/EventLoop.h>
  8. #include <LibGC/Heap.h>
  9. #include <LibJS/Runtime/Realm.h>
  10. #include <LibJS/Runtime/VM.h>
  11. #include <LibWeb/Bindings/EventSourcePrototype.h>
  12. #include <LibWeb/Bindings/Intrinsics.h>
  13. #include <LibWeb/DOM/Event.h>
  14. #include <LibWeb/Fetch/Fetching/Fetching.h>
  15. #include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
  16. #include <LibWeb/Fetch/Infrastructure/FetchController.h>
  17. #include <LibWeb/Fetch/Infrastructure/HTTP/Headers.h>
  18. #include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
  19. #include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
  20. #include <LibWeb/HTML/CORSSettingAttribute.h>
  21. #include <LibWeb/HTML/EventLoop/EventLoop.h>
  22. #include <LibWeb/HTML/EventNames.h>
  23. #include <LibWeb/HTML/EventSource.h>
  24. #include <LibWeb/HTML/MessageEvent.h>
  25. #include <LibWeb/HTML/PotentialCORSRequest.h>
  26. #include <LibWeb/HTML/Scripting/Environments.h>
  27. #include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
  28. namespace Web::HTML {
  29. GC_DEFINE_ALLOCATOR(EventSource);
  30. // https://html.spec.whatwg.org/multipage/server-sent-events.html#dom-eventsource
  31. WebIDL::ExceptionOr<GC::Ref<EventSource>> EventSource::construct_impl(JS::Realm& realm, StringView url, EventSourceInit event_source_init_dict)
  32. {
  33. auto& vm = realm.vm();
  34. // 1. Let ev be a new EventSource object.
  35. auto event_source = realm.create<EventSource>(realm);
  36. // 2. Let settings be ev's relevant settings object.
  37. auto& settings = relevant_settings_object(event_source);
  38. // 3. Let urlRecord be the result of encoding-parsing a URL given url, relative to settings.
  39. auto url_record = settings.encoding_parse_url(url);
  40. // 4. If urlRecord is failure, then throw a "SyntaxError" DOMException.
  41. if (!url_record.is_valid())
  42. return WebIDL::SyntaxError::create(realm, MUST(String::formatted("Invalid URL '{}'", url)));
  43. // 5. Set ev's url to urlRecord.
  44. event_source->m_url = move(url_record);
  45. // 6. Let corsAttributeState be Anonymous.
  46. auto cors_attribute_state = CORSSettingAttribute::Anonymous;
  47. // 7. If the value of eventSourceInitDict's withCredentials member is true, then set corsAttributeState to Use Credentials
  48. // and set ev's withCredentials attribute to true.
  49. if (event_source_init_dict.with_credentials) {
  50. cors_attribute_state = CORSSettingAttribute::UseCredentials;
  51. event_source->m_with_credentials = true;
  52. }
  53. // 8. Let request be the result of creating a potential-CORS request given urlRecord, the empty string, and corsAttributeState.
  54. auto request = create_potential_CORS_request(vm, event_source->m_url, {}, cors_attribute_state);
  55. // 9. Set request's client to settings.
  56. request->set_client(&settings);
  57. // 10. User agents may set (`Accept`, `text/event-stream`) in request's header list.
  58. auto header = Fetch::Infrastructure::Header::from_string_pair("Accept"sv, "text/event-stream"sv);
  59. request->header_list()->set(move(header));
  60. // 11. Set request's cache mode to "no-store".
  61. request->set_cache_mode(Fetch::Infrastructure::Request::CacheMode::NoStore);
  62. // 12. Set request's initiator type to "other".
  63. request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Other);
  64. // AD-HOC: We must not buffer the response as the connection generally never ends, thus we can't wait for the end
  65. // of the response body.
  66. request->set_buffer_policy(Fetch::Infrastructure::Request::BufferPolicy::DoNotBufferResponse);
  67. // 13. Set ev's request to request.
  68. event_source->m_request = request;
  69. // 14. Let processEventSourceEndOfBody given response res be the following step: if res is not a network error, then
  70. // reestablish the connection.
  71. auto process_event_source_end_of_body = [event_source](GC::Ref<Fetch::Infrastructure::Response> response) {
  72. if (!response->is_network_error())
  73. event_source->reestablish_the_connection();
  74. };
  75. // 15. Fetch request, with processResponseEndOfBody set to processEventSourceEndOfBody and processResponse set to the
  76. // following steps given response res:
  77. Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
  78. fetch_algorithms_input.process_response_end_of_body = move(process_event_source_end_of_body);
  79. fetch_algorithms_input.process_response = [event_source](GC::Ref<Fetch::Infrastructure::Response> response) {
  80. auto& realm = event_source->realm();
  81. // FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See:
  82. // https://github.com/whatwg/html/issues/9355
  83. response = response->unsafe_response();
  84. auto content_type_is_text_event_stream = [&]() {
  85. auto content_type = response->header_list()->extract_mime_type();
  86. if (!content_type.has_value())
  87. return false;
  88. return content_type->essence() == "text/event-stream"sv;
  89. };
  90. // 1. If res is an aborted network error, then fail the connection.
  91. if (response->is_aborted_network_error()) {
  92. event_source->fail_the_connection();
  93. }
  94. // 2. Otherwise, if res is a network error, then reestablish the connection, unless the user agent knows that
  95. // to be futile, in which case the user agent may fail the connection.
  96. else if (response->is_network_error()) {
  97. event_source->reestablish_the_connection();
  98. }
  99. // 3. Otherwise, if res's status is not 200, or if res's `Content-Type` is not `text/event-stream`, then fail
  100. // the connection.
  101. else if (response->status() != 200 || !content_type_is_text_event_stream()) {
  102. event_source->fail_the_connection();
  103. }
  104. // 4. Otherwise, announce the connection and interpret res's body line by line.
  105. else {
  106. event_source->announce_the_connection();
  107. auto process_body_chunk = GC::create_function(realm.heap(), [event_source, pending_data = ByteBuffer()](ByteBuffer body) mutable {
  108. if (pending_data.is_empty())
  109. pending_data = move(body);
  110. else
  111. pending_data.append(body);
  112. auto last_line_break = AK::StringUtils::find_any_of(pending_data, "\r\n"sv, AK::StringUtils::SearchDirection::Backward);
  113. if (!last_line_break.has_value())
  114. return;
  115. auto end_index = *last_line_break + 1;
  116. event_source->interpret_response({ pending_data.bytes().slice(0, end_index) });
  117. pending_data = MUST(pending_data.slice(end_index, pending_data.size() - end_index));
  118. });
  119. auto process_end_of_body = GC::create_function(realm.heap(), []() {
  120. // This case is handled by `process_event_source_end_of_body` above.
  121. });
  122. auto process_body_error = GC::create_function(realm.heap(), [](JS::Value) {
  123. // This case is handled by `process_event_source_end_of_body` above.
  124. });
  125. response->body()->incrementally_read(process_body_chunk, process_end_of_body, process_body_error, { realm.global_object() });
  126. }
  127. };
  128. event_source->m_fetch_algorithms = Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input));
  129. event_source->m_fetch_controller = TRY(Fetch::Fetching::fetch(realm, request, *event_source->m_fetch_algorithms));
  130. // 16. Return ev.
  131. return event_source;
  132. }
  133. EventSource::EventSource(JS::Realm& realm)
  134. : EventTarget(realm)
  135. {
  136. }
  137. EventSource::~EventSource() = default;
  138. void EventSource::initialize(JS::Realm& realm)
  139. {
  140. Base::initialize(realm);
  141. WEB_SET_PROTOTYPE_FOR_INTERFACE(EventSource);
  142. auto* relevant_global = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&HTML::relevant_global_object(*this));
  143. VERIFY(relevant_global);
  144. relevant_global->register_event_source({}, *this);
  145. }
  146. // https://html.spec.whatwg.org/multipage/server-sent-events.html#garbage-collection
  147. void EventSource::finalize()
  148. {
  149. // If an EventSource object is garbage collected while its connection is still open, the user agent must abort any
  150. // instance of the fetch algorithm opened by this EventSource.
  151. if (m_ready_state != ReadyState::Closed) {
  152. if (m_fetch_controller)
  153. m_fetch_controller->abort(realm(), {});
  154. }
  155. auto* relevant_global = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&HTML::relevant_global_object(*this));
  156. VERIFY(relevant_global);
  157. relevant_global->unregister_event_source({}, *this);
  158. }
  159. void EventSource::visit_edges(Cell::Visitor& visitor)
  160. {
  161. Base::visit_edges(visitor);
  162. visitor.visit(m_request);
  163. visitor.visit(m_fetch_algorithms);
  164. visitor.visit(m_fetch_controller);
  165. }
  166. // https://html.spec.whatwg.org/multipage/server-sent-events.html#handler-eventsource-onopen
  167. void EventSource::set_onopen(WebIDL::CallbackType* event_handler)
  168. {
  169. set_event_handler_attribute(HTML::EventNames::open, event_handler);
  170. }
  171. // https://html.spec.whatwg.org/multipage/server-sent-events.html#handler-eventsource-onopen
  172. WebIDL::CallbackType* EventSource::onopen()
  173. {
  174. return event_handler_attribute(HTML::EventNames::open);
  175. }
  176. // https://html.spec.whatwg.org/multipage/server-sent-events.html#handler-eventsource-onmessage
  177. void EventSource::set_onmessage(WebIDL::CallbackType* event_handler)
  178. {
  179. set_event_handler_attribute(HTML::EventNames::message, event_handler);
  180. }
  181. // https://html.spec.whatwg.org/multipage/server-sent-events.html#handler-eventsource-onmessage
  182. WebIDL::CallbackType* EventSource::onmessage()
  183. {
  184. return event_handler_attribute(HTML::EventNames::message);
  185. }
  186. // https://html.spec.whatwg.org/multipage/server-sent-events.html#handler-eventsource-onerror
  187. void EventSource::set_onerror(WebIDL::CallbackType* event_handler)
  188. {
  189. set_event_handler_attribute(HTML::EventNames::error, event_handler);
  190. }
  191. // https://html.spec.whatwg.org/multipage/server-sent-events.html#handler-eventsource-onerror
  192. WebIDL::CallbackType* EventSource::onerror()
  193. {
  194. return event_handler_attribute(HTML::EventNames::error);
  195. }
  196. // https://html.spec.whatwg.org/multipage/server-sent-events.html#dom-eventsource-close
  197. void EventSource::close()
  198. {
  199. // The close() method must abort any instances of the fetch algorithm started for this EventSource object, and must
  200. // set the readyState attribute to CLOSED.
  201. if (m_fetch_controller)
  202. m_fetch_controller->abort(realm(), {});
  203. m_ready_state = ReadyState::Closed;
  204. }
  205. // https://html.spec.whatwg.org/multipage/server-sent-events.html#concept-eventsource-forcibly-close
  206. void EventSource::forcibly_close()
  207. {
  208. // If a user agent is to forcibly close an EventSource object (this happens when a Document object goes away
  209. // permanently), the user agent must abort any instances of the fetch algorithm started for this EventSource
  210. // object, and must set the readyState attribute to CLOSED.
  211. if (m_fetch_controller)
  212. m_fetch_controller->abort(realm(), {});
  213. m_ready_state = ReadyState::Closed;
  214. }
  215. // https://html.spec.whatwg.org/multipage/server-sent-events.html#announce-the-connection
  216. void EventSource::announce_the_connection()
  217. {
  218. // When a user agent is to announce the connection, the user agent must queue a task which, if the readyState attribute
  219. // is set to a value other than CLOSED, sets the readyState attribute to OPEN and fires an event named open at the
  220. // EventSource object.
  221. HTML::queue_a_task(HTML::Task::Source::RemoteEvent, nullptr, nullptr, GC::create_function(heap(), [this]() {
  222. if (m_ready_state != ReadyState::Closed) {
  223. m_ready_state = ReadyState::Open;
  224. dispatch_event(DOM::Event::create(realm(), HTML::EventNames::open));
  225. }
  226. }));
  227. }
  228. // https://html.spec.whatwg.org/multipage/server-sent-events.html#reestablish-the-connection
  229. void EventSource::reestablish_the_connection()
  230. {
  231. IGNORE_USE_IN_ESCAPING_LAMBDA bool initial_task_has_run { false };
  232. // 1. Queue a task to run the following steps:
  233. HTML::queue_a_task(HTML::Task::Source::RemoteEvent, nullptr, nullptr, GC::create_function(heap(), [&]() {
  234. ScopeGuard guard { [&]() { initial_task_has_run = true; } };
  235. // 1. If the readyState attribute is set to CLOSED, abort the task.
  236. if (m_ready_state == ReadyState::Closed)
  237. return;
  238. // 2. Set the readyState attribute to CONNECTING.
  239. m_ready_state = ReadyState::Connecting;
  240. // 3. Fire an event named error at the EventSource object.
  241. dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error));
  242. }));
  243. // 2. Wait a delay equal to the reconnection time of the event source.
  244. HTML::main_thread_event_loop().spin_until(GC::create_function(heap(), [&, delay_start = MonotonicTime::now()]() {
  245. return (MonotonicTime::now() - delay_start) >= m_reconnection_time;
  246. }));
  247. // 3. Optionally, wait some more. In particular, if the previous attempt failed, then user agents might introduce
  248. // an exponential backoff delay to avoid overloading a potentially already overloaded server. Alternatively, if
  249. // the operating system has reported that there is no network connectivity, user agents might wait for the
  250. // operating system to announce that the network connection has returned before retrying.
  251. // 4. Wait until the aforementioned task has run, if it has not yet run.
  252. if (!initial_task_has_run) {
  253. HTML::main_thread_event_loop().spin_until(GC::create_function(heap(), [&]() { return initial_task_has_run; }));
  254. }
  255. // 5. Queue a task to run the following steps:
  256. HTML::queue_a_task(HTML::Task::Source::RemoteEvent, nullptr, nullptr, GC::create_function(heap(), [this]() {
  257. // 1. If the EventSource object's readyState attribute is not set to CONNECTING, then return.
  258. if (m_ready_state != ReadyState::Connecting)
  259. return;
  260. // 2. Let request be the EventSource object's request.
  261. GC::Ref request { *m_request };
  262. // 3. If the EventSource object's last event ID string is not the empty string, then:
  263. if (!m_last_event_id.is_empty()) {
  264. // 1. Let lastEventIDValue be the EventSource object's last event ID string, encoded as UTF-8.
  265. // 2. Set (`Last-Event-ID`, lastEventIDValue) in request's header list.
  266. auto header = Fetch::Infrastructure::Header::from_string_pair("Last-Event-ID"sv, m_last_event_id);
  267. request->header_list()->set(header);
  268. }
  269. // 4. Fetch request and process the response obtained in this fashion, if any, as described earlier in this section.
  270. m_fetch_controller = Fetch::Fetching::fetch(realm(), request, *m_fetch_algorithms).release_value_but_fixme_should_propagate_errors();
  271. }));
  272. }
  273. // https://html.spec.whatwg.org/multipage/server-sent-events.html#fail-the-connection
  274. void EventSource::fail_the_connection()
  275. {
  276. // When a user agent is to fail the connection, the user agent must queue a task which, if the readyState attribute
  277. // is set to a value other than CLOSED, sets the readyState attribute to CLOSED and fires an event named error at the
  278. // EventSource object. Once the user agent has failed the connection, it does not attempt to reconnect.
  279. HTML::queue_a_task(HTML::Task::Source::RemoteEvent, nullptr, nullptr, GC::create_function(heap(), [this]() {
  280. if (m_ready_state != ReadyState::Closed) {
  281. m_ready_state = ReadyState::Closed;
  282. dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error));
  283. }
  284. }));
  285. }
  286. // https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation
  287. void EventSource::interpret_response(StringView response)
  288. {
  289. // Lines must be processed, in the order they are received, as follows:
  290. for (auto line : response.lines(StringView::ConsiderCarriageReturn::Yes)) {
  291. // -> If the line is empty (a blank line)
  292. if (line.is_empty()) {
  293. // Dispatch the event, as defined below.
  294. dispatch_the_event();
  295. }
  296. // -> If the line starts with a U+003A COLON character (:)
  297. else if (line.starts_with(':')) {
  298. // Ignore the line.
  299. }
  300. // -> If the line contains a U+003A COLON character (:)
  301. else if (auto index = line.find(':'); index.has_value()) {
  302. // Collect the characters on the line before the first U+003A COLON character (:), and let field be that string.
  303. auto field = line.substring_view(0, *index);
  304. // Collect the characters on the line after the first U+003A COLON character (:), and let value be that string.
  305. // If value starts with a U+0020 SPACE character, remove it from value.
  306. auto value = line.substring_view(*index + 1);
  307. if (value.starts_with(' '))
  308. value = value.substring_view(1);
  309. // Process the field using the steps described below, using field as the field name and value as the field value.
  310. process_field(field, value);
  311. }
  312. // -> Otherwise, the string is not empty but does not contain a U+003A COLON character (:)
  313. else {
  314. // Process the field using the steps described below, using the whole line as the field name, and the empty
  315. // string as the field value.
  316. process_field(line, {});
  317. }
  318. }
  319. }
  320. // https://html.spec.whatwg.org/multipage/server-sent-events.html#processField
  321. void EventSource::process_field(StringView field, StringView value)
  322. {
  323. // -> If the field name is "event"
  324. if (field == "event"sv) {
  325. // Set the event type buffer to field value.
  326. m_event_type = MUST(String::from_utf8(value));
  327. }
  328. // -> If the field name is "data"
  329. else if (field == "data"sv) {
  330. // Append the field value to the data buffer, then append a single U+000A LINE FEED (LF) character to the data buffer.
  331. m_data.append(value);
  332. m_data.append('\n');
  333. }
  334. // -> If the field name is "id"
  335. else if (field == "id"sv) {
  336. // If the field value does not contain U+0000 NULL, then set the last event ID buffer to the field value.
  337. // Otherwise, ignore the field.
  338. if (!value.contains('\0'))
  339. m_last_event_id = MUST(String::from_utf8(value));
  340. }
  341. // -> If the field name is "retry"
  342. else if (field == "retry"sv) {
  343. // If the field value consists of only ASCII digits, then interpret the field value as an integer in base ten,
  344. // and set the event stream's reconnection time to that integer. Otherwise, ignore the field.
  345. if (auto retry = value.to_number<i64>(); retry.has_value())
  346. m_reconnection_time = AK::Duration::from_seconds(*retry);
  347. }
  348. // -> Otherwise
  349. else {
  350. // The field is ignored.
  351. }
  352. }
  353. // https://html.spec.whatwg.org/multipage/server-sent-events.html#dispatchMessage
  354. void EventSource::dispatch_the_event()
  355. {
  356. // 1. Set the last event ID string of the event source to the value of the last event ID buffer. The buffer does not
  357. // get reset, so the last event ID string of the event source remains set to this value until the next time it is
  358. // set by the server.
  359. auto const& last_event_id = m_last_event_id;
  360. // 2. If the data buffer is an empty string, set the data buffer and the event type buffer to the empty string and return.
  361. auto data_buffer = m_data.string_view();
  362. if (data_buffer.is_empty()) {
  363. m_event_type = {};
  364. m_data.clear();
  365. return;
  366. }
  367. // 3. If the data buffer's last character is a U+000A LINE FEED (LF) character, then remove the last character from the data buffer.
  368. if (data_buffer.ends_with('\n'))
  369. data_buffer = data_buffer.substring_view(0, data_buffer.length() - 1);
  370. // 4. Let event be the result of creating an event using MessageEvent, in the relevant realm of the EventSource object.
  371. // 5. Initialize event's type attribute to "message", its data attribute to data, its origin attribute to the serialization
  372. // of the origin of the event stream's final URL (i.e., the URL after redirects), and its lastEventId attribute to the
  373. // last event ID string of the event source.
  374. // 6. If the event type buffer has a value other than the empty string, change the type of the newly created event to equal
  375. // the value of the event type buffer.
  376. MessageEventInit init {};
  377. init.data = JS::PrimitiveString::create(vm(), data_buffer);
  378. init.origin = m_url.origin().serialize();
  379. init.last_event_id = last_event_id;
  380. auto type = m_event_type.is_empty() ? HTML::EventNames::message : m_event_type;
  381. auto event = MessageEvent::create(realm(), type, init);
  382. // 7. Set the data buffer and the event type buffer to the empty string.
  383. m_event_type = {};
  384. m_data.clear();
  385. // 8. Queue a task which, if the readyState attribute is set to a value other than CLOSED, dispatches the newly created
  386. // event at the EventSource object.
  387. HTML::queue_a_task(HTML::Task::Source::RemoteEvent, nullptr, nullptr, GC::create_function(heap(), [this, event]() {
  388. if (m_ready_state != ReadyState::Closed)
  389. dispatch_event(event);
  390. }));
  391. }
  392. }