Serenity Operating System
1/*
2 * Copyright (c) 2021-2022, Dex♪ <dexes.ttp@gmail.com>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/QuickSort.h>
8#include <LibJS/Runtime/ArrayBuffer.h>
9#include <LibJS/Runtime/FunctionObject.h>
10#include <LibWeb/DOM/Document.h>
11#include <LibWeb/DOM/Event.h>
12#include <LibWeb/DOM/EventDispatcher.h>
13#include <LibWeb/DOM/IDLEventListener.h>
14#include <LibWeb/HTML/CloseEvent.h>
15#include <LibWeb/HTML/EventHandler.h>
16#include <LibWeb/HTML/EventNames.h>
17#include <LibWeb/HTML/MessageEvent.h>
18#include <LibWeb/HTML/Origin.h>
19#include <LibWeb/HTML/Window.h>
20#include <LibWeb/WebIDL/DOMException.h>
21#include <LibWeb/WebIDL/ExceptionOr.h>
22#include <LibWeb/WebSockets/WebSocket.h>
23
24namespace Web::WebSockets {
25
26static RefPtr<WebSocketClientManager> s_websocket_client_manager;
27
28void WebSocketClientManager::initialize(RefPtr<WebSocketClientManager> websocket_client_manager)
29{
30 s_websocket_client_manager = websocket_client_manager;
31}
32
33WebSocketClientManager& WebSocketClientManager::the()
34{
35 if (!s_websocket_client_manager) [[unlikely]] {
36 dbgln("Web::WebSockets::WebSocketClientManager was not initialized!");
37 VERIFY_NOT_REACHED();
38 }
39 return *s_websocket_client_manager;
40}
41
42WebSocketClientSocket::WebSocketClientSocket() = default;
43
44WebSocketClientSocket::~WebSocketClientSocket() = default;
45
46WebSocketClientManager::WebSocketClientManager() = default;
47
48// https://websockets.spec.whatwg.org/#dom-websocket-websocket
49WebIDL::ExceptionOr<JS::NonnullGCPtr<WebSocket>> WebSocket::construct_impl(JS::Realm& realm, DeprecatedString const& url, Optional<Variant<DeprecatedString, Vector<DeprecatedString>>> const& protocols)
50{
51 auto& window = verify_cast<HTML::Window>(realm.global_object());
52 AK::URL url_record(url);
53 if (!url_record.is_valid())
54 return WebIDL::SyntaxError::create(realm, "Invalid URL");
55 if (!url_record.scheme().is_one_of("ws", "wss"))
56 return WebIDL::SyntaxError::create(realm, "Invalid protocol");
57 if (!url_record.fragment().is_empty())
58 return WebIDL::SyntaxError::create(realm, "Presence of URL fragment is invalid");
59 Vector<DeprecatedString> protocols_sequence;
60 if (protocols.has_value()) {
61 // 5. If `protocols` is a string, set `protocols` to a sequence consisting of just that string
62 if (protocols.value().has<DeprecatedString>())
63 protocols_sequence = { protocols.value().get<DeprecatedString>() };
64 else
65 protocols_sequence = protocols.value().get<Vector<DeprecatedString>>();
66 // 6. If any of the values in `protocols` occur more than once or otherwise fail to match the requirements, throw SyntaxError
67 auto sorted_protocols = protocols_sequence;
68 quick_sort(sorted_protocols);
69 for (size_t i = 0; i < sorted_protocols.size(); i++) {
70 // https://datatracker.ietf.org/doc/html/rfc6455
71 // The elements that comprise this value MUST be non-empty strings with characters in the range U+0021 to U+007E not including
72 // separator characters as defined in [RFC2616] and MUST all be unique strings.
73 auto protocol = sorted_protocols[i];
74 if (i < sorted_protocols.size() - 1 && protocol == sorted_protocols[i + 1])
75 return WebIDL::SyntaxError::create(realm, "Found a duplicate protocol name in the specified list");
76 for (auto character : protocol) {
77 if (character < '\x21' || character > '\x7E')
78 return WebIDL::SyntaxError::create(realm, "Found invalid character in subprotocol name");
79 }
80 }
81 }
82 return MUST_OR_THROW_OOM(realm.heap().allocate<WebSocket>(realm, window, url_record, protocols_sequence));
83}
84
85WebSocket::WebSocket(HTML::Window& window, AK::URL& url, Vector<DeprecatedString> const& protocols)
86 : EventTarget(window.realm())
87 , m_window(window)
88{
89 // FIXME: Integrate properly with FETCH as per https://fetch.spec.whatwg.org/#websocket-opening-handshake
90 auto origin_string = m_window->associated_document().origin().serialize();
91 m_websocket = WebSocketClientManager::the().connect(url, origin_string, protocols);
92 m_websocket->on_open = [weak_this = make_weak_ptr<WebSocket>()] {
93 if (!weak_this)
94 return;
95 auto& websocket = const_cast<WebSocket&>(*weak_this);
96 websocket.on_open();
97 };
98 m_websocket->on_message = [weak_this = make_weak_ptr<WebSocket>()](auto message) {
99 if (!weak_this)
100 return;
101 auto& websocket = const_cast<WebSocket&>(*weak_this);
102 websocket.on_message(move(message.data), message.is_text);
103 };
104 m_websocket->on_close = [weak_this = make_weak_ptr<WebSocket>()](auto code, auto reason, bool was_clean) {
105 if (!weak_this)
106 return;
107 auto& websocket = const_cast<WebSocket&>(*weak_this);
108 websocket.on_close(code, reason, was_clean);
109 };
110 m_websocket->on_error = [weak_this = make_weak_ptr<WebSocket>()](auto) {
111 if (!weak_this)
112 return;
113 auto& websocket = const_cast<WebSocket&>(*weak_this);
114 websocket.on_error();
115 };
116}
117
118WebSocket::~WebSocket() = default;
119
120JS::ThrowCompletionOr<void> WebSocket::initialize(JS::Realm& realm)
121{
122 MUST_OR_THROW_OOM(Base::initialize(realm));
123 set_prototype(&Bindings::ensure_web_prototype<Bindings::WebSocketPrototype>(realm, "WebSocket"));
124
125 return {};
126}
127
128void WebSocket::visit_edges(Cell::Visitor& visitor)
129{
130 Base::visit_edges(visitor);
131 visitor.visit(m_window.ptr());
132}
133
134// https://websockets.spec.whatwg.org/#dom-websocket-readystate
135WebSocket::ReadyState WebSocket::ready_state() const
136{
137 if (!m_websocket)
138 return WebSocket::ReadyState::Closed;
139 return const_cast<WebSocketClientSocket&>(*m_websocket).ready_state();
140}
141
142// https://websockets.spec.whatwg.org/#dom-websocket-extensions
143DeprecatedString WebSocket::extensions() const
144{
145 if (!m_websocket)
146 return DeprecatedString::empty();
147 // https://websockets.spec.whatwg.org/#feedback-from-the-protocol
148 // FIXME: Change the extensions attribute's value to the extensions in use, if it is not the null value.
149 return DeprecatedString::empty();
150}
151
152// https://websockets.spec.whatwg.org/#dom-websocket-protocol
153DeprecatedString WebSocket::protocol() const
154{
155 if (!m_websocket)
156 return DeprecatedString::empty();
157 return m_websocket->subprotocol_in_use();
158}
159
160// https://websockets.spec.whatwg.org/#dom-websocket-close
161WebIDL::ExceptionOr<void> WebSocket::close(Optional<u16> code, Optional<DeprecatedString> reason)
162{
163 // 1. If code is present, but is neither an integer equal to 1000 nor an integer in the range 3000 to 4999, inclusive, throw an "InvalidAccessError" DOMException.
164 if (code.has_value() && *code != 1000 && (*code < 3000 || *code > 4099))
165 return WebIDL::InvalidAccessError::create(realm(), "The close error code is invalid");
166 // 2. If reason is present, then run these substeps:
167 if (reason.has_value()) {
168 // 1. Let reasonBytes be the result of encoding reason.
169 // 2. If reasonBytes is longer than 123 bytes, then throw a "SyntaxError" DOMException.
170 if (reason->bytes().size() > 123)
171 return WebIDL::SyntaxError::create(realm(), "The close reason is longer than 123 bytes");
172 }
173 // 3. Run the first matching steps from the following list:
174 auto state = ready_state();
175 // -> If this's ready state is CLOSING (2) or CLOSED (3)
176 if (state == WebSocket::ReadyState::Closing || state == WebSocket::ReadyState::Closed)
177 return {};
178 // -> If the WebSocket connection is not yet established [WSP]
179 // -> If the WebSocket closing handshake has not yet been started [WSP]
180 // -> Otherwise
181 // NOTE: All of these are handled by the WebSocket Protocol when calling close()
182 // FIXME: LibProtocol does not yet support sending empty Close messages, so we use default values for now
183 m_websocket->close(code.value_or(1000), reason.value_or(DeprecatedString::empty()));
184 return {};
185}
186
187// https://websockets.spec.whatwg.org/#dom-websocket-send
188WebIDL::ExceptionOr<void> WebSocket::send(DeprecatedString const& data)
189{
190 auto state = ready_state();
191 if (state == WebSocket::ReadyState::Connecting)
192 return WebIDL::InvalidStateError::create(realm(), "Websocket is still CONNECTING");
193 if (state == WebSocket::ReadyState::Open) {
194 m_websocket->send(data);
195 // TODO : If the data cannot be sent, e.g. because it would need to be buffered but the buffer is full, the user agent must flag the WebSocket as full and then close the WebSocket connection.
196 // TODO : Any invocation of this method with a string argument that does not throw an exception must increase the bufferedAmount attribute by the number of bytes needed to express the argument as UTF-8.
197 }
198 return {};
199}
200
201// https://websockets.spec.whatwg.org/#feedback-from-the-protocol
202void WebSocket::on_open()
203{
204 // 1. Change the readyState attribute's value to OPEN (1).
205 // 2. Change the extensions attribute's value to the extensions in use, if it is not the null value. [WSP]
206 // 3. Change the protocol attribute's value to the subprotocol in use, if it is not the null value. [WSP]
207 dispatch_event(DOM::Event::create(realm(), HTML::EventNames::open).release_value_but_fixme_should_propagate_errors());
208}
209
210// https://websockets.spec.whatwg.org/#feedback-from-the-protocol
211void WebSocket::on_error()
212{
213 dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors());
214}
215
216// https://websockets.spec.whatwg.org/#feedback-from-the-protocol
217void WebSocket::on_close(u16 code, DeprecatedString reason, bool was_clean)
218{
219 // 1. Change the readyState attribute's value to CLOSED. This is handled by the Protocol's WebSocket
220 // 2. If [needed], fire an event named error at the WebSocket object. This is handled by the Protocol's WebSocket
221 HTML::CloseEventInit event_init {};
222 event_init.was_clean = was_clean;
223 event_init.code = code;
224 event_init.reason = String::from_deprecated_string(reason).release_value_but_fixme_should_propagate_errors();
225 dispatch_event(HTML::CloseEvent::create(realm(), String::from_deprecated_string(HTML::EventNames::close.view()).release_value_but_fixme_should_propagate_errors(), event_init).release_value_but_fixme_should_propagate_errors());
226}
227
228// https://websockets.spec.whatwg.org/#feedback-from-the-protocol
229void WebSocket::on_message(ByteBuffer message, bool is_text)
230{
231 if (m_websocket->ready_state() != WebSocket::ReadyState::Open)
232 return;
233 if (is_text) {
234 auto text_message = DeprecatedString(ReadonlyBytes(message));
235 HTML::MessageEventInit event_init;
236 event_init.data = JS::PrimitiveString::create(vm(), text_message);
237 event_init.origin = String::from_deprecated_string(url()).release_value_but_fixme_should_propagate_errors();
238 dispatch_event(HTML::MessageEvent::create(realm(), String::from_deprecated_string(HTML::EventNames::message).release_value_but_fixme_should_propagate_errors(), event_init).release_value_but_fixme_should_propagate_errors());
239 return;
240 }
241
242 if (m_binary_type == "blob") {
243 // type indicates that the data is Binary and binaryType is "blob"
244 TODO();
245 } else if (m_binary_type == "arraybuffer") {
246 // type indicates that the data is Binary and binaryType is "arraybuffer"
247 HTML::MessageEventInit event_init;
248 event_init.data = JS::ArrayBuffer::create(realm(), message);
249 event_init.origin = String::from_deprecated_string(url()).release_value_but_fixme_should_propagate_errors();
250 dispatch_event(HTML::MessageEvent::create(realm(), String::from_deprecated_string(HTML::EventNames::message).release_value_but_fixme_should_propagate_errors(), event_init).release_value_but_fixme_should_propagate_errors());
251 return;
252 }
253
254 dbgln("Unsupported WebSocket message type {}", m_binary_type);
255 TODO();
256}
257
258#undef __ENUMERATE
259#define __ENUMERATE(attribute_name, event_name) \
260 void WebSocket::set_##attribute_name(WebIDL::CallbackType* value) \
261 { \
262 set_event_handler_attribute(event_name, value); \
263 } \
264 WebIDL::CallbackType* WebSocket::attribute_name() \
265 { \
266 return event_handler_attribute(event_name); \
267 }
268ENUMERATE_WEBSOCKET_EVENT_HANDLERS(__ENUMERATE)
269#undef __ENUMERATE
270
271}