LibWeb: Move WebSocket constructor steps closer to spec

This commit is contained in:
Kenneth Myhra 2023-06-24 23:00:09 +02:00 committed by Andreas Kling
parent 110eeb8591
commit c445bd3a0c
Notes: sideshowbarker 2024-07-17 10:08:28 +09:00
2 changed files with 91 additions and 50 deletions

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2021-2022, Dex <dexes.ttp@gmail.com> * Copyright (c) 2021-2022, Dex <dexes.ttp@gmail.com>
* Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -50,50 +51,103 @@ WebSocketClientManager::WebSocketClientManager() = default;
// https://websockets.spec.whatwg.org/#dom-websocket-websocket // https://websockets.spec.whatwg.org/#dom-websocket-websocket
WebIDL::ExceptionOr<JS::NonnullGCPtr<WebSocket>> WebSocket::construct_impl(JS::Realm& realm, String const& url, Optional<Variant<String, Vector<String>>> const& protocols) WebIDL::ExceptionOr<JS::NonnullGCPtr<WebSocket>> WebSocket::construct_impl(JS::Realm& realm, String const& url, Optional<Variant<String, Vector<String>>> const& protocols)
{ {
auto& window = verify_cast<HTML::Window>(realm.global_object()); auto& vm = realm.vm();
AK::URL url_record(url);
auto web_socket = MUST_OR_THROW_OOM(realm.heap().allocate<WebSocket>(realm, realm));
auto& relevant_settings_object = HTML::relevant_settings_object(*web_socket);
// 1. Let baseURL be this's relevant settings object's API base URL.
auto base_url = relevant_settings_object.api_base_url();
// 2. Let urlRecord be the result of applying the URL parser to url with baseURL.
// FIXME: This should call an implementation of https://url.spec.whatwg.org/#concept-url-parser, currently it calls https://url.spec.whatwg.org/#concept-basic-url-parser
auto url_record = base_url.complete_url(url);
// 3. If urlRecord is failure, then throw a "SyntaxError" DOMException.
if (!url_record.is_valid()) if (!url_record.is_valid())
return WebIDL::SyntaxError::create(realm, "Invalid URL"); return WebIDL::SyntaxError::create(realm, "Invalid URL");
if (!url_record.scheme().is_one_of("ws", "wss"))
return WebIDL::SyntaxError::create(realm, "Invalid protocol"); // 4. If urlRecords scheme is "http", then set urlRecords scheme to "ws".
if (url_record.scheme() == "http"sv)
url_record.set_scheme("ws"sv);
// 5. Otherwise, if urlRecords scheme is "https", set urlRecords scheme to "wss".
else if (url_record.scheme() == "https"sv)
url_record.set_scheme("wss"sv);
// 6. If urlRecords scheme is not "ws" or "wss", then throw a "SyntaxError" DOMException.
if (!url_record.scheme().is_one_of("ws"sv, "wss"sv))
return WebIDL::SyntaxError::create(realm, "Invalid protocol"sv);
// 7. If urlRecords fragment is non-null, then throw a "SyntaxError" DOMException.
if (!url_record.fragment().is_empty()) if (!url_record.fragment().is_empty())
return WebIDL::SyntaxError::create(realm, "Presence of URL fragment is invalid"); return WebIDL::SyntaxError::create(realm, "Presence of URL fragment is invalid"sv);
Vector<String> protocols_sequence; Vector<String> protocols_sequence;
if (protocols.has_value()) { // 8. If protocols is a string, set protocols to a sequence consisting of just that string.
// 5. If `protocols` is a string, set `protocols` to a sequence consisting of just that string if (protocols.has_value() && protocols->has<String>())
if (protocols.value().has<String>()) protocols_sequence = { protocols.value().get<String>() };
protocols_sequence = { protocols.value().get<String>() }; else if (protocols.has_value() && protocols->has<Vector<String>>())
else protocols_sequence = protocols.value().get<Vector<String>>();
protocols_sequence = protocols.value().get<Vector<String>>(); else
// 6. If any of the values in `protocols` occur more than once or otherwise fail to match the requirements, throw SyntaxError protocols_sequence = {};
auto sorted_protocols = protocols_sequence;
quick_sort(sorted_protocols); // 9. If any of the values in protocols occur more than once or otherwise fail to match the requirements for elements that comprise
for (size_t i = 0; i < sorted_protocols.size(); i++) { // the value of `Sec-WebSocket-Protocol` fields as defined by The WebSocket protocol, then throw a "SyntaxError" DOMException. [WSP]
// https://datatracker.ietf.org/doc/html/rfc6455 auto sorted_protocols = protocols_sequence;
// The elements that comprise this value MUST be non-empty strings with characters in the range U+0021 to U+007E not including quick_sort(sorted_protocols);
// separator characters as defined in [RFC2616] and MUST all be unique strings. for (size_t i = 0; i < sorted_protocols.size(); i++) {
auto protocol = sorted_protocols[i]; // https://datatracker.ietf.org/doc/html/rfc6455
if (i < sorted_protocols.size() - 1 && protocol == sorted_protocols[i + 1]) // The elements that comprise this value MUST be non-empty strings with characters in the range U+0021 to U+007E not including
return WebIDL::SyntaxError::create(realm, "Found a duplicate protocol name in the specified list"); // separator characters as defined in [RFC2616] and MUST all be unique strings.
for (auto character : protocol.code_points()) { auto protocol = sorted_protocols[i];
if (character < '\x21' || character > '\x7E') if (i < sorted_protocols.size() - 1 && protocol == sorted_protocols[i + 1])
return WebIDL::SyntaxError::create(realm, "Found invalid character in subprotocol name"); return WebIDL::SyntaxError::create(realm, "Found a duplicate protocol name in the specified list"sv);
} for (auto code_point : protocol.code_points()) {
if (code_point < '\x21' || code_point > '\x7E')
return WebIDL::SyntaxError::create(realm, "Found invalid character in subprotocol name"sv);
} }
} }
return MUST_OR_THROW_OOM(realm.heap().allocate<WebSocket>(realm, window, url_record, protocols_sequence));
// 10. Set this's url to urlRecord.
web_socket->set_url(url_record);
// 11. Let client be thiss relevant settings object.
auto& client = relevant_settings_object;
// FIXME: 12. Run this step in parallel:
// 1. Establish a WebSocket connection given urlRecord, protocols, and client. [FETCH]
TRY_OR_THROW_OOM(vm, web_socket->establish_web_socket_connection(url_record, protocols_sequence, client));
return web_socket;
} }
WebSocket::WebSocket(HTML::Window& window, AK::URL& url, Vector<String> const& protocols) WebSocket::WebSocket(JS::Realm& realm)
: EventTarget(window.realm()) : EventTarget(realm)
, m_window(window) {
}
WebSocket::~WebSocket() = default;
JS::ThrowCompletionOr<void> WebSocket::initialize(JS::Realm& realm)
{
MUST_OR_THROW_OOM(Base::initialize(realm));
set_prototype(&Bindings::ensure_web_prototype<Bindings::WebSocketPrototype>(realm, "WebSocket"));
return {};
}
ErrorOr<void> WebSocket::establish_web_socket_connection(AK::URL& url_record, Vector<String>& protocols, HTML::EnvironmentSettingsObject& client)
{ {
// FIXME: Integrate properly with FETCH as per https://fetch.spec.whatwg.org/#websocket-opening-handshake // FIXME: Integrate properly with FETCH as per https://fetch.spec.whatwg.org/#websocket-opening-handshake
auto origin_string = m_window->associated_document().origin().serialize();
auto& window = verify_cast<HTML::Window>(client.global_object());
auto origin_string = window.associated_document().origin().serialize();
Vector<DeprecatedString> protcol_deprecated_strings; Vector<DeprecatedString> protcol_deprecated_strings;
for (auto protocol : protocols) for (auto const& protocol : protocols)
protcol_deprecated_strings.append(protocol.to_deprecated_string()); TRY(protcol_deprecated_strings.try_append(protocol.to_deprecated_string()));
m_websocket = WebSocketClientManager::the().connect(url, origin_string, protcol_deprecated_strings);
m_websocket = WebSocketClientManager::the().connect(url_record, origin_string, protcol_deprecated_strings);
m_websocket->on_open = [weak_this = make_weak_ptr<WebSocket>()] { m_websocket->on_open = [weak_this = make_weak_ptr<WebSocket>()] {
if (!weak_this) if (!weak_this)
return; return;
@ -118,24 +172,10 @@ WebSocket::WebSocket(HTML::Window& window, AK::URL& url, Vector<String> const& p
auto& websocket = const_cast<WebSocket&>(*weak_this); auto& websocket = const_cast<WebSocket&>(*weak_this);
websocket.on_error(); websocket.on_error();
}; };
}
WebSocket::~WebSocket() = default;
JS::ThrowCompletionOr<void> WebSocket::initialize(JS::Realm& realm)
{
MUST_OR_THROW_OOM(Base::initialize(realm));
set_prototype(&Bindings::ensure_web_prototype<Bindings::WebSocketPrototype>(realm, "WebSocket"));
return {}; return {};
} }
void WebSocket::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_window.ptr());
}
// https://websockets.spec.whatwg.org/#dom-websocket-readystate // https://websockets.spec.whatwg.org/#dom-websocket-readystate
WebSocket::ReadyState WebSocket::ready_state() const WebSocket::ReadyState WebSocket::ready_state() const
{ {

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2021-2022, Dex <dexes.ttp@gmail.com> * Copyright (c) 2021-2022, Dex <dexes.ttp@gmail.com>
* Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -42,6 +43,7 @@ public:
virtual ~WebSocket() override; virtual ~WebSocket() override;
WebIDL::ExceptionOr<String> url() const { return TRY_OR_THROW_OOM(vm(), m_url.to_string()); } WebIDL::ExceptionOr<String> url() const { return TRY_OR_THROW_OOM(vm(), m_url.to_string()); }
void set_url(AK::URL url) { m_url = move(url); }
#undef __ENUMERATE #undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \ #define __ENUMERATE(attribute_name, event_name) \
@ -66,12 +68,11 @@ private:
void on_error(); void on_error();
void on_close(u16 code, String reason, bool was_clean); void on_close(u16 code, String reason, bool was_clean);
WebSocket(HTML::Window&, AK::URL&, Vector<String> const& protocols); WebSocket(JS::Realm&);
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override; virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
JS::NonnullGCPtr<HTML::Window> m_window; ErrorOr<void> establish_web_socket_connection(AK::URL& url_record, Vector<String>& protocols, HTML::EnvironmentSettingsObject& client);
AK::URL m_url; AK::URL m_url;
String m_binary_type { "blob"_string.release_value_but_fixme_should_propagate_errors() }; String m_binary_type { "blob"_string.release_value_but_fixme_should_propagate_errors() };