Skip to content

Commit

Permalink
LibWeb: Implement EventSource for server-sent events
Browse files Browse the repository at this point in the history
EventSource allows opening a persistent HTTP connection to a server over
which events are continuously streamed.

Unfortunately, our test infrastructure does not allow for automating any
tests of this feature yet. It only works with HTTP connections.
  • Loading branch information
trflynn89 committed May 26, 2024
1 parent 6f5970d commit 260ffe6
Show file tree
Hide file tree
Showing 12 changed files with 619 additions and 1 deletion.
1 change: 1 addition & 0 deletions Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ source_set("HTML") {
"ErrorEvent.cpp",
"EventHandler.cpp",
"EventNames.cpp",
"EventSource.cpp",
"FileFilter.cpp",
"Focus.cpp",
"FormAssociatedElement.cpp",
Expand Down
1 change: 1 addition & 0 deletions Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ standard_idl_files = [
"//Userland/Libraries/LibWeb/HTML/DOMParser.idl",
"//Userland/Libraries/LibWeb/HTML/DOMStringMap.idl",
"//Userland/Libraries/LibWeb/HTML/ErrorEvent.idl",
"//Userland/Libraries/LibWeb/HTML/EventSource.idl",
"//Userland/Libraries/LibWeb/HTML/FormDataEvent.idl",
"//Userland/Libraries/LibWeb/HTML/HashChangeEvent.idl",
"//Userland/Libraries/LibWeb/HTML/History.idl",
Expand Down
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ set(SOURCES
HTML/DataTransfer.cpp
HTML/ErrorEvent.cpp
HTML/EventHandler.cpp
HTML/EventSource.cpp
HTML/EventLoop/EventLoop.cpp
HTML/EventLoop/Task.cpp
HTML/EventLoop/TaskQueue.cpp
Expand Down
3 changes: 2 additions & 1 deletion Userland/Libraries/LibWeb/DOM/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3118,7 +3118,8 @@ void Document::run_unloading_cleanup_steps()

// 4. If document's salvageable state is false, then:
if (!m_salvageable) {
// FIXME: 1. For each EventSource object eventSource whose relevant global object is equal to window, forcibly close eventSource.
// 1. For each EventSource object eventSource whose relevant global object is equal to window, forcibly close eventSource.
window->forcibly_close_all_event_sources();

// 2. Clear window's map of active timers.
window->clear_map_of_active_timers();
Expand Down
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/Forward.h
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ class DOMStringMap;
class ErrorEvent;
class EventHandler;
class EventLoop;
class EventSource;
class FormAssociatedElement;
class FormDataEvent;
class History;
Expand Down
3 changes: 3 additions & 0 deletions Userland/Libraries/LibWeb/HTML/EventLoop/Task.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ class Task final : public JS::Cell {
// https://drafts.csswg.org/css-font-loading/#task-source
FontLoading,

// https://html.spec.whatwg.org/multipage/server-sent-events.html#remote-event-task-source
RemoteEvent,

// !!! IMPORTANT: Keep this field last!
// This serves as the base value of all unique task sources.
// Some elements, such as the HTMLMediaElement, must have a unique task source per instance.
Expand Down
457 changes: 457 additions & 0 deletions Userland/Libraries/LibWeb/HTML/EventSource.cpp

Large diffs are not rendered by default.

101 changes: 101 additions & 0 deletions Userland/Libraries/LibWeb/HTML/EventSource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <AK/Time.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap/GCPtr.h>
#include <LibURL/URL.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/Forward.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
#include <LibWeb/WebIDL/Types.h>

namespace Web::HTML {

struct EventSourceInit {
bool with_credentials { false };
};

class EventSource : public DOM::EventTarget {
WEB_PLATFORM_OBJECT(EventSource, DOM::EventTarget);
JS_DECLARE_ALLOCATOR(EventSource);

public:
virtual ~EventSource() override;

static WebIDL::ExceptionOr<JS::NonnullGCPtr<EventSource>> construct_impl(JS::Realm&, StringView url, EventSourceInit event_source_init_dict = {});

// https://html.spec.whatwg.org/multipage/server-sent-events.html#dom-eventsource-url
String url() const { return MUST(String::from_byte_string(m_url.serialize())); }

// https://html.spec.whatwg.org/multipage/server-sent-events.html#dom-eventsource-withcredentials
bool with_credentials() const { return m_with_credentials; }

enum class ReadyState : WebIDL::UnsignedShort {
Connecting = 0,
Open = 1,
Closed = 2,
};

// https://html.spec.whatwg.org/multipage/server-sent-events.html#dom-eventsource-readystate
ReadyState ready_state() const { return m_ready_state; }

void set_onopen(WebIDL::CallbackType*);
WebIDL::CallbackType* onopen();

void set_onmessage(WebIDL::CallbackType*);
WebIDL::CallbackType* onmessage();

void set_onerror(WebIDL::CallbackType*);
WebIDL::CallbackType* onerror();

void close();
void forcibly_close();

private:
explicit EventSource(JS::Realm&);

virtual void initialize(JS::Realm&) override;
virtual void finalize() override;
virtual void visit_edges(Cell::Visitor&) override;

void announce_the_connection();
void reestablish_the_connection();
void fail_the_connection();

void interpret_response(StringView);
void process_field(StringView field, StringView value);
void dispatch_the_event();

// https://html.spec.whatwg.org/multipage/server-sent-events.html#concept-eventsource-url
URL::URL m_url;

// https://html.spec.whatwg.org/multipage/server-sent-events.html#concept-event-stream-request
JS::GCPtr<Fetch::Infrastructure::Request> m_request;

// https://html.spec.whatwg.org/multipage/server-sent-events.html#concept-event-stream-reconnection-time
Duration m_reconnection_time { Duration::from_seconds(3) };

// https://html.spec.whatwg.org/multipage/server-sent-events.html#concept-event-stream-last-event-id
String m_last_event_id;

String m_event_type;
StringBuilder m_data;

bool m_with_credentials { false };

ReadyState m_ready_state { ReadyState::Connecting };

JS::GCPtr<Fetch::Infrastructure::FetchAlgorithms> m_fetch_algorithms;
JS::GCPtr<Fetch::Infrastructure::FetchController> m_fetch_controller;
};

}
27 changes: 27 additions & 0 deletions Userland/Libraries/LibWeb/HTML/EventSource.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#import <DOM/EventHandler.idl>
#import <DOM/EventTarget.idl>

// https://html.spec.whatwg.org/multipage/server-sent-events.html#eventsource
[Exposed=(Window,Worker)]
interface EventSource : EventTarget {
constructor(USVString url, optional EventSourceInit eventSourceInitDict = {});

readonly attribute USVString url;
readonly attribute boolean withCredentials;

// ready state
const unsigned short CONNECTING = 0;
const unsigned short OPEN = 1;
const unsigned short CLOSED = 2;
readonly attribute unsigned short readyState;

// networking
attribute EventHandler onopen;
attribute EventHandler onmessage;
attribute EventHandler onerror;
undefined close();
};

dictionary EventSourceInit {
boolean withCredentials = false;
};
18 changes: 18 additions & 0 deletions Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <LibWeb/Fetch/FetchMethod.h>
#include <LibWeb/HTML/CanvasRenderingContext2D.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
#include <LibWeb/HTML/EventSource.h>
#include <LibWeb/HTML/ImageBitmap.h>
#include <LibWeb/HTML/Scripting/ClassicScript.h>
#include <LibWeb/HTML/Scripting/Environments.h>
Expand Down Expand Up @@ -70,6 +71,7 @@ void WindowOrWorkerGlobalScopeMixin::visit_edges(JS::Cell::Visitor& visitor)
visitor.visit(m_indexed_db);
for (auto& entry : m_performance_entry_buffer_map)
entry.value.visit_edges(visitor);
visitor.visit(m_registered_event_sources);
}

void WindowOrWorkerGlobalScopeMixin::finalize()
Expand Down Expand Up @@ -649,6 +651,22 @@ void WindowOrWorkerGlobalScopeMixin::queue_the_performance_observer_task()
}));
}

void WindowOrWorkerGlobalScopeMixin::register_event_source(Badge<EventSource>, JS::NonnullGCPtr<EventSource> event_source)
{
m_registered_event_sources.set(event_source);
}

void WindowOrWorkerGlobalScopeMixin::unregister_event_source(Badge<EventSource>, JS::NonnullGCPtr<EventSource> event_source)
{
m_registered_event_sources.remove(event_source);
}

void WindowOrWorkerGlobalScopeMixin::forcibly_close_all_event_sources()
{
for (auto event_source : m_registered_event_sources)
event_source->forcibly_close();
}

// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#run-steps-after-a-timeout
void WindowOrWorkerGlobalScopeMixin::run_steps_after_a_timeout(i32 timeout, Function<void()> completion_step)
{
Expand Down
6 changes: 6 additions & 0 deletions Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ class WindowOrWorkerGlobalScopeMixin {

void queue_the_performance_observer_task();

void register_event_source(Badge<EventSource>, JS::NonnullGCPtr<EventSource>);
void unregister_event_source(Badge<EventSource>, JS::NonnullGCPtr<EventSource>);
void forcibly_close_all_event_sources();

void run_steps_after_a_timeout(i32 timeout, Function<void()> completion_step);

[[nodiscard]] JS::NonnullGCPtr<HighResolutionTime::Performance> performance();
Expand Down Expand Up @@ -103,6 +107,8 @@ class WindowOrWorkerGlobalScopeMixin {
// NOTE: See the PerformanceEntryTuple struct above for the map's value tuple.
OrderedHashMap<FlyString, PerformanceTimeline::PerformanceEntryTuple> m_performance_entry_buffer_map;

HashTable<JS::NonnullGCPtr<EventSource>> m_registered_event_sources;

JS::GCPtr<HighResolutionTime::Performance> m_performance;

JS::GCPtr<IndexedDB::IDBFactory> m_indexed_db;
Expand Down
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/idl_files.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ libweb_js_bindings(HTML/DOMParser)
libweb_js_bindings(HTML/DOMStringMap)
libweb_js_bindings(HTML/DataTransfer)
libweb_js_bindings(HTML/ErrorEvent)
libweb_js_bindings(HTML/EventSource)
libweb_js_bindings(HTML/FormDataEvent)
libweb_js_bindings(HTML/HashChangeEvent)
libweb_js_bindings(HTML/History)
Expand Down

0 comments on commit 260ffe6

Please sign in to comment.