Serenity Operating System
at master 271 lines 13 kB view raw
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}