mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 05:27:46 +00:00
LibWeb: Rename URL platform object to DOMURL
Along with putting functions in the URL namespace into a DOMURL namespace. This is done as LibWeb is in an awkward situation where it needs two URL classes. AK::URL is the general purpose URL class which is all that is needed in 95% of cases. URL in the Web namespace is needed predominantly for interfacing with the javascript interfaces. Because of two URLs in the same namespace, AK::URL has had to be used throughout LibWeb. If we move AK::URL into a URL namespace, this becomes more painful - where ::URL::URL is required to specify the constructor (and something like ::URL::create_with_url_or_path in other places). To fix this problem - rename the class in LibWeb implementing the URL IDL interface to DOMURL, along with moving the other Web URL related classes into this DOMURL folder. One could argue that this name also makes the situation a little more clear in LibWeb for why these two URL classes need be used in the first place.
This commit is contained in:
parent
1b6346ee1c
commit
f9e5b43b7a
30 changed files with 125 additions and 125 deletions
588
Userland/Libraries/LibWeb/DOMURL/DOMURL.cpp
Normal file
588
Userland/Libraries/LibWeb/DOMURL/DOMURL.cpp
Normal file
|
@ -0,0 +1,588 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021, the SerenityOS developers.
|
||||
* Copyright (c) 2023, networkException <networkexception@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/IPv4Address.h>
|
||||
#include <AK/IPv6Address.h>
|
||||
#include <AK/URLParser.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/DOMURL/DOMURL.h>
|
||||
#include <LibWeb/FileAPI/Blob.h>
|
||||
#include <LibWeb/FileAPI/BlobURLStore.h>
|
||||
|
||||
namespace Web::DOMURL {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(DOMURL);
|
||||
|
||||
JS::NonnullGCPtr<DOMURL> DOMURL::create(JS::Realm& realm, AK::URL url, JS::NonnullGCPtr<URLSearchParams> query)
|
||||
{
|
||||
return realm.heap().allocate<DOMURL>(realm, realm, move(url), move(query));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#api-url-parser
|
||||
static Optional<AK::URL> parse_api_url(String const& url, Optional<String> const& base)
|
||||
{
|
||||
// FIXME: We somewhat awkwardly have two failure states encapsulated in the return type (and convert between them in the steps),
|
||||
// ideally we'd get rid of URL's valid flag
|
||||
|
||||
// 1. Let parsedBase be null.
|
||||
Optional<AK::URL> parsed_base;
|
||||
|
||||
// 2. If base is non-null:
|
||||
if (base.has_value()) {
|
||||
// 1. Set parsedBase to the result of running the basic URL parser on base.
|
||||
auto parsed_base_url = URLParser::basic_parse(*base);
|
||||
|
||||
// 2. If parsedBase is failure, then return failure.
|
||||
if (!parsed_base_url.is_valid())
|
||||
return {};
|
||||
|
||||
parsed_base = parsed_base_url;
|
||||
}
|
||||
|
||||
// 3. Return the result of running the basic URL parser on url with parsedBase.
|
||||
auto parsed = URLParser::basic_parse(url, parsed_base);
|
||||
return parsed.is_valid() ? parsed : Optional<AK::URL> {};
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-url
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMURL>> DOMURL::construct_impl(JS::Realm& realm, String const& url, Optional<String> const& base)
|
||||
{
|
||||
// 1. Let parsedURL be the result of running the API URL parser on url with base, if given.
|
||||
auto parsed_url = parse_api_url(url, base);
|
||||
|
||||
// 2. If parsedURL is failure, then throw a TypeError.
|
||||
if (!parsed_url.has_value())
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid URL"sv };
|
||||
|
||||
// 3. Let query be parsedURL’s query, if that is non-null, and the empty string otherwise.
|
||||
auto query = parsed_url->query().value_or(String {});
|
||||
|
||||
// 4. Set this’s URL to parsedURL.
|
||||
// 5. Set this’s query object to a new URLSearchParams object.
|
||||
auto query_object = MUST(URLSearchParams::construct_impl(realm, query));
|
||||
|
||||
// 6. Initialize this’s query object with query.
|
||||
auto result_url = DOMURL::create(realm, parsed_url.release_value(), move(query_object));
|
||||
|
||||
// 7. Set this’s query object’s URL object to this.
|
||||
result_url->m_query->m_url = result_url;
|
||||
|
||||
return result_url;
|
||||
}
|
||||
|
||||
DOMURL::DOMURL(JS::Realm& realm, AK::URL url, JS::NonnullGCPtr<URLSearchParams> query)
|
||||
: PlatformObject(realm)
|
||||
, m_url(move(url))
|
||||
, m_query(move(query))
|
||||
{
|
||||
}
|
||||
|
||||
DOMURL::~DOMURL() = default;
|
||||
|
||||
void DOMURL::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
set_prototype(&Bindings::ensure_web_prototype<Bindings::DOMURLPrototype>(realm, "URL"_fly_string));
|
||||
}
|
||||
|
||||
void DOMURL::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_query);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/FileAPI/#dfn-createObjectURL
|
||||
WebIDL::ExceptionOr<String> DOMURL::create_object_url(JS::VM& vm, JS::NonnullGCPtr<FileAPI::Blob> object)
|
||||
{
|
||||
// The createObjectURL(obj) static method must return the result of adding an entry to the blob URL store for obj.
|
||||
return TRY_OR_THROW_OOM(vm, FileAPI::add_entry_to_blob_url_store(object));
|
||||
}
|
||||
|
||||
// https://w3c.github.io/FileAPI/#dfn-revokeObjectURL
|
||||
WebIDL::ExceptionOr<void> DOMURL::revoke_object_url(JS::VM& vm, StringView url)
|
||||
{
|
||||
// 1. Let url record be the result of parsing url.
|
||||
auto url_record = parse(url);
|
||||
|
||||
// 2. If url record’s scheme is not "blob", return.
|
||||
if (url_record.scheme() != "blob"sv)
|
||||
return {};
|
||||
|
||||
// 3. Let origin be the origin of url record.
|
||||
auto origin = url_origin(url_record);
|
||||
|
||||
// 4. Let settings be the current settings object.
|
||||
auto& settings = HTML::current_settings_object();
|
||||
|
||||
// 5. If origin is not same origin with settings’s origin, return.
|
||||
if (!origin.is_same_origin(settings.origin()))
|
||||
return {};
|
||||
|
||||
// 6. Remove an entry from the Blob URL Store for url.
|
||||
TRY_OR_THROW_OOM(vm, FileAPI::remove_entry_from_blob_url_store(url));
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-canparse
|
||||
bool DOMURL::can_parse(JS::VM&, String const& url, Optional<String> const& base)
|
||||
{
|
||||
// 1. Let parsedURL be the result of running the API URL parser on url with base, if given.
|
||||
auto parsed_url = parse_api_url(url, base);
|
||||
|
||||
// 2. If parsedURL is failure, then return false.
|
||||
if (!parsed_url.has_value())
|
||||
return false;
|
||||
|
||||
// 3. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-href
|
||||
WebIDL::ExceptionOr<String> DOMURL::href() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// The href getter steps and the toJSON() method steps are to return the serialization of this’s URL.
|
||||
return TRY_OR_THROW_OOM(vm, String::from_byte_string(m_url.serialize()));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-tojson
|
||||
WebIDL::ExceptionOr<String> DOMURL::to_json() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// The href getter steps and the toJSON() method steps are to return the serialization of this’s URL.
|
||||
return TRY_OR_THROW_OOM(vm, String::from_byte_string(m_url.serialize()));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#ref-for-dom-url-href②
|
||||
WebIDL::ExceptionOr<void> DOMURL::set_href(String const& href)
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// 1. Let parsedURL be the result of running the basic URL parser on the given value.
|
||||
AK::URL parsed_url = href;
|
||||
|
||||
// 2. If parsedURL is failure, then throw a TypeError.
|
||||
if (!parsed_url.is_valid())
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid URL"sv };
|
||||
|
||||
// 3. Set this’s URL to parsedURL.
|
||||
m_url = move(parsed_url);
|
||||
|
||||
// 4. Empty this’s query object’s list.
|
||||
m_query->m_list.clear();
|
||||
|
||||
// 5. Let query be this’s URL’s query.
|
||||
auto query = m_url.query();
|
||||
|
||||
// 6. If query is non-null, then set this’s query object’s list to the result of parsing query.
|
||||
if (query.has_value())
|
||||
m_query->m_list = TRY_OR_THROW_OOM(vm, url_decode(*query));
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-origin
|
||||
WebIDL::ExceptionOr<String> DOMURL::origin() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// The origin getter steps are to return the serialization of this’s URL’s origin. [HTML]
|
||||
return TRY_OR_THROW_OOM(vm, String::from_byte_string(m_url.serialize_origin()));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-protocol
|
||||
WebIDL::ExceptionOr<String> DOMURL::protocol() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// The protocol getter steps are to return this’s URL’s scheme, followed by U+003A (:).
|
||||
return TRY_OR_THROW_OOM(vm, String::formatted("{}:", m_url.scheme()));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#ref-for-dom-url-protocol%E2%91%A0
|
||||
WebIDL::ExceptionOr<void> DOMURL::set_protocol(String const& protocol)
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// The protocol setter steps are to basic URL parse the given value, followed by U+003A (:), with this’s URL as
|
||||
// url and scheme start state as state override.
|
||||
auto result_url = URLParser::basic_parse(TRY_OR_THROW_OOM(vm, String::formatted("{}:", protocol)), {}, m_url, URLParser::State::SchemeStart);
|
||||
if (result_url.is_valid())
|
||||
m_url = move(result_url);
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-username
|
||||
WebIDL::ExceptionOr<String> DOMURL::username() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// The username getter steps are to return this’s URL’s username.
|
||||
return TRY_OR_THROW_OOM(vm, m_url.username());
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#ref-for-dom-url-username%E2%91%A0
|
||||
void DOMURL::set_username(String const& username)
|
||||
{
|
||||
// 1. If this’s URL cannot have a username/password/port, then return.
|
||||
if (m_url.cannot_have_a_username_or_password_or_port())
|
||||
return;
|
||||
|
||||
// 2. Set the username given this’s URL and the given value.
|
||||
MUST(m_url.set_username(username));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-password
|
||||
WebIDL::ExceptionOr<String> DOMURL::password() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// The password getter steps are to return this’s URL’s password.
|
||||
return TRY_OR_THROW_OOM(vm, m_url.password());
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#ref-for-dom-url-password%E2%91%A0
|
||||
void DOMURL::set_password(String const& password)
|
||||
{
|
||||
// 1. If this’s URL cannot have a username/password/port, then return.
|
||||
if (m_url.cannot_have_a_username_or_password_or_port())
|
||||
return;
|
||||
|
||||
// 2. Set the password given this’s URL and the given value.
|
||||
MUST(m_url.set_password(password));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-host
|
||||
WebIDL::ExceptionOr<String> DOMURL::host() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// 1. Let url be this’s URL.
|
||||
auto& url = m_url;
|
||||
|
||||
// 2. If url’s host is null, then return the empty string.
|
||||
if (url.host().has<Empty>())
|
||||
return String {};
|
||||
|
||||
// 3. If url’s port is null, return url’s host, serialized.
|
||||
if (!url.port().has_value())
|
||||
return TRY_OR_THROW_OOM(vm, url.serialized_host());
|
||||
|
||||
// 4. Return url’s host, serialized, followed by U+003A (:) and url’s port, serialized.
|
||||
return TRY_OR_THROW_OOM(vm, String::formatted("{}:{}", TRY_OR_THROW_OOM(vm, url.serialized_host()), *url.port()));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-hostref-for-dom-url-host%E2%91%A0
|
||||
void DOMURL::set_host(String const& host)
|
||||
{
|
||||
// 1. If this’s URL’s cannot-be-a-base-URL is true, then return.
|
||||
if (m_url.cannot_be_a_base_url())
|
||||
return;
|
||||
|
||||
// 2. Basic URL parse the given value with this’s URL as url and host state as state override.
|
||||
auto result_url = URLParser::basic_parse(host, {}, m_url, URLParser::State::Host);
|
||||
if (result_url.is_valid())
|
||||
m_url = move(result_url);
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-hostname
|
||||
WebIDL::ExceptionOr<String> DOMURL::hostname() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// 1. If this’s URL’s host is null, then return the empty string.
|
||||
if (m_url.host().has<Empty>())
|
||||
return String {};
|
||||
|
||||
// 2. Return this’s URL’s host, serialized.
|
||||
return TRY_OR_THROW_OOM(vm, m_url.serialized_host());
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#ref-for-dom-url-hostname①
|
||||
void DOMURL::set_hostname(String const& hostname)
|
||||
{
|
||||
// 1. If this’s URL’s cannot-be-a-base-URL is true, then return.
|
||||
if (m_url.cannot_be_a_base_url())
|
||||
return;
|
||||
|
||||
// 2. Basic URL parse the given value with this’s URL as url and hostname state as state override.
|
||||
auto result_url = URLParser::basic_parse(hostname, {}, m_url, URLParser::State::Hostname);
|
||||
if (result_url.is_valid())
|
||||
m_url = move(result_url);
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-port
|
||||
WebIDL::ExceptionOr<String> DOMURL::port() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// 1. If this’s URL’s port is null, then return the empty string.
|
||||
if (!m_url.port().has_value())
|
||||
return String {};
|
||||
|
||||
// 2. Return this’s URL’s port, serialized.
|
||||
return TRY_OR_THROW_OOM(vm, String::formatted("{}", *m_url.port()));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#ref-for-dom-url-port%E2%91%A0
|
||||
void DOMURL::set_port(String const& port)
|
||||
{
|
||||
// 1. If this’s URL cannot have a username/password/port, then return.
|
||||
if (m_url.cannot_have_a_username_or_password_or_port())
|
||||
return;
|
||||
|
||||
// 2. If the given value is the empty string, then set this’s URL’s port to null.
|
||||
if (port.is_empty()) {
|
||||
m_url.set_port({});
|
||||
}
|
||||
// 3. Otherwise, basic URL parse the given value with this’s URL as url and port state as state override.
|
||||
else {
|
||||
auto result_url = URLParser::basic_parse(port, {}, m_url, URLParser::State::Port);
|
||||
if (result_url.is_valid())
|
||||
m_url = move(result_url);
|
||||
}
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-pathname
|
||||
WebIDL::ExceptionOr<String> DOMURL::pathname() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// The pathname getter steps are to return the result of URL path serializing this’s URL.
|
||||
return TRY_OR_THROW_OOM(vm, String::from_byte_string(m_url.serialize_path(AK::URL::ApplyPercentDecoding::No)));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#ref-for-dom-url-pathname%E2%91%A0
|
||||
void DOMURL::set_pathname(String const& pathname)
|
||||
{
|
||||
// FIXME: These steps no longer match the speci.
|
||||
// 1. If this’s URL’s cannot-be-a-base-URL is true, then return.
|
||||
if (m_url.cannot_be_a_base_url())
|
||||
return;
|
||||
|
||||
// 2. Empty this’s URL’s path.
|
||||
auto url = m_url; // We copy the URL here to follow other browser's behavior of reverting the path change if the parse failed.
|
||||
url.set_paths({});
|
||||
|
||||
// 3. Basic URL parse the given value with this’s URL as url and path start state as state override.
|
||||
auto result_url = URLParser::basic_parse(pathname, {}, move(url), URLParser::State::PathStart);
|
||||
if (result_url.is_valid())
|
||||
m_url = move(result_url);
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-search
|
||||
WebIDL::ExceptionOr<String> DOMURL::search() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// 1. If this’s URL’s query is either null or the empty string, then return the empty string.
|
||||
if (!m_url.query().has_value() || m_url.query()->is_empty())
|
||||
return String {};
|
||||
|
||||
// 2. Return U+003F (?), followed by this’s URL’s query.
|
||||
return TRY_OR_THROW_OOM(vm, String::formatted("?{}", *m_url.query()));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#ref-for-dom-url-search%E2%91%A0
|
||||
WebIDL::ExceptionOr<void> DOMURL::set_search(String const& search)
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// 1. Let url be this’s URL.
|
||||
auto& url = m_url;
|
||||
|
||||
// 2. If the given value is the empty string:
|
||||
if (search.is_empty()) {
|
||||
// 1. Set url’s query to null.
|
||||
url.set_query({});
|
||||
|
||||
// 2. Empty this’s query object’s list.
|
||||
m_query->m_list.clear();
|
||||
|
||||
// 3. Potentially strip trailing spaces from an opaque path with this.
|
||||
strip_trailing_spaces_from_an_opaque_path(*this);
|
||||
|
||||
// 4. Return.
|
||||
return {};
|
||||
}
|
||||
|
||||
// 3. Let input be the given value with a single leading U+003F (?) removed, if any.
|
||||
auto search_as_string_view = search.bytes_as_string_view();
|
||||
auto input = search_as_string_view.substring_view(search_as_string_view.starts_with('?'));
|
||||
|
||||
// 4. Set url’s query to the empty string.
|
||||
auto url_copy = url; // We copy the URL here to follow other browser's behavior of reverting the search change if the parse failed.
|
||||
url_copy.set_query(String {});
|
||||
|
||||
// 5. Basic URL parse input with url as url and query state as state override.
|
||||
auto result_url = URLParser::basic_parse(input, {}, move(url_copy), URLParser::State::Query);
|
||||
if (result_url.is_valid()) {
|
||||
m_url = move(result_url);
|
||||
|
||||
// 6. Set this’s query object’s list to the result of parsing input.
|
||||
m_query->m_list = TRY_OR_THROW_OOM(vm, url_decode(input));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-searchparams
|
||||
JS::NonnullGCPtr<URLSearchParams const> DOMURL::search_params() const
|
||||
{
|
||||
// The searchParams getter steps are to return this’s query object.
|
||||
return m_query;
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-url-hash
|
||||
WebIDL::ExceptionOr<String> DOMURL::hash() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// 1. If this’s URL’s fragment is either null or the empty string, then return the empty string.
|
||||
if (!m_url.fragment().has_value() || m_url.fragment()->is_empty())
|
||||
return String {};
|
||||
|
||||
// 2. Return U+0023 (#), followed by this’s URL’s fragment.
|
||||
return TRY_OR_THROW_OOM(vm, String::formatted("#{}", m_url.fragment()));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#ref-for-dom-url-hash%E2%91%A0
|
||||
void DOMURL::set_hash(String const& hash)
|
||||
{
|
||||
// 1. If the given value is the empty string:
|
||||
if (hash.is_empty()) {
|
||||
// 1. Set this’s URL’s fragment to null.
|
||||
m_url.set_fragment({});
|
||||
|
||||
// 2. Potentially strip trailing spaces from an opaque path with this.
|
||||
strip_trailing_spaces_from_an_opaque_path(*this);
|
||||
|
||||
// 3. Return.
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Let input be the given value with a single leading U+0023 (#) removed, if any.
|
||||
auto hash_as_string_view = hash.bytes_as_string_view();
|
||||
auto input = hash_as_string_view.substring_view(hash_as_string_view.starts_with('#'));
|
||||
|
||||
// 3. Set this’s URL’s fragment to the empty string.
|
||||
auto url = m_url; // We copy the URL here to follow other browser's behavior of reverting the hash change if the parse failed.
|
||||
url.set_fragment(String {});
|
||||
|
||||
// 4. Basic URL parse input with this’s URL as url and fragment state as state override.
|
||||
auto result_url = URLParser::basic_parse(input, {}, move(url), URLParser::State::Fragment);
|
||||
if (result_url.is_valid())
|
||||
m_url = move(result_url);
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#concept-url-origin
|
||||
HTML::Origin url_origin(AK::URL const& url)
|
||||
{
|
||||
// FIXME: We should probably have an extended version of AK::URL for LibWeb instead of standalone functions like this.
|
||||
|
||||
// The origin of a URL url is the origin returned by running these steps, switching on url’s scheme:
|
||||
// -> "blob"
|
||||
if (url.scheme() == "blob"sv) {
|
||||
auto url_string = url.to_string().release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
// 1. If url’s blob URL entry is non-null, then return url’s blob URL entry’s environment’s origin.
|
||||
if (auto blob_url_entry = FileAPI::blob_url_store().get(url_string); blob_url_entry.has_value())
|
||||
return blob_url_entry->environment->origin();
|
||||
|
||||
// 2. Let pathURL be the result of parsing the result of URL path serializing url.
|
||||
auto path_url = parse(url.serialize_path());
|
||||
|
||||
// 3. If pathURL is failure, then return a new opaque origin.
|
||||
if (!path_url.is_valid())
|
||||
return HTML::Origin {};
|
||||
|
||||
// 4. If pathURL’s scheme is "http", "https", or "file", then return pathURL’s origin.
|
||||
if (path_url.scheme().is_one_of("http"sv, "https"sv, "file"sv))
|
||||
return url_origin(path_url);
|
||||
|
||||
// 5. Return a new opaque origin.
|
||||
return HTML::Origin {};
|
||||
}
|
||||
|
||||
// -> "ftp"
|
||||
// -> "http"
|
||||
// -> "https"
|
||||
// -> "ws"
|
||||
// -> "wss"
|
||||
if (url.scheme().is_one_of("ftp"sv, "http"sv, "https"sv, "ws"sv, "wss"sv)) {
|
||||
// Return the tuple origin (url’s scheme, url’s host, url’s port, null).
|
||||
return HTML::Origin(url.scheme().to_byte_string(), url.host(), url.port().value_or(0));
|
||||
}
|
||||
|
||||
// -> "file"
|
||||
if (url.scheme() == "file"sv) {
|
||||
// Unfortunate as it is, this is left as an exercise to the reader. When in doubt, return a new opaque origin.
|
||||
// Note: We must return an origin with the `file://' protocol for `file://' iframes to work from `file://' pages.
|
||||
return HTML::Origin(url.scheme().to_byte_string(), String {}, 0);
|
||||
}
|
||||
|
||||
// -> Otherwise
|
||||
// Return a new opaque origin.
|
||||
return HTML::Origin {};
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#concept-domain
|
||||
bool host_is_domain(AK::URL::Host const& host)
|
||||
{
|
||||
// A domain is a non-empty ASCII string that identifies a realm within a network.
|
||||
return host.has<String>() && host.get<String>() != String {};
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#potentially-strip-trailing-spaces-from-an-opaque-path
|
||||
void strip_trailing_spaces_from_an_opaque_path(DOMURL& url)
|
||||
{
|
||||
// 1. If url’s URL does not have an opaque path, then return.
|
||||
// FIXME: Reimplement this step once we modernize the URL implementation to meet the spec.
|
||||
if (!url.cannot_be_a_base_url())
|
||||
return;
|
||||
|
||||
// 2. If url’s URL’s fragment is non-null, then return.
|
||||
if (url.fragment().has_value())
|
||||
return;
|
||||
|
||||
// 3. If url’s URL’s query is non-null, then return.
|
||||
if (url.query().has_value())
|
||||
return;
|
||||
|
||||
// 4. Remove all trailing U+0020 SPACE code points from url’s URL’s path.
|
||||
// NOTE: At index 0 since the first step tells us that the URL only has one path segment.
|
||||
auto opaque_path = url.path_segment_at_index(0);
|
||||
auto trimmed_path = opaque_path.trim(" "sv, TrimMode::Right);
|
||||
url.set_paths({ trimmed_path });
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#concept-url-parser
|
||||
AK::URL parse(StringView input, Optional<AK::URL> const& base_url)
|
||||
{
|
||||
// FIXME: We should probably have an extended version of AK::URL for LibWeb instead of standalone functions like this.
|
||||
|
||||
// 1. Let url be the result of running the basic URL parser on input with base and encoding.
|
||||
auto url = URLParser::basic_parse(input, base_url);
|
||||
|
||||
// 2. If url is failure, return failure.
|
||||
if (!url.is_valid())
|
||||
return {};
|
||||
|
||||
// 3. If url’s scheme is not "blob",
|
||||
if (url.scheme() != "blob")
|
||||
return url;
|
||||
|
||||
// FIXME: 4. Set url’s blob URL entry to the result of resolving the blob URL url,
|
||||
// FIXME: 5. if that did not return failure, and null otherwise.
|
||||
|
||||
// 6. Return url
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
100
Userland/Libraries/LibWeb/DOMURL/DOMURL.h
Normal file
100
Userland/Libraries/LibWeb/DOMURL/DOMURL.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021, the SerenityOS developers.
|
||||
* Copyright (c) 2023, networkException <networkexception@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/URL.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/DOMURL/URLSearchParams.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::DOMURL {
|
||||
|
||||
class DOMURL : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(DOMURL, Bindings::PlatformObject);
|
||||
JS_DECLARE_ALLOCATOR(DOMURL);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static JS::NonnullGCPtr<DOMURL> create(JS::Realm&, URL, JS::NonnullGCPtr<URLSearchParams> query);
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMURL>> construct_impl(JS::Realm&, String const& url, Optional<String> const& base = {});
|
||||
|
||||
virtual ~DOMURL() override;
|
||||
|
||||
static WebIDL::ExceptionOr<String> create_object_url(JS::VM&, JS::NonnullGCPtr<FileAPI::Blob> object);
|
||||
static WebIDL::ExceptionOr<void> revoke_object_url(JS::VM&, StringView url);
|
||||
|
||||
static bool can_parse(JS::VM&, String const& url, Optional<String> const& base = {});
|
||||
|
||||
WebIDL::ExceptionOr<String> href() const;
|
||||
WebIDL::ExceptionOr<void> set_href(String const&);
|
||||
|
||||
WebIDL::ExceptionOr<String> origin() const;
|
||||
|
||||
WebIDL::ExceptionOr<String> protocol() const;
|
||||
WebIDL::ExceptionOr<void> set_protocol(String const&);
|
||||
|
||||
WebIDL::ExceptionOr<String> username() const;
|
||||
void set_username(String const&);
|
||||
|
||||
WebIDL::ExceptionOr<String> password() const;
|
||||
void set_password(String const&);
|
||||
|
||||
WebIDL::ExceptionOr<String> host() const;
|
||||
void set_host(String const&);
|
||||
|
||||
WebIDL::ExceptionOr<String> hostname() const;
|
||||
void set_hostname(String const&);
|
||||
|
||||
WebIDL::ExceptionOr<String> port() const;
|
||||
void set_port(String const&);
|
||||
|
||||
WebIDL::ExceptionOr<String> pathname() const;
|
||||
void set_pathname(String const&);
|
||||
|
||||
Optional<String> const& fragment() const { return m_url.fragment(); }
|
||||
|
||||
ByteString path_segment_at_index(size_t index) const { return m_url.path_segment_at_index(index); }
|
||||
|
||||
void set_paths(Vector<ByteString> const& paths) { return m_url.set_paths(paths); }
|
||||
|
||||
// FIXME: Reimplement this to meet the definition in https://url.spec.whatwg.org/#url-opaque-path once we modernize URL to meet the spec.
|
||||
bool cannot_be_a_base_url() const { return m_url.cannot_be_a_base_url(); }
|
||||
|
||||
WebIDL::ExceptionOr<String> search() const;
|
||||
WebIDL::ExceptionOr<void> set_search(String const&);
|
||||
|
||||
JS::NonnullGCPtr<URLSearchParams const> search_params() const;
|
||||
|
||||
WebIDL::ExceptionOr<String> hash() const;
|
||||
void set_hash(String const&);
|
||||
|
||||
WebIDL::ExceptionOr<String> to_json() const;
|
||||
|
||||
Optional<String> const& query() const { return m_url.query(); }
|
||||
void set_query(Badge<URLSearchParams>, Optional<String> query) { m_url.set_query(move(query)); }
|
||||
|
||||
private:
|
||||
DOMURL(JS::Realm&, AK::URL, JS::NonnullGCPtr<URLSearchParams> query);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
AK::URL m_url;
|
||||
JS::NonnullGCPtr<URLSearchParams> m_query;
|
||||
};
|
||||
|
||||
HTML::Origin url_origin(AK::URL const&);
|
||||
bool host_is_domain(AK::URL::Host const&);
|
||||
|
||||
// https://url.spec.whatwg.org/#potentially-strip-trailing-spaces-from-an-opaque-path
|
||||
void strip_trailing_spaces_from_an_opaque_path(DOMURL& url);
|
||||
|
||||
// https://url.spec.whatwg.org/#concept-url-parser
|
||||
AK::URL parse(StringView input, Optional<AK::URL> const& base_url = {});
|
||||
|
||||
}
|
28
Userland/Libraries/LibWeb/DOMURL/DOMURL.idl
Normal file
28
Userland/Libraries/LibWeb/DOMURL/DOMURL.idl
Normal file
|
@ -0,0 +1,28 @@
|
|||
#import <FileAPI/Blob.idl>
|
||||
#import <DOMURL/URLSearchParams.idl>
|
||||
|
||||
// https://url.spec.whatwg.org/#url
|
||||
[Exposed=*, LegacyWindowAlias=webkitURL, ImplementedAs=DOMURL]
|
||||
interface URL {
|
||||
constructor(USVString url, optional USVString base);
|
||||
|
||||
static boolean canParse(USVString url, optional USVString base);
|
||||
|
||||
stringifier attribute USVString href;
|
||||
readonly attribute USVString origin;
|
||||
attribute USVString protocol;
|
||||
attribute USVString username;
|
||||
attribute USVString password;
|
||||
attribute USVString host;
|
||||
attribute USVString hostname;
|
||||
attribute USVString port;
|
||||
attribute USVString pathname;
|
||||
attribute USVString search;
|
||||
[SameObject] readonly attribute URLSearchParams searchParams;
|
||||
attribute USVString hash;
|
||||
|
||||
USVString toJSON();
|
||||
|
||||
static DOMString createObjectURL(Blob obj); // FIXME: Should be (Blob or MediaSource).
|
||||
static undefined revokeObjectURL(DOMString url);
|
||||
};
|
334
Userland/Libraries/LibWeb/DOMURL/URLSearchParams.cpp
Normal file
334
Userland/Libraries/LibWeb/DOMURL/URLSearchParams.cpp
Normal file
|
@ -0,0 +1,334 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/QuickSort.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/URLParser.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibTextCodec/Decoder.h>
|
||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/DOMURL/DOMURL.h>
|
||||
#include <LibWeb/DOMURL/URLSearchParams.h>
|
||||
|
||||
namespace Web::DOMURL {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(URLSearchParams);
|
||||
|
||||
URLSearchParams::URLSearchParams(JS::Realm& realm, Vector<QueryParam> list)
|
||||
: PlatformObject(realm)
|
||||
, m_list(move(list))
|
||||
{
|
||||
}
|
||||
|
||||
URLSearchParams::~URLSearchParams() = default;
|
||||
|
||||
void URLSearchParams::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
set_prototype(&Bindings::ensure_web_prototype<Bindings::URLSearchParamsPrototype>(realm, "URLSearchParams"_fly_string));
|
||||
}
|
||||
|
||||
void URLSearchParams::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_url);
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#concept-urlencoded-serializer
|
||||
// The application/x-www-form-urlencoded serializer takes a list of name-value tuples tuples, with an optional encoding encoding (default UTF-8), and then runs these steps. They return an ASCII string.
|
||||
ErrorOr<String> url_encode(Vector<QueryParam> const& tuples, StringView encoding)
|
||||
{
|
||||
// 1. Set encoding to the result of getting an output encoding from encoding.
|
||||
encoding = TextCodec::get_output_encoding(encoding);
|
||||
|
||||
// 2. Let output be the empty string.
|
||||
StringBuilder output;
|
||||
|
||||
// 3. For each tuple of tuples:
|
||||
for (auto const& tuple : tuples) {
|
||||
// 1. Assert: tuple’s name and tuple’s value are scalar value strings.
|
||||
|
||||
// 2. Let name be the result of running percent-encode after encoding with encoding, tuple’s name, the application/x-www-form-urlencoded percent-encode set, and true.
|
||||
// FIXME: URLParser does not currently implement encoding.
|
||||
auto name = TRY(URLParser::percent_encode_after_encoding(tuple.name, AK::URL::PercentEncodeSet::ApplicationXWWWFormUrlencoded, true));
|
||||
|
||||
// 3. Let value be the result of running percent-encode after encoding with encoding, tuple’s value, the application/x-www-form-urlencoded percent-encode set, and true.
|
||||
// FIXME: URLParser does not currently implement encoding.
|
||||
auto value = TRY(URLParser::percent_encode_after_encoding(tuple.value, AK::URL::PercentEncodeSet::ApplicationXWWWFormUrlencoded, true));
|
||||
|
||||
// 4. If output is not the empty string, then append U+0026 (&) to output.
|
||||
if (!output.is_empty())
|
||||
TRY(output.try_append('&'));
|
||||
|
||||
// 5. Append name, followed by U+003D (=), followed by value, to output.
|
||||
TRY(output.try_append(name));
|
||||
TRY(output.try_append('='));
|
||||
TRY(output.try_append(value));
|
||||
}
|
||||
|
||||
// 4. Return output.
|
||||
return output.to_string();
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#concept-urlencoded-parser
|
||||
// The application/x-www-form-urlencoded parser takes a byte sequence input, and then runs these steps:
|
||||
ErrorOr<Vector<QueryParam>> url_decode(StringView input)
|
||||
{
|
||||
// 1. Let sequences be the result of splitting input on 0x26 (&).
|
||||
auto sequences = input.split_view('&');
|
||||
|
||||
// 2. Let output be an initially empty list of name-value tuples where both name and value hold a string.
|
||||
Vector<QueryParam> output;
|
||||
|
||||
// 3. For each byte sequence bytes in sequences:
|
||||
for (auto bytes : sequences) {
|
||||
// 1. If bytes is the empty byte sequence, then continue.
|
||||
if (bytes.is_empty())
|
||||
continue;
|
||||
|
||||
StringView name;
|
||||
StringView value;
|
||||
|
||||
// 2. If bytes contains a 0x3D (=), then let name be the bytes from the start of bytes up to but excluding its first 0x3D (=), and let value be the bytes, if any, after the first 0x3D (=) up to the end of bytes. If 0x3D (=) is the first byte, then name will be the empty byte sequence. If it is the last, then value will be the empty byte sequence.
|
||||
if (auto index = bytes.find('='); index.has_value()) {
|
||||
name = bytes.substring_view(0, *index);
|
||||
value = bytes.substring_view(*index + 1);
|
||||
}
|
||||
// 3. Otherwise, let name have the value of bytes and let value be the empty byte sequence.
|
||||
else {
|
||||
name = bytes;
|
||||
value = ""sv;
|
||||
}
|
||||
|
||||
// 4. Replace any 0x2B (+) in name and value with 0x20 (SP).
|
||||
auto space_decoded_name = name.replace("+"sv, " "sv, ReplaceMode::All);
|
||||
|
||||
// 5. Let nameString and valueString be the result of running UTF-8 decode without BOM on the percent-decoding of name and value, respectively.
|
||||
auto name_string = TRY(String::from_byte_string(AK::URL::percent_decode(space_decoded_name)));
|
||||
auto value_string = TRY(String::from_byte_string(AK::URL::percent_decode(value)));
|
||||
|
||||
TRY(output.try_empend(move(name_string), move(value_string)));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<URLSearchParams>> URLSearchParams::create(JS::Realm& realm, Vector<QueryParam> list)
|
||||
{
|
||||
return realm.heap().allocate<URLSearchParams>(realm, realm, move(list));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
|
||||
// https://url.spec.whatwg.org/#urlsearchparams-initialize
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<URLSearchParams>> URLSearchParams::construct_impl(JS::Realm& realm, Variant<Vector<Vector<String>>, OrderedHashMap<String, String>, String> const& init)
|
||||
{
|
||||
auto& vm = realm.vm();
|
||||
|
||||
// 1. If init is a string and starts with U+003F (?), then remove the first code point from init.
|
||||
// NOTE: We do this when we know that it's a string on step 3 of initialization.
|
||||
|
||||
// 2. Initialize this with init.
|
||||
|
||||
// URLSearchParams init from this point forward
|
||||
|
||||
// 1. If init is a sequence, then for each pair in init:
|
||||
if (init.has<Vector<Vector<String>>>()) {
|
||||
auto const& init_sequence = init.get<Vector<Vector<String>>>();
|
||||
|
||||
Vector<QueryParam> list;
|
||||
list.ensure_capacity(init_sequence.size());
|
||||
|
||||
for (auto const& pair : init_sequence) {
|
||||
// a. If pair does not contain exactly two items, then throw a TypeError.
|
||||
if (pair.size() != 2)
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, TRY_OR_THROW_OOM(vm, String::formatted("Expected only 2 items in pair, got {}", pair.size())) };
|
||||
|
||||
// b. Append a new name-value pair whose name is pair’s first item, and value is pair’s second item, to query’s list.
|
||||
list.append(QueryParam { .name = pair[0], .value = pair[1] });
|
||||
}
|
||||
|
||||
return URLSearchParams::create(realm, move(list));
|
||||
}
|
||||
|
||||
// 2. Otherwise, if init is a record, then for each name → value of init, append a new name-value pair whose name is name and value is value, to query’s list.
|
||||
if (init.has<OrderedHashMap<String, String>>()) {
|
||||
auto const& init_record = init.get<OrderedHashMap<String, String>>();
|
||||
|
||||
Vector<QueryParam> list;
|
||||
list.ensure_capacity(init_record.size());
|
||||
|
||||
for (auto const& pair : init_record)
|
||||
list.append(QueryParam { .name = pair.key, .value = pair.value });
|
||||
|
||||
return URLSearchParams::create(realm, move(list));
|
||||
}
|
||||
|
||||
// 3. Otherwise:
|
||||
// a. Assert: init is a string.
|
||||
// NOTE: `get` performs `VERIFY(has<T>())`
|
||||
auto const& init_string = init.get<String>();
|
||||
|
||||
// See NOTE at the start of this function.
|
||||
auto init_string_view = init_string.bytes_as_string_view();
|
||||
auto stripped_init = init_string_view.substring_view(init_string_view.starts_with('?'));
|
||||
|
||||
// b. Set query’s list to the result of parsing init.
|
||||
return URLSearchParams::create(realm, TRY_OR_THROW_OOM(vm, url_decode(stripped_init)));
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-urlsearchparams-size
|
||||
size_t URLSearchParams::size() const
|
||||
{
|
||||
// The size getter steps are to return this’s list’s size.
|
||||
return m_list.size();
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> URLSearchParams::append(String const& name, String const& value)
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// 1. Append a new name-value pair whose name is name and value is value, to list.
|
||||
TRY_OR_THROW_OOM(vm, m_list.try_empend(name, value));
|
||||
// 2. Update this.
|
||||
TRY(update());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> URLSearchParams::update()
|
||||
{
|
||||
// 1. If query’s URL object is null, then return.
|
||||
if (!m_url)
|
||||
return {};
|
||||
// 2. Let serializedQuery be the serialization of query’s list.
|
||||
auto serialized_query = TRY(to_string());
|
||||
// 3. If serializedQuery is the empty string, then set serializedQuery to null.
|
||||
if (serialized_query.is_empty())
|
||||
serialized_query = {};
|
||||
// 4. Set query’s URL object’s URL’s query to serializedQuery.
|
||||
m_url->set_query({}, move(serialized_query));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> URLSearchParams::delete_(String const& name)
|
||||
{
|
||||
// 1. Remove all name-value pairs whose name is name from list.
|
||||
m_list.remove_all_matching([&name](auto& entry) {
|
||||
return entry.name == name;
|
||||
});
|
||||
// 2. Update this.
|
||||
TRY(update());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<String> URLSearchParams::get(String const& name)
|
||||
{
|
||||
// return the value of the first name-value pair whose name is name in this’s list, if there is such a pair, and null otherwise.
|
||||
auto result = m_list.find_if([&name](auto& entry) {
|
||||
return entry.name == name;
|
||||
});
|
||||
if (result.is_end())
|
||||
return {};
|
||||
return result->value;
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#dom-urlsearchparams-getall
|
||||
WebIDL::ExceptionOr<Vector<String>> URLSearchParams::get_all(String const& name)
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// return the values of all name-value pairs whose name is name, in this’s list, in list order, and the empty sequence otherwise.
|
||||
Vector<String> values;
|
||||
for (auto& entry : m_list) {
|
||||
if (entry.name == name)
|
||||
TRY_OR_THROW_OOM(vm, values.try_append(entry.value));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
bool URLSearchParams::has(String const& name)
|
||||
{
|
||||
// return true if there is a name-value pair whose name is name in this’s list, and false otherwise.
|
||||
return !m_list.find_if([&name](auto& entry) {
|
||||
return entry.name == name;
|
||||
})
|
||||
.is_end();
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> URLSearchParams::set(String const& name, String const& value)
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// 1. If this’s list contains any name-value pairs whose name is name, then set the value of the first such name-value pair to value and remove the others.
|
||||
auto existing = m_list.find_if([&name](auto& entry) {
|
||||
return entry.name == name;
|
||||
});
|
||||
if (!existing.is_end()) {
|
||||
existing->value = value;
|
||||
m_list.remove_all_matching([&name, &existing](auto& entry) {
|
||||
return &entry != &*existing && entry.name == name;
|
||||
});
|
||||
}
|
||||
// 2. Otherwise, append a new name-value pair whose name is name and value is value, to this’s list.
|
||||
else {
|
||||
TRY_OR_THROW_OOM(vm, m_list.try_empend(name, value));
|
||||
}
|
||||
// 3. Update this.
|
||||
TRY(update());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> URLSearchParams::sort()
|
||||
{
|
||||
// 1. Sort all name-value pairs, if any, by their names. Sorting must be done by comparison of code units. The relative order between name-value pairs with equal names must be preserved.
|
||||
quick_sort(m_list.begin(), m_list.end(), [](auto& a, auto& b) {
|
||||
Utf8View a_code_points { a.name };
|
||||
Utf8View b_code_points { b.name };
|
||||
|
||||
if (a_code_points.starts_with(b_code_points))
|
||||
return false;
|
||||
if (b_code_points.starts_with(a_code_points))
|
||||
return true;
|
||||
|
||||
for (auto k = a_code_points.begin(), l = b_code_points.begin();
|
||||
k != a_code_points.end() && l != b_code_points.end();
|
||||
++k, ++l) {
|
||||
if (*k != *l) {
|
||||
return *k < *l;
|
||||
}
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
});
|
||||
// 2. Update this.
|
||||
TRY(update());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<String> URLSearchParams::to_string() const
|
||||
{
|
||||
auto& vm = realm().vm();
|
||||
|
||||
// return the serialization of this’s list.
|
||||
return TRY_OR_THROW_OOM(vm, url_encode(m_list));
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<void> URLSearchParams::for_each(ForEachCallback callback)
|
||||
{
|
||||
for (auto i = 0u; i < m_list.size(); ++i) {
|
||||
auto& query_param = m_list[i]; // We are explicitly iterating over the indices here as the callback might delete items from the list
|
||||
TRY(callback(query_param.name, query_param.value));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
62
Userland/Libraries/LibWeb/DOMURL/URLSearchParams.h
Normal file
62
Userland/Libraries/LibWeb/DOMURL/URLSearchParams.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/URL.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::DOMURL {
|
||||
|
||||
struct QueryParam {
|
||||
String name;
|
||||
String value;
|
||||
};
|
||||
ErrorOr<String> url_encode(Vector<QueryParam> const&, StringView encoding = "UTF-8"sv);
|
||||
ErrorOr<Vector<QueryParam>> url_decode(StringView);
|
||||
|
||||
class URLSearchParams : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(URLSearchParams, Bindings::PlatformObject);
|
||||
JS_DECLARE_ALLOCATOR(URLSearchParams);
|
||||
|
||||
public:
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<URLSearchParams>> create(JS::Realm&, Vector<QueryParam> list);
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<URLSearchParams>> construct_impl(JS::Realm&, Variant<Vector<Vector<String>>, OrderedHashMap<String, String>, String> const& init);
|
||||
|
||||
virtual ~URLSearchParams() override;
|
||||
|
||||
size_t size() const;
|
||||
WebIDL::ExceptionOr<void> append(String const& name, String const& value);
|
||||
WebIDL::ExceptionOr<void> delete_(String const& name);
|
||||
Optional<String> get(String const& name);
|
||||
WebIDL::ExceptionOr<Vector<String>> get_all(String const& name);
|
||||
bool has(String const& name);
|
||||
WebIDL::ExceptionOr<void> set(String const& name, String const& value);
|
||||
|
||||
WebIDL::ExceptionOr<void> sort();
|
||||
|
||||
WebIDL::ExceptionOr<String> to_string() const;
|
||||
|
||||
using ForEachCallback = Function<JS::ThrowCompletionOr<void>(String const&, String const&)>;
|
||||
JS::ThrowCompletionOr<void> for_each(ForEachCallback);
|
||||
|
||||
private:
|
||||
friend class DOMURL;
|
||||
friend class URLSearchParamsIterator;
|
||||
|
||||
URLSearchParams(JS::Realm&, Vector<QueryParam> list);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
WebIDL::ExceptionOr<void> update();
|
||||
|
||||
Vector<QueryParam> m_list;
|
||||
JS::GCPtr<DOMURL> m_url;
|
||||
};
|
||||
|
||||
}
|
21
Userland/Libraries/LibWeb/DOMURL/URLSearchParams.idl
Normal file
21
Userland/Libraries/LibWeb/DOMURL/URLSearchParams.idl
Normal file
|
@ -0,0 +1,21 @@
|
|||
// https://url.spec.whatwg.org/#urlsearchparams
|
||||
[Exposed=*]
|
||||
interface URLSearchParams {
|
||||
|
||||
constructor(optional (sequence<sequence<USVString>> or record<USVString, USVString> or USVString) init = "");
|
||||
|
||||
readonly attribute unsigned long size;
|
||||
|
||||
undefined append(USVString name, USVString value);
|
||||
undefined delete(USVString name);
|
||||
USVString? get(USVString name);
|
||||
sequence<USVString> getAll(USVString name);
|
||||
boolean has(USVString name);
|
||||
undefined set(USVString name, USVString value);
|
||||
|
||||
undefined sort();
|
||||
|
||||
iterable<USVString, USVString>;
|
||||
stringifier;
|
||||
|
||||
};
|
68
Userland/Libraries/LibWeb/DOMURL/URLSearchParamsIterator.cpp
Normal file
68
Userland/Libraries/LibWeb/DOMURL/URLSearchParamsIterator.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/Iterator.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/URLSearchParamsIteratorPrototype.h>
|
||||
#include <LibWeb/DOMURL/URLSearchParamsIterator.h>
|
||||
|
||||
namespace Web::Bindings {
|
||||
|
||||
template<>
|
||||
void Intrinsics::create_web_prototype_and_constructor<URLSearchParamsIteratorPrototype>(JS::Realm& realm)
|
||||
{
|
||||
auto prototype = heap().allocate<URLSearchParamsIteratorPrototype>(realm, realm);
|
||||
m_prototypes.set("URLSearchParamsIterator"_fly_string, prototype);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Web::DOMURL {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(URLSearchParamsIterator);
|
||||
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<URLSearchParamsIterator>> URLSearchParamsIterator::create(URLSearchParams const& url_search_params, JS::Object::PropertyKind iteration_kind)
|
||||
{
|
||||
return url_search_params.heap().allocate<URLSearchParamsIterator>(url_search_params.realm(), url_search_params, iteration_kind);
|
||||
}
|
||||
|
||||
URLSearchParamsIterator::URLSearchParamsIterator(URLSearchParams const& url_search_params, JS::Object::PropertyKind iteration_kind)
|
||||
: PlatformObject(url_search_params.realm())
|
||||
, m_url_search_params(url_search_params)
|
||||
, m_iteration_kind(iteration_kind)
|
||||
{
|
||||
}
|
||||
|
||||
URLSearchParamsIterator::~URLSearchParamsIterator() = default;
|
||||
|
||||
void URLSearchParamsIterator::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
set_prototype(&Bindings::ensure_web_prototype<Bindings::URLSearchParamsIteratorPrototype>(realm, "URLSearchParamsIterator"_fly_string));
|
||||
}
|
||||
|
||||
void URLSearchParamsIterator::visit_edges(JS::Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_url_search_params);
|
||||
}
|
||||
|
||||
JS::Object* URLSearchParamsIterator::next()
|
||||
{
|
||||
if (m_index >= m_url_search_params->m_list.size())
|
||||
return create_iterator_result_object(vm(), JS::js_undefined(), true);
|
||||
|
||||
auto& entry = m_url_search_params->m_list[m_index++];
|
||||
if (m_iteration_kind == JS::Object::PropertyKind::Key)
|
||||
return create_iterator_result_object(vm(), JS::PrimitiveString::create(vm(), entry.name), false);
|
||||
else if (m_iteration_kind == JS::Object::PropertyKind::Value)
|
||||
return create_iterator_result_object(vm(), JS::PrimitiveString::create(vm(), entry.value), false);
|
||||
|
||||
return create_iterator_result_object(vm(), JS::Array::create_from(realm(), { JS::PrimitiveString::create(vm(), entry.name), JS::PrimitiveString::create(vm(), entry.value) }), false);
|
||||
}
|
||||
|
||||
}
|
36
Userland/Libraries/LibWeb/DOMURL/URLSearchParamsIterator.h
Normal file
36
Userland/Libraries/LibWeb/DOMURL/URLSearchParamsIterator.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/DOMURL/URLSearchParams.h>
|
||||
|
||||
namespace Web::DOMURL {
|
||||
|
||||
class URLSearchParamsIterator : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(URLSearchParamsIterator, Bindings::PlatformObject);
|
||||
JS_DECLARE_ALLOCATOR(URLSearchParamsIterator);
|
||||
|
||||
public:
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<URLSearchParamsIterator>> create(URLSearchParams const&, JS::Object::PropertyKind iteration_kind);
|
||||
|
||||
virtual ~URLSearchParamsIterator() override;
|
||||
|
||||
JS::Object* next();
|
||||
|
||||
private:
|
||||
URLSearchParamsIterator(URLSearchParams const&, JS::Object::PropertyKind iteration_kind);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
JS::NonnullGCPtr<URLSearchParams const> m_url_search_params;
|
||||
JS::Object::PropertyKind m_iteration_kind;
|
||||
size_t m_index { 0 };
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue