Merge branch 'LadybirdBrowser:master' into master

This commit is contained in:
xnacly 2025-04-12 17:42:46 +02:00 committed by GitHub
commit 4f403913cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
234 changed files with 7866 additions and 1153 deletions

View file

@ -114,6 +114,10 @@
# cmakedefine01 ICO_DEBUG # cmakedefine01 ICO_DEBUG
#endif #endif
#ifndef IDB_DEBUG
# cmakedefine01 IDB_DEBUG
#endif
#ifndef IDL_DEBUG #ifndef IDL_DEBUG
# cmakedefine01 IDL_DEBUG # cmakedefine01 IDL_DEBUG
#endif #endif

View file

@ -9,6 +9,7 @@
#include <LibCore/EventLoopImplementationWindows.h> #include <LibCore/EventLoopImplementationWindows.h>
#include <LibCore/Notifier.h> #include <LibCore/Notifier.h>
#include <LibCore/ThreadEventQueue.h> #include <LibCore/ThreadEventQueue.h>
#include <LibCore/Timer.h>
#include <AK/Windows.h> #include <AK/Windows.h>
@ -55,10 +56,12 @@ struct EventLoopTimer {
}; };
struct ThreadData { struct ThreadData {
static ThreadData& the() static ThreadData* the()
{ {
thread_local OwnPtr<ThreadData> thread_data = make<ThreadData>(); thread_local OwnPtr<ThreadData> thread_data = make<ThreadData>();
return *thread_data; if (thread_data)
return &*thread_data;
return nullptr;
} }
ThreadData() ThreadData()
@ -76,7 +79,7 @@ struct ThreadData {
}; };
EventLoopImplementationWindows::EventLoopImplementationWindows() EventLoopImplementationWindows::EventLoopImplementationWindows()
: m_wake_event(ThreadData::the().wake_event.handle) : m_wake_event(ThreadData::the()->wake_event.handle)
{ {
} }
@ -92,9 +95,10 @@ int EventLoopImplementationWindows::exec()
size_t EventLoopImplementationWindows::pump(PumpMode) size_t EventLoopImplementationWindows::pump(PumpMode)
{ {
auto& thread_data = ThreadData::the(); auto& event_queue = ThreadEventQueue::current();
auto& notifiers = thread_data.notifiers; auto* thread_data = ThreadData::the();
auto& timers = thread_data.timers; auto& notifiers = thread_data->notifiers;
auto& timers = thread_data->timers;
size_t event_count = 1 + notifiers.size() + timers.size(); size_t event_count = 1 + notifiers.size() + timers.size();
// If 64 events limit proves to be insufficient RegisterWaitForSingleObject or other methods // If 64 events limit proves to be insufficient RegisterWaitForSingleObject or other methods
@ -103,30 +107,41 @@ size_t EventLoopImplementationWindows::pump(PumpMode)
VERIFY(event_count <= MAXIMUM_WAIT_OBJECTS); VERIFY(event_count <= MAXIMUM_WAIT_OBJECTS);
Vector<HANDLE, MAXIMUM_WAIT_OBJECTS> event_handles; Vector<HANDLE, MAXIMUM_WAIT_OBJECTS> event_handles;
event_handles.append(thread_data.wake_event.handle); event_handles.append(thread_data->wake_event.handle);
for (auto& entry : notifiers) for (auto& entry : notifiers)
event_handles.append(entry.key.handle); event_handles.append(entry.key.handle);
for (auto& entry : timers) for (auto& entry : timers)
event_handles.append(entry.key.handle); event_handles.append(entry.key.handle);
DWORD result = WaitForMultipleObjects(event_count, event_handles.data(), FALSE, INFINITE); bool has_pending_events = event_queue.has_pending_events();
size_t index = result - WAIT_OBJECT_0; int timeout = has_pending_events ? 0 : INFINITE;
VERIFY(index < event_count); DWORD result = WaitForMultipleObjects(event_count, event_handles.data(), FALSE, timeout);
if (result == WAIT_TIMEOUT) {
if (index != 0) { // FIXME: This verification sometimes fails with ERROR_INVALID_HANDLE, but when I check
if (index <= notifiers.size()) { // the handles they all seem to be valid.
Notifier* notifier = *notifiers.get(event_handles[index]); // VERIFY(GetLastError() == ERROR_SUCCESS || GetLastError() == ERROR_IO_PENDING);
ThreadEventQueue::current().post_event(*notifier, make<NotifierActivationEvent>(notifier->fd(), notifier->type()));
} else { } else {
auto& timer = *timers.get(event_handles[index]); size_t const index = result - WAIT_OBJECT_0;
VERIFY(index < event_count);
// : 1 - skip wake event
for (size_t i = index ? index : 1; i < event_count; i++) {
// i == index already checked by WaitForMultipleObjects
if (i == index || WaitForSingleObject(event_handles[i], 0) == WAIT_OBJECT_0) {
if (i <= notifiers.size()) {
Notifier* notifier = *notifiers.get(event_handles[i]);
event_queue.post_event(*notifier, make<NotifierActivationEvent>(notifier->fd(), notifier->type()));
} else {
auto& timer = *timers.get(event_handles[i]);
if (auto strong_owner = timer.owner.strong_ref()) if (auto strong_owner = timer.owner.strong_ref())
if (timer.fire_when_not_visible == TimerShouldFireWhenNotVisible::Yes || strong_owner->is_visible_for_timer_purposes()) if (timer.fire_when_not_visible == TimerShouldFireWhenNotVisible::Yes || strong_owner->is_visible_for_timer_purposes())
ThreadEventQueue::current().post_event(*strong_owner, make<TimerEvent>()); event_queue.post_event(*strong_owner, make<TimerEvent>());
}
}
} }
} }
return ThreadEventQueue::current().process(); return event_queue.process();
} }
void EventLoopImplementationWindows::quit(int code) void EventLoopImplementationWindows::quit(int code)
@ -167,7 +182,7 @@ void EventLoopManagerWindows::register_notifier(Notifier& notifier)
int rc = WSAEventSelect(notifier.fd(), event, notifier_type_to_network_event(notifier.type())); int rc = WSAEventSelect(notifier.fd(), event, notifier_type_to_network_event(notifier.type()));
VERIFY(!rc); VERIFY(!rc);
auto& notifiers = ThreadData::the().notifiers; auto& notifiers = ThreadData::the()->notifiers;
VERIFY(!notifiers.get(event).has_value()); VERIFY(!notifiers.get(event).has_value());
notifiers.set(Handle(event), &notifier); notifiers.set(Handle(event), &notifier);
} }
@ -175,13 +190,16 @@ void EventLoopManagerWindows::register_notifier(Notifier& notifier)
void EventLoopManagerWindows::unregister_notifier(Notifier& notifier) void EventLoopManagerWindows::unregister_notifier(Notifier& notifier)
{ {
// remove_first_matching would be clearer, but currently there is no such method in HashMap // remove_first_matching would be clearer, but currently there is no such method in HashMap
ThreadData::the().notifiers.remove_all_matching([&](auto&, auto value) { return value == &notifier; }); if (ThreadData::the())
ThreadData::the()->notifiers.remove_all_matching([&](auto&, auto value) { return value == &notifier; });
} }
intptr_t EventLoopManagerWindows::register_timer(EventReceiver& object, int milliseconds, bool should_reload, TimerShouldFireWhenNotVisible fire_when_not_visible) intptr_t EventLoopManagerWindows::register_timer(EventReceiver& object, int milliseconds, bool should_reload, TimerShouldFireWhenNotVisible fire_when_not_visible)
{ {
VERIFY(milliseconds >= 0); VERIFY(milliseconds >= 0);
HANDLE timer = CreateWaitableTimer(NULL, FALSE, NULL); // FIXME: This is a temporary fix for issue #3641
bool manual_reset = static_cast<Timer&>(object).is_single_shot();
HANDLE timer = CreateWaitableTimer(NULL, manual_reset, NULL);
VERIFY(timer); VERIFY(timer);
LARGE_INTEGER first_time = {}; LARGE_INTEGER first_time = {};
@ -190,15 +208,16 @@ intptr_t EventLoopManagerWindows::register_timer(EventReceiver& object, int mill
BOOL rc = SetWaitableTimer(timer, &first_time, should_reload ? milliseconds : 0, NULL, NULL, FALSE); BOOL rc = SetWaitableTimer(timer, &first_time, should_reload ? milliseconds : 0, NULL, NULL, FALSE);
VERIFY(rc); VERIFY(rc);
auto& timers = ThreadData::the().timers; auto& timers = ThreadData::the()->timers;
VERIFY(!timers.get(timer).has_value()); VERIFY(!timers.get(timer).has_value());
timers.set(Handle(timer), { object, fire_when_not_visible }); timers.set(Handle(timer), { object, fire_when_not_visible });
return (intptr_t)timer; return reinterpret_cast<intptr_t>(timer);
} }
void EventLoopManagerWindows::unregister_timer(intptr_t timer_id) void EventLoopManagerWindows::unregister_timer(intptr_t timer_id)
{ {
ThreadData::the().timers.remove((HANDLE)timer_id); if (ThreadData::the())
ThreadData::the()->timers.remove(reinterpret_cast<HANDLE>(timer_id));
} }
int EventLoopManagerWindows::register_signal([[maybe_unused]] int signal_number, [[maybe_unused]] Function<void(int)> handler) int EventLoopManagerWindows::register_signal([[maybe_unused]] int signal_number, [[maybe_unused]] Function<void(int)> handler)

View file

@ -107,8 +107,8 @@ public:
visit(value); visit(value);
} }
template<typename T> template<typename T, size_t inline_capacity>
void visit(Vector<T> const& vector) void visit(Vector<T, inline_capacity> const& vector)
{ {
for (auto& value : vector) for (auto& value : vector)
visit(value); visit(value);

View file

@ -12,7 +12,6 @@
#include <LibIPC/Connection.h> #include <LibIPC/Connection.h>
#include <LibIPC/Message.h> #include <LibIPC/Message.h>
#include <LibIPC/Stub.h> #include <LibIPC/Stub.h>
#include <LibIPC/UnprocessedFileDescriptors.h>
namespace IPC { namespace IPC {
@ -40,21 +39,16 @@ bool ConnectionBase::is_open() const
ErrorOr<void> ConnectionBase::post_message(Message const& message) ErrorOr<void> ConnectionBase::post_message(Message const& message)
{ {
return post_message(message.endpoint_magic(), TRY(message.encode())); return post_message(TRY(message.encode()));
} }
ErrorOr<void> ConnectionBase::post_message(u32 endpoint_magic, MessageBuffer buffer) ErrorOr<void> ConnectionBase::post_message(MessageBuffer buffer)
{ {
// NOTE: If this connection is being shut down, but has not yet been destroyed, // NOTE: If this connection is being shut down, but has not yet been destroyed,
// the socket will be closed. Don't try to send more messages. // the socket will be closed. Don't try to send more messages.
if (!m_transport->is_open()) if (!m_transport->is_open())
return Error::from_string_literal("Trying to post_message during IPC shutdown"); return Error::from_string_literal("Trying to post_message during IPC shutdown");
if (buffer.data().size() > TransportSocket::SOCKET_BUFFER_SIZE) {
auto wrapper = LargeMessageWrapper::create(endpoint_magic, buffer);
buffer = MUST(wrapper->encode());
}
MUST(buffer.transfer_message(*m_transport)); MUST(buffer.transfer_message(*m_transport));
m_responsiveness_timer->start(); m_responsiveness_timer->start();
@ -85,7 +79,7 @@ void ConnectionBase::handle_messages()
} }
if (auto response = handler_result.release_value()) { if (auto response = handler_result.release_value()) {
if (auto post_result = post_message(m_local_endpoint_magic, *response); post_result.is_error()) { if (auto post_result = post_message(*response); post_result.is_error()) {
dbgln("IPC::ConnectionBase::handle_messages: {}", post_result.error()); dbgln("IPC::ConnectionBase::handle_messages: {}", post_result.error());
} }
} }
@ -100,24 +94,11 @@ void ConnectionBase::wait_for_transport_to_become_readable()
ErrorOr<void> ConnectionBase::drain_messages_from_peer() ErrorOr<void> ConnectionBase::drain_messages_from_peer()
{ {
auto schedule_shutdown = m_transport->read_as_many_messages_as_possible_without_blocking([&](auto&& unparsed_message) { auto schedule_shutdown = m_transport->read_as_many_messages_as_possible_without_blocking([&](auto&& raw_message) {
auto const& bytes = unparsed_message.bytes; if (auto message = try_parse_message(raw_message.bytes, raw_message.fds)) {
UnprocessedFileDescriptors unprocessed_fds;
unprocessed_fds.return_fds_to_front_of_queue(move(unparsed_message.fds));
if (auto message = try_parse_message(bytes, unprocessed_fds)) {
if (message->message_id() == LargeMessageWrapper::MESSAGE_ID) {
LargeMessageWrapper* wrapper = static_cast<LargeMessageWrapper*>(message.ptr());
auto wrapped_message = wrapper->wrapped_message_data();
unprocessed_fds.return_fds_to_front_of_queue(wrapper->take_fds());
auto parsed_message = try_parse_message(wrapped_message, unprocessed_fds);
VERIFY(parsed_message);
m_unprocessed_messages.append(parsed_message.release_nonnull());
return;
}
m_unprocessed_messages.append(message.release_nonnull()); m_unprocessed_messages.append(message.release_nonnull());
} else { } else {
dbgln("Failed to parse IPC message {:hex-dump}", bytes); dbgln("Failed to parse IPC message {:hex-dump}", raw_message.bytes);
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
}); });

View file

@ -15,10 +15,6 @@
#include <LibIPC/Forward.h> #include <LibIPC/Forward.h>
#include <LibIPC/Message.h> #include <LibIPC/Message.h>
#include <LibIPC/Transport.h> #include <LibIPC/Transport.h>
#include <LibIPC/UnprocessedFileDescriptors.h>
#include <LibThreading/ConditionVariable.h>
#include <LibThreading/MutexProtected.h>
#include <LibThreading/Thread.h>
namespace IPC { namespace IPC {
@ -30,7 +26,7 @@ public:
[[nodiscard]] bool is_open() const; [[nodiscard]] bool is_open() const;
ErrorOr<void> post_message(Message const&); ErrorOr<void> post_message(Message const&);
ErrorOr<void> post_message(u32 endpoint_magic, MessageBuffer); ErrorOr<void> post_message(MessageBuffer);
void shutdown(); void shutdown();
virtual void die() { } virtual void die() { }
@ -43,7 +39,7 @@ protected:
virtual void may_have_become_unresponsive() { } virtual void may_have_become_unresponsive() { }
virtual void did_become_responsive() { } virtual void did_become_responsive() { }
virtual void shutdown_with_error(Error const&); virtual void shutdown_with_error(Error const&);
virtual OwnPtr<Message> try_parse_message(ReadonlyBytes, UnprocessedFileDescriptors&) = 0; virtual OwnPtr<Message> try_parse_message(ReadonlyBytes, Queue<File>&) = 0;
OwnPtr<IPC::Message> wait_for_specific_endpoint_message_impl(u32 endpoint_magic, int message_id); OwnPtr<IPC::Message> wait_for_specific_endpoint_message_impl(u32 endpoint_magic, int message_id);
void wait_for_transport_to_become_readable(); void wait_for_transport_to_become_readable();
@ -102,7 +98,7 @@ protected:
return {}; return {};
} }
virtual OwnPtr<Message> try_parse_message(ReadonlyBytes bytes, UnprocessedFileDescriptors& fds) override virtual OwnPtr<Message> try_parse_message(ReadonlyBytes bytes, Queue<File>& fds) override
{ {
auto local_message = LocalEndpoint::decode_message(bytes, fds); auto local_message = LocalEndpoint::decode_message(bytes, fds);
if (!local_message.is_error()) if (!local_message.is_error())

View file

@ -23,7 +23,6 @@
#include <LibIPC/File.h> #include <LibIPC/File.h>
#include <LibIPC/Forward.h> #include <LibIPC/Forward.h>
#include <LibIPC/Message.h> #include <LibIPC/Message.h>
#include <LibIPC/UnprocessedFileDescriptors.h>
#include <LibURL/Origin.h> #include <LibURL/Origin.h>
#include <LibURL/URL.h> #include <LibURL/URL.h>
@ -38,7 +37,7 @@ inline ErrorOr<T> decode(Decoder&)
class Decoder { class Decoder {
public: public:
Decoder(Stream& stream, UnprocessedFileDescriptors& files) Decoder(Stream& stream, Queue<File>& files)
: m_stream(stream) : m_stream(stream)
, m_files(files) , m_files(files)
{ {
@ -63,11 +62,11 @@ public:
ErrorOr<size_t> decode_size(); ErrorOr<size_t> decode_size();
Stream& stream() { return m_stream; } Stream& stream() { return m_stream; }
UnprocessedFileDescriptors& files() { return m_files; } Queue<File>& files() { return m_files; }
private: private:
Stream& m_stream; Stream& m_stream;
UnprocessedFileDescriptors& m_files; Queue<File>& m_files;
}; };
template<Arithmetic T> template<Arithmetic T>

View file

@ -6,7 +6,6 @@
#include <AK/Checked.h> #include <AK/Checked.h>
#include <LibIPC/Decoder.h> #include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <LibIPC/Message.h> #include <LibIPC/Message.h>
namespace IPC { namespace IPC {
@ -47,53 +46,4 @@ ErrorOr<void> MessageBuffer::transfer_message(Transport& transport)
return {}; return {};
} }
NonnullOwnPtr<LargeMessageWrapper> LargeMessageWrapper::create(u32 endpoint_magic, MessageBuffer& buffer_to_wrap)
{
auto size = buffer_to_wrap.data().size();
auto wrapped_message_data = MUST(Core::AnonymousBuffer::create_with_size(size));
memcpy(wrapped_message_data.data<void>(), buffer_to_wrap.data().data(), size);
Vector<File> files;
for (auto& owned_fd : buffer_to_wrap.take_fds()) {
files.append(File::adopt_fd(owned_fd->take_fd()));
}
return make<LargeMessageWrapper>(endpoint_magic, move(wrapped_message_data), move(files));
}
LargeMessageWrapper::LargeMessageWrapper(u32 endpoint_magic, Core::AnonymousBuffer wrapped_message_data, Vector<File>&& wrapped_fds)
: m_endpoint_magic(endpoint_magic)
, m_wrapped_message_data(move(wrapped_message_data))
, m_wrapped_fds(move(wrapped_fds))
{
}
ErrorOr<MessageBuffer> LargeMessageWrapper::encode() const
{
MessageBuffer buffer;
Encoder stream { buffer };
TRY(stream.encode(m_endpoint_magic));
TRY(stream.encode(MESSAGE_ID));
TRY(stream.encode(m_wrapped_message_data));
TRY(stream.encode(m_wrapped_fds.size()));
for (auto const& wrapped_fd : m_wrapped_fds) {
TRY(stream.append_file_descriptor(wrapped_fd.take_fd()));
}
return buffer;
}
ErrorOr<NonnullOwnPtr<LargeMessageWrapper>> LargeMessageWrapper::decode(u32 endpoint_magic, Stream& stream, UnprocessedFileDescriptors& files)
{
Decoder decoder { stream, files };
auto wrapped_message_data = TRY(decoder.decode<Core::AnonymousBuffer>());
Vector<File> wrapped_fds;
auto num_fds = TRY(decoder.decode<u32>());
for (u32 i = 0; i < num_fds; ++i) {
auto fd = TRY(decoder.decode<IPC::File>());
wrapped_fds.append(move(fd));
}
return make<LargeMessageWrapper>(endpoint_magic, wrapped_message_data, move(wrapped_fds));
}
} }

View file

@ -8,14 +8,8 @@
#pragma once #pragma once
#include <AK/Error.h> #include <AK/Error.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibCore/AnonymousBuffer.h>
#include <LibCore/Forward.h>
#include <LibCore/System.h>
#include <LibIPC/Transport.h> #include <LibIPC/Transport.h>
#include <LibIPC/UnprocessedFileDescriptors.h>
namespace IPC { namespace IPC {
@ -67,30 +61,4 @@ protected:
Message() = default; Message() = default;
}; };
class LargeMessageWrapper : public Message {
public:
~LargeMessageWrapper() override = default;
static constexpr int MESSAGE_ID = 0x0;
static NonnullOwnPtr<LargeMessageWrapper> create(u32 endpoint_magic, MessageBuffer& buffer_to_wrap);
u32 endpoint_magic() const override { return m_endpoint_magic; }
int message_id() const override { return MESSAGE_ID; }
char const* message_name() const override { return "LargeMessageWrapper"; }
ErrorOr<MessageBuffer> encode() const override;
static ErrorOr<NonnullOwnPtr<LargeMessageWrapper>> decode(u32 endpoint_magic, Stream& stream, UnprocessedFileDescriptors& files);
ReadonlyBytes wrapped_message_data() const { return ReadonlyBytes { m_wrapped_message_data.data<u8>(), m_wrapped_message_data.size() }; }
auto take_fds() { return move(m_wrapped_fds); }
LargeMessageWrapper(u32 endpoint_magic, Core::AnonymousBuffer wrapped_message_data, Vector<IPC::File>&& wrapped_fds);
private:
u32 m_endpoint_magic { 0 };
Core::AnonymousBuffer m_wrapped_message_data;
Vector<File> m_wrapped_fds;
};
} }

View file

@ -13,26 +13,80 @@
namespace IPC { namespace IPC {
void SendQueue::enqueue_message(Vector<u8>&& bytes, Vector<int>&& fds)
{
Threading::MutexLocker locker(m_mutex);
m_bytes.append(bytes.data(), bytes.size());
m_fds.append(fds.data(), fds.size());
m_condition.signal();
}
SendQueue::Running SendQueue::block_until_message_enqueued()
{
Threading::MutexLocker locker(m_mutex);
while (m_bytes.is_empty() && m_fds.is_empty() && m_running)
m_condition.wait();
return m_running ? Running::Yes : Running::No;
}
SendQueue::BytesAndFds SendQueue::dequeue(size_t max_bytes)
{
Threading::MutexLocker locker(m_mutex);
auto bytes_to_send = min(max_bytes, m_bytes.size());
Vector<u8> bytes;
bytes.append(m_bytes.data(), bytes_to_send);
m_bytes.remove(0, bytes_to_send);
return { move(bytes), move(m_fds) };
}
void SendQueue::return_unsent_data_to_front_of_queue(ReadonlyBytes const& bytes, Vector<int> const& fds)
{
Threading::MutexLocker locker(m_mutex);
m_bytes.prepend(bytes.data(), bytes.size());
m_fds.prepend(fds.data(), fds.size());
}
void SendQueue::stop()
{
Threading::MutexLocker locker(m_mutex);
m_running = false;
m_condition.signal();
}
TransportSocket::TransportSocket(NonnullOwnPtr<Core::LocalSocket> socket) TransportSocket::TransportSocket(NonnullOwnPtr<Core::LocalSocket> socket)
: m_socket(move(socket)) : m_socket(move(socket))
{ {
m_send_queue = adopt_ref(*new SendQueue); m_send_queue = adopt_ref(*new SendQueue);
m_send_thread = Threading::Thread::construct([this, send_queue = m_send_queue]() -> intptr_t { m_send_thread = Threading::Thread::construct([this, send_queue = m_send_queue]() -> intptr_t {
for (;;) { for (;;) {
send_queue->mutex.lock(); if (send_queue->block_until_message_enqueued() == SendQueue::Running::No)
while (send_queue->messages.is_empty() && send_queue->running)
send_queue->condition.wait();
if (!send_queue->running) {
send_queue->mutex.unlock();
break; break;
auto [bytes, fds] = send_queue->dequeue(4096);
ReadonlyBytes remaining_to_send_bytes = bytes;
Threading::RWLockLocker<Threading::LockMode::Read> lock(m_socket_rw_lock);
auto result = send_message(*m_socket, remaining_to_send_bytes, fds);
if (result.is_error()) {
dbgln("TransportSocket::send_thread: {}", result.error());
VERIFY_NOT_REACHED();
} }
auto [bytes, fds] = send_queue->messages.take_first(); if (!remaining_to_send_bytes.is_empty() || !fds.is_empty()) {
send_queue->mutex.unlock(); send_queue->return_unsent_data_to_front_of_queue(remaining_to_send_bytes, fds);
}
if (auto result = send_message(*m_socket, bytes, fds); result.is_error()) { if (!m_socket->is_open())
dbgln("TransportSocket::send_thread: {}", result.error()); break;
{
Vector<struct pollfd, 1> pollfds;
pollfds.append({ .fd = m_socket->fd().value(), .events = POLLOUT, .revents = 0 });
ErrorOr<int> result { 0 };
do {
result = Core::System::poll(pollfds, -1);
} while (result.is_error() && result.error().code() == EINTR);
} }
} }
return 0; return 0;
@ -45,32 +99,32 @@ TransportSocket::TransportSocket(NonnullOwnPtr<Core::LocalSocket> socket)
TransportSocket::~TransportSocket() TransportSocket::~TransportSocket()
{ {
{ m_send_queue->stop();
Threading::MutexLocker locker(m_send_queue->mutex);
m_send_queue->running = false;
m_send_queue->condition.signal();
}
(void)m_send_thread->join(); (void)m_send_thread->join();
} }
void TransportSocket::set_up_read_hook(Function<void()> hook) void TransportSocket::set_up_read_hook(Function<void()> hook)
{ {
Threading::RWLockLocker<Threading::LockMode::Write> lock(m_socket_rw_lock);
VERIFY(m_socket->is_open()); VERIFY(m_socket->is_open());
m_socket->on_ready_to_read = move(hook); m_socket->on_ready_to_read = move(hook);
} }
bool TransportSocket::is_open() const bool TransportSocket::is_open() const
{ {
Threading::RWLockLocker<Threading::LockMode::Read> lock(m_socket_rw_lock);
return m_socket->is_open(); return m_socket->is_open();
} }
void TransportSocket::close() void TransportSocket::close()
{ {
Threading::RWLockLocker<Threading::LockMode::Write> lock(m_socket_rw_lock);
m_socket->close(); m_socket->close();
} }
void TransportSocket::wait_until_readable() void TransportSocket::wait_until_readable()
{ {
Threading::RWLockLocker<Threading::LockMode::Read> lock(m_socket_rw_lock);
auto maybe_did_become_readable = m_socket->can_read_without_blocking(-1); auto maybe_did_become_readable = m_socket->can_read_without_blocking(-1);
if (maybe_did_become_readable.is_error()) { if (maybe_did_become_readable.is_error()) {
dbgln("TransportSocket::wait_until_readable: {}", maybe_did_become_readable.error()); dbgln("TransportSocket::wait_until_readable: {}", maybe_did_become_readable.error());
@ -114,67 +168,39 @@ void TransportSocket::post_message(Vector<u8> const& bytes_to_write, Vector<Nonn
} }
} }
queue_message_on_send_thread({ move(message_buffer), move(raw_fds) }); m_send_queue->enqueue_message(move(message_buffer), move(raw_fds));
} }
void TransportSocket::queue_message_on_send_thread(MessageToSend&& message_to_send) const ErrorOr<void> TransportSocket::send_message(Core::LocalSocket& socket, ReadonlyBytes& bytes_to_write, Vector<int>& unowned_fds)
{
Threading::MutexLocker lock(m_send_queue->mutex);
m_send_queue->messages.append(move(message_to_send));
m_send_queue->condition.signal();
}
ErrorOr<void> TransportSocket::send_message(Core::LocalSocket& socket, ReadonlyBytes&& bytes_to_write, Vector<int, 1> const& unowned_fds)
{ {
auto num_fds_to_transfer = unowned_fds.size(); auto num_fds_to_transfer = unowned_fds.size();
while (!bytes_to_write.is_empty()) { while (!bytes_to_write.is_empty()) {
ErrorOr<ssize_t> maybe_nwritten = 0; ErrorOr<ssize_t> maybe_nwritten = 0;
if (num_fds_to_transfer > 0) { if (num_fds_to_transfer > 0) {
maybe_nwritten = socket.send_message(bytes_to_write, 0, unowned_fds); maybe_nwritten = socket.send_message(bytes_to_write, 0, unowned_fds);
if (!maybe_nwritten.is_error())
num_fds_to_transfer = 0;
} else { } else {
maybe_nwritten = socket.write_some(bytes_to_write); maybe_nwritten = socket.write_some(bytes_to_write);
} }
if (maybe_nwritten.is_error()) { if (maybe_nwritten.is_error()) {
if (auto error = maybe_nwritten.release_error(); error.is_errno() && (error.code() == EAGAIN || error.code() == EWOULDBLOCK)) { if (auto error = maybe_nwritten.release_error(); error.is_errno() && (error.code() == EAGAIN || error.code() == EWOULDBLOCK || error.code() == EINTR)) {
return {};
// FIXME: Refactor this to pass the unwritten bytes back to the caller to send 'later'
// or next time the socket is writable
Vector<struct pollfd, 1> pollfds;
if (pollfds.is_empty())
pollfds.append({ .fd = socket.fd().value(), .events = POLLOUT, .revents = 0 });
ErrorOr<int> result { 0 };
do {
constexpr u32 POLL_TIMEOUT_MS = 100;
result = Core::System::poll(pollfds, POLL_TIMEOUT_MS);
} while (result.is_error() && result.error().code() == EINTR);
if (!result.is_error() && result.value() != 0)
continue;
switch (error.code()) {
case EPIPE:
return Error::from_string_literal("IPC::transfer_message: Disconnected from peer");
case EAGAIN:
return Error::from_string_literal("IPC::transfer_message: Timed out waiting for socket to become writable");
default:
return Error::from_syscall("IPC::transfer_message write"sv, -error.code());
}
} else { } else {
return error; return error;
} }
} }
bytes_to_write = bytes_to_write.slice(maybe_nwritten.value()); bytes_to_write = bytes_to_write.slice(maybe_nwritten.value());
num_fds_to_transfer = 0;
unowned_fds.clear();
} }
return {}; return {};
} }
TransportSocket::ShouldShutdown TransportSocket::read_as_many_messages_as_possible_without_blocking(Function<void(Message)>&& callback) TransportSocket::ShouldShutdown TransportSocket::read_as_many_messages_as_possible_without_blocking(Function<void(Message&&)>&& callback)
{ {
Threading::RWLockLocker<Threading::LockMode::Read> lock(m_socket_rw_lock);
bool should_shutdown = false; bool should_shutdown = false;
while (is_open()) { while (is_open()) {
u8 buffer[4096]; u8 buffer[4096];
@ -222,7 +248,7 @@ TransportSocket::ShouldShutdown TransportSocket::read_as_many_messages_as_possib
Message message; Message message;
received_fd_count += header.fd_count; received_fd_count += header.fd_count;
for (size_t i = 0; i < header.fd_count; ++i) for (size_t i = 0; i < header.fd_count; ++i)
message.fds.append(m_unprocessed_fds.dequeue()); message.fds.enqueue(m_unprocessed_fds.dequeue());
message.bytes.append(m_unprocessed_bytes.data() + index + sizeof(MessageHeader), header.payload_size); message.bytes.append(m_unprocessed_bytes.data() + index + sizeof(MessageHeader), header.payload_size);
callback(move(message)); callback(move(message));
} else if (header.type == MessageHeader::Type::FileDescriptorAcknowledgement) { } else if (header.type == MessageHeader::Type::FileDescriptorAcknowledgement) {
@ -252,7 +278,7 @@ TransportSocket::ShouldShutdown TransportSocket::read_as_many_messages_as_possib
header.fd_count = received_fd_count; header.fd_count = received_fd_count;
header.type = MessageHeader::Type::FileDescriptorAcknowledgement; header.type = MessageHeader::Type::FileDescriptorAcknowledgement;
memcpy(message_buffer.data(), &header, sizeof(MessageHeader)); memcpy(message_buffer.data(), &header, sizeof(MessageHeader));
queue_message_on_send_thread({ move(message_buffer), {} }); m_send_queue->enqueue_message(move(message_buffer), {});
} }
if (index < m_unprocessed_bytes.size()) { if (index < m_unprocessed_bytes.size()) {
@ -267,11 +293,13 @@ TransportSocket::ShouldShutdown TransportSocket::read_as_many_messages_as_possib
ErrorOr<int> TransportSocket::release_underlying_transport_for_transfer() ErrorOr<int> TransportSocket::release_underlying_transport_for_transfer()
{ {
Threading::RWLockLocker<Threading::LockMode::Write> lock(m_socket_rw_lock);
return m_socket->release_fd(); return m_socket->release_fd();
} }
ErrorOr<IPC::File> TransportSocket::clone_for_transfer() ErrorOr<IPC::File> TransportSocket::clone_for_transfer()
{ {
Threading::RWLockLocker<Threading::LockMode::Write> lock(m_socket_rw_lock);
return IPC::File::clone_fd(m_socket->fd().value()); return IPC::File::clone_fd(m_socket->fd().value());
} }

View file

@ -9,9 +9,9 @@
#include <AK/Queue.h> #include <AK/Queue.h>
#include <LibCore/Socket.h> #include <LibCore/Socket.h>
#include <LibIPC/UnprocessedFileDescriptors.h>
#include <LibThreading/ConditionVariable.h> #include <LibThreading/ConditionVariable.h>
#include <LibThreading/MutexProtected.h> #include <LibThreading/MutexProtected.h>
#include <LibThreading/RWLock.h>
#include <LibThreading/Thread.h> #include <LibThreading/Thread.h>
namespace IPC { namespace IPC {
@ -42,6 +42,31 @@ private:
int m_fd; int m_fd;
}; };
class SendQueue : public AtomicRefCounted<SendQueue> {
public:
enum class Running {
No,
Yes,
};
Running block_until_message_enqueued();
void stop();
void enqueue_message(Vector<u8>&& bytes, Vector<int>&& fds);
struct BytesAndFds {
Vector<u8> bytes;
Vector<int> fds;
};
BytesAndFds dequeue(size_t max_bytes);
void return_unsent_data_to_front_of_queue(ReadonlyBytes const& bytes, Vector<int> const& fds);
private:
Vector<u8> m_bytes;
Vector<int> m_fds;
Threading::Mutex m_mutex;
Threading::ConditionVariable m_condition { m_mutex };
bool m_running { true };
};
class TransportSocket { class TransportSocket {
AK_MAKE_NONCOPYABLE(TransportSocket); AK_MAKE_NONCOPYABLE(TransportSocket);
AK_MAKE_NONMOVABLE(TransportSocket); AK_MAKE_NONMOVABLE(TransportSocket);
@ -66,9 +91,9 @@ public:
}; };
struct Message { struct Message {
Vector<u8> bytes; Vector<u8> bytes;
Vector<File> fds; Queue<File> fds;
}; };
ShouldShutdown read_as_many_messages_as_possible_without_blocking(Function<void(Message)>&& schedule_shutdown); ShouldShutdown read_as_many_messages_as_possible_without_blocking(Function<void(Message&&)>&&);
// Obnoxious name to make it clear that this is a dangerous operation. // Obnoxious name to make it clear that this is a dangerous operation.
ErrorOr<int> release_underlying_transport_for_transfer(); ErrorOr<int> release_underlying_transport_for_transfer();
@ -76,30 +101,20 @@ public:
ErrorOr<IPC::File> clone_for_transfer(); ErrorOr<IPC::File> clone_for_transfer();
private: private:
static ErrorOr<void> send_message(Core::LocalSocket&, ReadonlyBytes&&, Vector<int, 1> const& unowned_fds); static ErrorOr<void> send_message(Core::LocalSocket&, ReadonlyBytes& bytes, Vector<int>& unowned_fds);
NonnullOwnPtr<Core::LocalSocket> m_socket; NonnullOwnPtr<Core::LocalSocket> m_socket;
mutable Threading::RWLock m_socket_rw_lock;
ByteBuffer m_unprocessed_bytes; ByteBuffer m_unprocessed_bytes;
UnprocessedFileDescriptors m_unprocessed_fds; Queue<File> m_unprocessed_fds;
// After file descriptor is sent, it is moved to the wait queue until an acknowledgement is received from the peer. // After file descriptor is sent, it is moved to the wait queue until an acknowledgement is received from the peer.
// This is necessary to handle a specific behavior of the macOS kernel, which may prematurely garbage-collect the file // This is necessary to handle a specific behavior of the macOS kernel, which may prematurely garbage-collect the file
// descriptor contained in the message before the peer receives it. https://openradar.me/9477351 // descriptor contained in the message before the peer receives it. https://openradar.me/9477351
Queue<NonnullRefPtr<AutoCloseFileDescriptor>> m_fds_retained_until_received_by_peer; Queue<NonnullRefPtr<AutoCloseFileDescriptor>> m_fds_retained_until_received_by_peer;
struct MessageToSend {
Vector<u8> bytes;
Vector<int, 1> fds;
};
struct SendQueue : public AtomicRefCounted<SendQueue> {
AK::SinglyLinkedList<MessageToSend> messages;
Threading::Mutex mutex;
Threading::ConditionVariable condition { mutex };
bool running { true };
};
RefPtr<Threading::Thread> m_send_thread; RefPtr<Threading::Thread> m_send_thread;
RefPtr<SendQueue> m_send_queue; RefPtr<SendQueue> m_send_queue;
void queue_message_on_send_thread(MessageToSend&&) const;
}; };
} }

View file

@ -1,36 +0,0 @@
/*
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibIPC/File.h>
namespace IPC {
class UnprocessedFileDescriptors {
public:
void enqueue(File&& fd)
{
m_fds.append(move(fd));
}
File dequeue()
{
return m_fds.take_first();
}
void return_fds_to_front_of_queue(Vector<File>&& fds)
{
m_fds.prepend(move(fds));
}
size_t size() const { return m_fds.size(); }
private:
Vector<File> m_fds;
};
}

View file

@ -69,7 +69,7 @@ static void update_function_name(Value value, FlyString const& name)
if (!value.is_function()) if (!value.is_function())
return; return;
auto& function = value.as_function(); auto& function = value.as_function();
if (is<ECMAScriptFunctionObject>(function) && function.name().is_empty()) if (is<ECMAScriptFunctionObject>(function) && static_cast<ECMAScriptFunctionObject const&>(function).name().is_empty())
static_cast<ECMAScriptFunctionObject&>(function).set_name(name); static_cast<ECMAScriptFunctionObject&>(function).set_name(name);
} }

View file

@ -1212,14 +1212,14 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
switch (kind) { switch (kind) {
case Op::PropertyKind::Getter: { case Op::PropertyKind::Getter: {
auto& function = value.as_function(); auto& function = value.as_function();
if (function.name().is_empty() && is<ECMAScriptFunctionObject>(function)) if (is<ECMAScriptFunctionObject>(function) && static_cast<ECMAScriptFunctionObject const&>(function).name().is_empty())
static_cast<ECMAScriptFunctionObject*>(&function)->set_name(MUST(String::formatted("get {}", name))); static_cast<ECMAScriptFunctionObject*>(&function)->set_name(MUST(String::formatted("get {}", name)));
object->define_direct_accessor(name, &function, nullptr, Attribute::Configurable | Attribute::Enumerable); object->define_direct_accessor(name, &function, nullptr, Attribute::Configurable | Attribute::Enumerable);
break; break;
} }
case Op::PropertyKind::Setter: { case Op::PropertyKind::Setter: {
auto& function = value.as_function(); auto& function = value.as_function();
if (function.name().is_empty() && is<ECMAScriptFunctionObject>(function)) if (is<ECMAScriptFunctionObject>(function) && static_cast<ECMAScriptFunctionObject const&>(function).name().is_empty())
static_cast<ECMAScriptFunctionObject*>(&function)->set_name(MUST(String::formatted("set {}", name))); static_cast<ECMAScriptFunctionObject*>(&function)->set_name(MUST(String::formatted("set {}", name)));
object->define_direct_accessor(name, nullptr, &function, Attribute::Configurable | Attribute::Enumerable); object->define_direct_accessor(name, nullptr, &function, Attribute::Configurable | Attribute::Enumerable);
break; break;

View file

@ -58,11 +58,16 @@ protected:
explicit Array(Object& prototype); explicit Array(Object& prototype);
private: private:
virtual bool is_array_exotic_object() const final { return true; }
ThrowCompletionOr<bool> set_length(PropertyDescriptor const&); ThrowCompletionOr<bool> set_length(PropertyDescriptor const&);
bool m_length_writable { true }; bool m_length_writable { true };
}; };
template<>
inline bool Object::fast_is<Array>() const { return is_array_exotic_object(); }
enum class Holes { enum class Holes {
SkipHoles, SkipHoles,
ReadThroughHoles, ReadThroughHoles,

View file

@ -39,8 +39,6 @@ BoundFunction::BoundFunction(Realm& realm, FunctionObject& bound_target_function
, m_bound_target_function(&bound_target_function) , m_bound_target_function(&bound_target_function)
, m_bound_this(bound_this) , m_bound_this(bound_this)
, m_bound_arguments(move(bound_arguments)) , m_bound_arguments(move(bound_arguments))
// FIXME: Non-standard and redundant, remove.
, m_name(MUST(String::formatted("bound {}", bound_target_function.name())))
{ {
} }

View file

@ -23,7 +23,6 @@ public:
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override; virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override;
virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct(ReadonlySpan<Value> arguments_list, FunctionObject& new_target) override; virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct(ReadonlySpan<Value> arguments_list, FunctionObject& new_target) override;
virtual FlyString const& name() const override { return m_name; }
virtual bool is_strict_mode() const override { return m_bound_target_function->is_strict_mode(); } virtual bool is_strict_mode() const override { return m_bound_target_function->is_strict_mode(); }
virtual bool has_constructor() const override { return m_bound_target_function->has_constructor(); } virtual bool has_constructor() const override { return m_bound_target_function->has_constructor(); }
@ -39,8 +38,6 @@ private:
GC::Ptr<FunctionObject> m_bound_target_function; // [[BoundTargetFunction]] GC::Ptr<FunctionObject> m_bound_target_function; // [[BoundTargetFunction]]
Value m_bound_this; // [[BoundThis]] Value m_bound_this; // [[BoundThis]]
Vector<Value> m_bound_arguments; // [[BoundArguments]] Vector<Value> m_bound_arguments; // [[BoundArguments]]
FlyString m_name;
}; };
} }

View file

@ -426,10 +426,9 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(
, m_shared_data(move(shared_data)) , m_shared_data(move(shared_data))
, m_environment(parent_environment) , m_environment(parent_environment)
, m_private_environment(private_environment) , m_private_environment(private_environment)
, m_realm(&prototype.shape().realm())
{ {
if (!is_arrow_function() && kind() == FunctionKind::Normal) if (!is_arrow_function() && kind() == FunctionKind::Normal)
unsafe_set_shape(m_realm->intrinsics().normal_function_shape()); unsafe_set_shape(realm()->intrinsics().normal_function_shape());
// 15. Set F.[[ScriptOrModule]] to GetActiveScriptOrModule(). // 15. Set F.[[ScriptOrModule]] to GetActiveScriptOrModule().
m_script_or_module = vm().get_active_script_or_module(); m_script_or_module = vm().get_active_script_or_module();
@ -643,7 +642,6 @@ void ECMAScriptFunctionObject::visit_edges(Visitor& visitor)
Base::visit_edges(visitor); Base::visit_edges(visitor);
visitor.visit(m_environment); visitor.visit(m_environment);
visitor.visit(m_private_environment); visitor.visit(m_private_environment);
visitor.visit(m_realm);
visitor.visit(m_home_object); visitor.visit(m_home_object);
visitor.visit(m_name_string); visitor.visit(m_name_string);
@ -694,27 +692,13 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::prepare_for_ordinary_call(Exec
// 1. Let callerContext be the running execution context. // 1. Let callerContext be the running execution context.
// 2. Let calleeContext be a new ECMAScript code execution context. // 2. Let calleeContext be a new ECMAScript code execution context.
// NOTE: In the specification, PrepareForOrdinaryCall "returns" a new callee execution context.
// To avoid heap allocations, we put our ExecutionContext objects on the C++ stack instead.
// Whoever calls us should put an ExecutionContext on their stack and pass that as the `callee_context`.
// 3. Set the Function of calleeContext to F. // 3. Set the Function of calleeContext to F.
callee_context.function = this; callee_context.function = this;
callee_context.function_name = m_name_string; callee_context.function_name = m_name_string;
// 4. Let calleeRealm be F.[[Realm]]. // 4. Let calleeRealm be F.[[Realm]].
auto callee_realm = m_realm;
// NOTE: This non-standard fallback is needed until we can guarantee that literally
// every function has a realm - especially in LibWeb that's sometimes not the case
// when a function is created while no JS is running, as we currently need to rely on
// that (:acid2:, I know - see set_event_handler_attribute() for an example).
// If there's no 'current realm' either, we can't continue and crash.
if (!callee_realm)
callee_realm = vm.current_realm();
VERIFY(callee_realm);
// 5. Set the Realm of calleeContext to calleeRealm. // 5. Set the Realm of calleeContext to calleeRealm.
callee_context.realm = callee_realm; callee_context.realm = realm();
// 6. Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]]. // 6. Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]].
callee_context.script_or_module = m_script_or_module; callee_context.script_or_module = m_script_or_module;
@ -758,15 +742,7 @@ void ECMAScriptFunctionObject::ordinary_call_bind_this(ExecutionContext& callee_
return; return;
// 3. Let calleeRealm be F.[[Realm]]. // 3. Let calleeRealm be F.[[Realm]].
auto callee_realm = m_realm; auto callee_realm = realm();
// NOTE: This non-standard fallback is needed until we can guarantee that literally
// every function has a realm - especially in LibWeb that's sometimes not the case
// when a function is created while no JS is running, as we currently need to rely on
// that (:acid2:, I know - see set_event_handler_attribute() for an example).
// If there's no 'current realm' either, we can't continue and crash.
if (!callee_realm)
callee_realm = vm.current_realm();
VERIFY(callee_realm);
// 4. Let localEnv be the LexicalEnvironment of calleeContext. // 4. Let localEnv be the LexicalEnvironment of calleeContext.
auto local_env = callee_context.lexical_environment; auto local_env = callee_context.lexical_environment;

View file

@ -127,7 +127,7 @@ public:
Statement const& ecmascript_code() const { return *shared_data().m_ecmascript_code; } Statement const& ecmascript_code() const { return *shared_data().m_ecmascript_code; }
[[nodiscard]] virtual FunctionParameters const& formal_parameters() const override { return *shared_data().m_formal_parameters; } [[nodiscard]] virtual FunctionParameters const& formal_parameters() const override { return *shared_data().m_formal_parameters; }
virtual FlyString const& name() const override { return shared_data().m_name; } FlyString const& name() const { return shared_data().m_name; }
void set_name(FlyString const& name); void set_name(FlyString const& name);
void set_is_class_constructor() { const_cast<SharedFunctionInstanceData&>(shared_data()).m_is_class_constructor = true; } void set_is_class_constructor() { const_cast<SharedFunctionInstanceData&>(shared_data()).m_is_class_constructor = true; }
@ -135,7 +135,7 @@ public:
auto& bytecode_executable() const { return m_bytecode_executable; } auto& bytecode_executable() const { return m_bytecode_executable; }
Environment* environment() { return m_environment; } Environment* environment() { return m_environment; }
virtual Realm* realm() const override { return m_realm; } virtual Realm* realm() const override { return &shape().realm(); }
[[nodiscard]] ConstructorKind constructor_kind() const { return shared_data().m_constructor_kind; } [[nodiscard]] ConstructorKind constructor_kind() const { return shared_data().m_constructor_kind; }
void set_constructor_kind(ConstructorKind constructor_kind) { const_cast<SharedFunctionInstanceData&>(shared_data()).m_constructor_kind = constructor_kind; } void set_constructor_kind(ConstructorKind constructor_kind) { const_cast<SharedFunctionInstanceData&>(shared_data()).m_constructor_kind = constructor_kind; }
@ -210,7 +210,6 @@ private:
// Internal Slots of ECMAScript Function Objects, https://tc39.es/ecma262/#table-internal-slots-of-ecmascript-function-objects // Internal Slots of ECMAScript Function Objects, https://tc39.es/ecma262/#table-internal-slots-of-ecmascript-function-objects
GC::Ptr<Environment> m_environment; // [[Environment]] GC::Ptr<Environment> m_environment; // [[Environment]]
GC::Ptr<PrivateEnvironment> m_private_environment; // [[PrivateEnvironment]] GC::Ptr<PrivateEnvironment> m_private_environment; // [[PrivateEnvironment]]
GC::Ptr<Realm> m_realm; // [[Realm]]
ScriptOrModule m_script_or_module; // [[ScriptOrModule]] ScriptOrModule m_script_or_module; // [[ScriptOrModule]]
GC::Ptr<Object> m_home_object; // [[HomeObject]] GC::Ptr<Object> m_home_object; // [[HomeObject]]
struct ClassData { struct ClassData {

View file

@ -26,8 +26,6 @@ public:
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) = 0; virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) = 0;
virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct([[maybe_unused]] ReadonlySpan<Value> arguments_list, [[maybe_unused]] FunctionObject& new_target) { VERIFY_NOT_REACHED(); } virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct([[maybe_unused]] ReadonlySpan<Value> arguments_list, [[maybe_unused]] FunctionObject& new_target) { VERIFY_NOT_REACHED(); }
virtual FlyString const& name() const = 0;
void set_function_name(Variant<PropertyKey, PrivateName> const& name_arg, Optional<StringView> const& prefix = {}); void set_function_name(Variant<PropertyKey, PrivateName> const& name_arg, Optional<StringView> const& prefix = {});
void set_function_length(double length); void set_function_length(double length);

View file

@ -19,7 +19,6 @@ public:
virtual ~FunctionPrototype() override = default; virtual ~FunctionPrototype() override = default;
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override; virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override;
virtual FlyString const& name() const override { return m_name; }
private: private:
explicit FunctionPrototype(Realm&); explicit FunctionPrototype(Realm&);
@ -29,9 +28,6 @@ private:
JS_DECLARE_NATIVE_FUNCTION(call); JS_DECLARE_NATIVE_FUNCTION(call);
JS_DECLARE_NATIVE_FUNCTION(to_string); JS_DECLARE_NATIVE_FUNCTION(to_string);
JS_DECLARE_NATIVE_FUNCTION(symbol_has_instance); JS_DECLARE_NATIVE_FUNCTION(symbol_has_instance);
// 20.2.3: The Function prototype object has a "name" property whose value is the empty String.
FlyString m_name;
}; };
} }

View file

@ -34,7 +34,7 @@ public:
virtual ThrowCompletionOr<Value> call(); virtual ThrowCompletionOr<Value> call();
virtual ThrowCompletionOr<GC::Ref<Object>> construct(FunctionObject& new_target); virtual ThrowCompletionOr<GC::Ref<Object>> construct(FunctionObject& new_target);
virtual FlyString const& name() const override { return m_name; } FlyString const& name() const { return m_name; }
virtual bool is_strict_mode() const override; virtual bool is_strict_mode() const override;
virtual bool has_constructor() const override { return false; } virtual bool has_constructor() const override { return false; }
virtual Realm* realm() const override { return m_realm; } virtual Realm* realm() const override { return m_realm; }

View file

@ -198,6 +198,7 @@ public:
virtual bool is_regexp_object() const { return false; } virtual bool is_regexp_object() const { return false; }
virtual bool is_bigint_object() const { return false; } virtual bool is_bigint_object() const { return false; }
virtual bool is_string_object() const { return false; } virtual bool is_string_object() const { return false; }
virtual bool is_array_exotic_object() const { return false; }
virtual bool is_global_object() const { return false; } virtual bool is_global_object() const { return false; }
virtual bool is_proxy_object() const { return false; } virtual bool is_proxy_object() const { return false; }
virtual bool is_native_function() const { return false; } virtual bool is_native_function() const { return false; }

View file

@ -106,6 +106,17 @@ Utf16View PrimitiveString::utf16_string_view() const
return m_utf16_string->view(); return m_utf16_string->view();
} }
bool PrimitiveString::operator==(PrimitiveString const& other) const
{
if (this == &other)
return true;
if (m_utf8_string.has_value() && other.m_utf8_string.has_value())
return m_utf8_string->bytes_as_string_view() == other.m_utf8_string->bytes_as_string_view();
if (m_utf16_string.has_value() && other.m_utf16_string.has_value())
return m_utf16_string->string() == other.m_utf16_string->string();
return utf8_string_view() == other.utf8_string_view();
}
ThrowCompletionOr<Optional<Value>> PrimitiveString::get(VM& vm, PropertyKey const& property_key) const ThrowCompletionOr<Optional<Value>> PrimitiveString::get(VM& vm, PropertyKey const& property_key) const
{ {
if (property_key.is_symbol()) if (property_key.is_symbol())

View file

@ -47,6 +47,8 @@ public:
ThrowCompletionOr<Optional<Value>> get(VM&, PropertyKey const&) const; ThrowCompletionOr<Optional<Value>> get(VM&, PropertyKey const&) const;
[[nodiscard]] bool operator==(PrimitiveString const&) const;
protected: protected:
enum class RopeTag { Rope }; enum class RopeTag { Rope };
explicit PrimitiveString(RopeTag) explicit PrimitiveString(RopeTag)

View file

@ -897,10 +897,4 @@ void ProxyObject::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_handler); visitor.visit(m_handler);
} }
FlyString const& ProxyObject::name() const
{
VERIFY(is_function());
return static_cast<FunctionObject&>(*m_target).name();
}
} }

View file

@ -21,7 +21,6 @@ public:
virtual ~ProxyObject() override = default; virtual ~ProxyObject() override = default;
virtual FlyString const& name() const override;
virtual bool has_constructor() const override; virtual bool has_constructor() const override;
Object const& target() const { return m_target; } Object const& target() const { return m_target; }

View file

@ -759,13 +759,6 @@ void VM::pop_execution_context()
on_call_stack_emptied(); on_call_stack_emptied();
} }
#if ARCH(X86_64)
struct [[gnu::packed]] NativeStackFrame {
NativeStackFrame* prev;
FlatPtr return_address;
};
#endif
static RefPtr<CachedSourceRange> get_source_range(ExecutionContext const* context) static RefPtr<CachedSourceRange> get_source_range(ExecutionContext const* context)
{ {
// native function // native function

View file

@ -926,6 +926,26 @@ ThrowCompletionOr<PropertyKey> Value::to_property_key(VM& vm) const
return MUST(key.to_string(vm)); return MUST(key.to_string(vm));
} }
// 7.1.6 ToInt32 ( argument ), https://tc39.es/ecma262/#sec-toint32
ThrowCompletionOr<i32> Value::to_i32(VM& vm) const
{
if (is_int32())
return as_i32();
#if __has_builtin(__builtin_arm_jcvt)
if (is_double())
return __builtin_arm_jcvt(m_value.as_double);
#endif
return to_i32_slow_case(vm);
}
// 7.1.7 ToUint32 ( argument ), https://tc39.es/ecma262/#sec-touint32
ThrowCompletionOr<u32> Value::to_u32(VM& vm) const
{
return static_cast<u32>(TRY(to_i32(vm)));
}
// 7.1.6 ToInt32 ( argument ), https://tc39.es/ecma262/#sec-toint32 // 7.1.6 ToInt32 ( argument ), https://tc39.es/ecma262/#sec-toint32
ThrowCompletionOr<i32> Value::to_i32_slow_case(VM& vm) const ThrowCompletionOr<i32> Value::to_i32_slow_case(VM& vm) const
{ {
@ -934,6 +954,9 @@ ThrowCompletionOr<i32> Value::to_i32_slow_case(VM& vm) const
// 1. Let number be ? ToNumber(argument). // 1. Let number be ? ToNumber(argument).
double number = TRY(to_number(vm)).as_double(); double number = TRY(to_number(vm)).as_double();
#if __has_builtin(__builtin_arm_jcvt)
return __builtin_arm_jcvt(number);
#else
// 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽.
if (!isfinite(number) || number == 0) if (!isfinite(number) || number == 0)
return 0; return 0;
@ -951,42 +974,7 @@ ThrowCompletionOr<i32> Value::to_i32_slow_case(VM& vm) const
if (int32bit >= 2147483648.0) if (int32bit >= 2147483648.0)
int32bit -= 4294967296.0; int32bit -= 4294967296.0;
return static_cast<i32>(int32bit); return static_cast<i32>(int32bit);
} #endif
// 7.1.6 ToInt32 ( argument ), https://tc39.es/ecma262/#sec-toint32
ThrowCompletionOr<i32> Value::to_i32(VM& vm) const
{
if (is_int32())
return as_i32();
return to_i32_slow_case(vm);
}
// 7.1.7 ToUint32 ( argument ), https://tc39.es/ecma262/#sec-touint32
ThrowCompletionOr<u32> Value::to_u32(VM& vm) const
{
// OPTIMIZATION: If this value is encoded as a positive i32, return it directly.
if (is_int32() && as_i32() >= 0)
return as_i32();
// 1. Let number be ? ToNumber(argument).
double number = TRY(to_number(vm)).as_double();
// 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽.
if (!isfinite(number) || number == 0)
return 0;
// 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs((number))).
auto int_val = floor(fabs(number));
if (signbit(number))
int_val = -int_val;
// 4. Let int32bit be int modulo 2^32.
auto int32bit = modulo(int_val, NumericLimits<u32>::max() + 1.0);
// 5. Return 𝔽(int32bit).
// Cast to i64 here to ensure that the double --> u32 cast doesn't invoke undefined behavior
// Otherwise, negative numbers cause a UBSAN warning.
return static_cast<u32>(static_cast<i64>(int32bit));
} }
// 7.1.8 ToInt16 ( argument ), https://tc39.es/ecma262/#sec-toint16 // 7.1.8 ToInt16 ( argument ), https://tc39.es/ecma262/#sec-toint16
@ -2237,7 +2225,7 @@ bool same_value_non_number(Value lhs, Value rhs)
// 5. If x is a String, then // 5. If x is a String, then
if (lhs.is_string()) { if (lhs.is_string()) {
// a. If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices), return true; otherwise, return false. // a. If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices), return true; otherwise, return false.
return lhs.as_string().utf8_string_view() == rhs.as_string().utf8_string_view(); return lhs.as_string() == rhs.as_string();
} }
// 3. If x is undefined, return true. // 3. If x is undefined, return true.

View file

@ -22,9 +22,6 @@ public:
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override; virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override;
// FIXME: Remove this (and stop inventing random internal slots that shouldn't exist, jeez)
virtual FlyString const& name() const override { return m_wrapped_target_function->name(); }
virtual Realm* realm() const override { return m_realm; } virtual Realm* realm() const override { return m_realm; }
FunctionObject const& wrapped_target_function() const { return m_wrapped_target_function; } FunctionObject const& wrapped_target_function() const { return m_wrapped_target_function; }

View file

@ -25,9 +25,9 @@ public:
void restart() { m_started = UnixDateTime::now(); } void restart() { m_started = UnixDateTime::now(); }
u64 elapsed_milliseconds() AK::Duration elapsed() const
{ {
return (UnixDateTime::now() - m_started).to_milliseconds(); return UnixDateTime::now() - m_started;
} }
private: private:
@ -187,17 +187,18 @@ int TestSuite::run(Vector<NonnullRefPtr<TestCase>> const& tests)
m_current_test_result = TestResult::NotRun; m_current_test_result = TestResult::NotRun;
enable_reporting(); enable_reporting();
u64 total_time = 0; AK::Duration total_time;
u64 sum_of_squared_times = 0; u64 sum_of_squared_times = 0;
u64 min_time = NumericLimits<u64>::max(); AK::Duration min_time = AK::Duration::max();
u64 max_time = 0; AK::Duration max_time;
for (u64 i = 0; i < repetitions; ++i) { for (u64 i = 0; i < repetitions; ++i) {
TestElapsedTimer timer; TestElapsedTimer timer;
t->func()(); t->func()();
auto const iteration_time = timer.elapsed_milliseconds(); auto const iteration_time = timer.elapsed();
auto const iteration_ms = iteration_time.to_milliseconds();
total_time += iteration_time; total_time += iteration_time;
sum_of_squared_times += iteration_time * iteration_time; sum_of_squared_times += iteration_ms * iteration_ms;
min_time = min(min_time, iteration_time); min_time = min(min_time, iteration_time);
max_time = max(max_time, iteration_time); max_time = max(max_time, iteration_time);
@ -206,20 +207,26 @@ int TestSuite::run(Vector<NonnullRefPtr<TestCase>> const& tests)
m_current_test_result = TestResult::Passed; m_current_test_result = TestResult::Passed;
} }
auto const total_time_ms = total_time.to_milliseconds();
if (repetitions != 1) { if (repetitions != 1) {
double average = total_time / double(repetitions); double average = total_time_ms / static_cast<double>(repetitions);
double average_squared = average * average; double average_squared = average * average;
double standard_deviation = sqrt((sum_of_squared_times + repetitions * average_squared - 2 * total_time * average) / (repetitions - 1)); double standard_deviation = sqrt((sum_of_squared_times + repetitions * average_squared - 2 * total_time_ms * average) / (repetitions - 1));
dbgln("{} {} '{}' on average in {:.1f}±{:.1f}ms (min={}ms, max={}ms, total={}ms)", dbgln("{} {} '{}' on average in {:.1f}±{:.1f}ms (min={}ms, max={}ms, total={}ms)",
test_result_to_string(m_current_test_result), test_type, t->name(), test_result_to_string(m_current_test_result), test_type, t->name(),
average, standard_deviation, min_time, max_time, total_time); average,
standard_deviation,
min_time.to_milliseconds(),
max_time.to_milliseconds(),
total_time_ms);
} else { } else {
dbgln("{} {} '{}' in {}ms", test_result_to_string(m_current_test_result), test_type, t->name(), total_time); dbgln("{} {} '{}' in {}ms", test_result_to_string(m_current_test_result), test_type, t->name(), total_time_ms);
} }
if (t->is_benchmark()) { if (t->is_benchmark()) {
m_benchtime += total_time; m_bench_time += total_time;
benchmark_count++; benchmark_count++;
switch (m_current_test_result) { switch (m_current_test_result) {
@ -233,7 +240,7 @@ int TestSuite::run(Vector<NonnullRefPtr<TestCase>> const& tests)
break; break;
} }
} else { } else {
m_testtime += total_time; m_test_time += total_time;
test_count++; test_count++;
switch (m_current_test_result) { switch (m_current_test_result) {
@ -249,13 +256,16 @@ int TestSuite::run(Vector<NonnullRefPtr<TestCase>> const& tests)
} }
} }
auto const runtime = m_test_time + m_bench_time;
auto const elapsed = global_timer.elapsed() - runtime;
dbgln("Finished {} tests and {} benchmarks in {}ms ({}ms tests, {}ms benchmarks, {}ms other).", dbgln("Finished {} tests and {} benchmarks in {}ms ({}ms tests, {}ms benchmarks, {}ms other).",
test_count, test_count,
benchmark_count, benchmark_count,
global_timer.elapsed_milliseconds(), elapsed.to_truncated_milliseconds(),
m_testtime, m_test_time.to_truncated_milliseconds(),
m_benchtime, m_bench_time.to_truncated_milliseconds(),
global_timer.elapsed_milliseconds() - (m_testtime + m_benchtime)); (elapsed - runtime).to_truncated_milliseconds());
if (test_count != 0) { if (test_count != 0) {
if (test_passed_count == test_count) { if (test_passed_count == test_count) {

View file

@ -9,6 +9,7 @@
#include <AK/ByteString.h> #include <AK/ByteString.h>
#include <AK/Function.h> #include <AK/Function.h>
#include <AK/Time.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibTest/Macros.h> #include <LibTest/Macros.h>
#include <LibTest/Randomized/RandomnessSource.h> #include <LibTest/Randomized/RandomnessSource.h>
@ -65,8 +66,8 @@ public:
private: private:
static TestSuite* s_global; static TestSuite* s_global;
Vector<NonnullRefPtr<TestCase>> m_cases; Vector<NonnullRefPtr<TestCase>> m_cases;
u64 m_testtime = 0; AK::Duration m_test_time;
u64 m_benchtime = 0; AK::Duration m_bench_time;
ByteString m_suite_name; ByteString m_suite_name;
u64 m_benchmark_repetitions = 1; u64 m_benchmark_repetitions = 1;
u64 m_randomized_runs = 100; u64 m_randomized_runs = 100;

View file

@ -246,6 +246,18 @@ String URL::serialize_path() const
return output.to_string_without_validation(); return output.to_string_without_validation();
} }
// This function is used whenever a path is needed to access the actual file on disk.
// On Windows serialize_path can produce a path like /C:/path/to/tst.htm, so the leading slash needs to be removed to obtain a valid path.
ByteString URL::file_path() const
{
ByteString path = percent_decode(serialize_path());
#ifdef AK_OS_WINDOWS
if (path.starts_with('/'))
path = path.substring(1);
#endif
return path;
}
// https://url.spec.whatwg.org/#concept-url-serializer // https://url.spec.whatwg.org/#concept-url-serializer
String URL::serialize(ExcludeFragment exclude_fragment) const String URL::serialize(ExcludeFragment exclude_fragment) const
{ {

View file

@ -122,6 +122,7 @@ public:
} }
String serialize_path() const; String serialize_path() const;
ByteString file_path() const;
String serialize(ExcludeFragment = ExcludeFragment::No) const; String serialize(ExcludeFragment = ExcludeFragment::No) const;
ByteString serialize_for_display() const; ByteString serialize_for_display() const;
ByteString to_byte_string() const { return serialize().to_byte_string(); } ByteString to_byte_string() const { return serialize().to_byte_string(); }

View file

@ -87,15 +87,13 @@ public:
virtual Optional<size_t> previous_boundary(size_t boundary, Inclusive inclusive) override virtual Optional<size_t> previous_boundary(size_t boundary, Inclusive inclusive) override
{ {
auto icu_boundary = align_boundary(boundary); auto icu_boundary = align_boundary(boundary);
if (!icu_boundary.has_value())
return {};
if (inclusive == Inclusive::Yes) { if (inclusive == Inclusive::Yes) {
if (static_cast<bool>(m_segmenter->isBoundary(*icu_boundary))) if (static_cast<bool>(m_segmenter->isBoundary(icu_boundary)))
return static_cast<size_t>(*icu_boundary); return static_cast<size_t>(icu_boundary);
} }
if (auto index = m_segmenter->preceding(*icu_boundary); index != icu::BreakIterator::DONE) if (auto index = m_segmenter->preceding(icu_boundary); index != icu::BreakIterator::DONE)
return static_cast<size_t>(index); return static_cast<size_t>(index);
return {}; return {};
@ -104,15 +102,13 @@ public:
virtual Optional<size_t> next_boundary(size_t boundary, Inclusive inclusive) override virtual Optional<size_t> next_boundary(size_t boundary, Inclusive inclusive) override
{ {
auto icu_boundary = align_boundary(boundary); auto icu_boundary = align_boundary(boundary);
if (!icu_boundary.has_value())
return {};
if (inclusive == Inclusive::Yes) { if (inclusive == Inclusive::Yes) {
if (static_cast<bool>(m_segmenter->isBoundary(*icu_boundary))) if (static_cast<bool>(m_segmenter->isBoundary(icu_boundary)))
return static_cast<size_t>(*icu_boundary); return static_cast<size_t>(icu_boundary);
} }
if (auto index = m_segmenter->following(*icu_boundary); index != icu::BreakIterator::DONE) if (auto index = m_segmenter->following(icu_boundary); index != icu::BreakIterator::DONE)
return static_cast<size_t>(index); return static_cast<size_t>(index);
return {}; return {};
@ -177,25 +173,25 @@ public:
} }
private: private:
Optional<i32> align_boundary(size_t boundary) i32 align_boundary(size_t boundary)
{ {
auto icu_boundary = static_cast<i32>(boundary); auto icu_boundary = static_cast<i32>(boundary);
return m_segmented_text.visit( return m_segmented_text.visit(
[&](String const& text) -> Optional<i32> { [&](String const& text) {
if (boundary >= text.byte_count()) if (boundary >= text.byte_count())
return {}; return static_cast<i32>(text.byte_count());
U8_SET_CP_START(text.bytes().data(), 0, icu_boundary); U8_SET_CP_START(text.bytes().data(), 0, icu_boundary);
return icu_boundary; return icu_boundary;
}, },
[&](icu::UnicodeString const& text) -> Optional<i32> { [&](icu::UnicodeString const& text) {
if (icu_boundary >= text.length()) if (icu_boundary >= text.length())
return {}; return text.length();
return text.getChar32Start(icu_boundary); return text.getChar32Start(icu_boundary);
}, },
[](Empty) -> Optional<i32> { VERIFY_NOT_REACHED(); }); [](Empty) -> i32 { VERIFY_NOT_REACHED(); });
} }
void for_each_boundary(SegmentationCallback callback) void for_each_boundary(SegmentationCallback callback)

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h> #include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/HTMLImageElementPrototype.h> #include <LibWeb/Bindings/HTMLImageElementPrototype.h>
#include <LibWeb/Bindings/ImageConstructor.h> #include <LibWeb/Bindings/ImageConstructor.h>

View file

@ -195,6 +195,7 @@ set(SOURCES
CSS/Time.cpp CSS/Time.cpp
CSS/Transformation.cpp CSS/Transformation.cpp
CSS/TransitionEvent.cpp CSS/TransitionEvent.cpp
CSS/URL.cpp
CSS/VisualViewport.cpp CSS/VisualViewport.cpp
Cookie/Cookie.cpp Cookie/Cookie.cpp
Cookie/ParsedCookie.cpp Cookie/ParsedCookie.cpp
@ -566,6 +567,7 @@ set(SOURCES
IndexedDB/IDBVersionChangeEvent.cpp IndexedDB/IDBVersionChangeEvent.cpp
IndexedDB/Internal/Algorithms.cpp IndexedDB/Internal/Algorithms.cpp
IndexedDB/Internal/Database.cpp IndexedDB/Internal/Database.cpp
IndexedDB/Internal/Index.cpp
IndexedDB/Internal/Key.cpp IndexedDB/Internal/Key.cpp
IndexedDB/Internal/ObjectStore.cpp IndexedDB/Internal/ObjectStore.cpp
IndexedDB/Internal/RequestList.cpp IndexedDB/Internal/RequestList.cpp
@ -689,6 +691,7 @@ set(SOURCES
Painting/SVGPaintable.cpp Painting/SVGPaintable.cpp
Painting/SVGSVGPaintable.cpp Painting/SVGSVGPaintable.cpp
Painting/ScrollFrame.cpp Painting/ScrollFrame.cpp
Painting/ScrollState.cpp
Painting/ShadowPainting.cpp Painting/ShadowPainting.cpp
Painting/StackingContext.cpp Painting/StackingContext.cpp
Painting/TableBordersPainting.cpp Painting/TableBordersPainting.cpp

View file

@ -24,7 +24,7 @@ Optional<int> CSSAnimation::class_specific_composite_order(GC::Ref<Animations::A
{ {
auto other = GC::Ref { as<CSSAnimation>(*other_animation) }; auto other = GC::Ref { as<CSSAnimation>(*other_animation) };
// The existance of an owning element determines the animation class, so both animations should have their owning // The existence of an owning element determines the animation class, so both animations should have their owning
// element in the same state // element in the same state
VERIFY(!owning_element() == !other->owning_element()); VERIFY(!owning_element() == !other->owning_element());

View file

@ -9,7 +9,6 @@
#include <AK/Debug.h> #include <AK/Debug.h>
#include <AK/ScopeGuard.h> #include <AK/ScopeGuard.h>
#include <LibTextCodec/Decoder.h> #include <LibTextCodec/Decoder.h>
#include <LibURL/URL.h>
#include <LibWeb/Bindings/CSSImportRulePrototype.h> #include <LibWeb/Bindings/CSSImportRulePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h> #include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSImportRule.h> #include <LibWeb/CSS/CSSImportRule.h>
@ -17,23 +16,23 @@
#include <LibWeb/CSS/Parser/Parser.h> #include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleComputer.h> #include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Document.h>
#include <LibWeb/DOMURL/DOMURL.h>
#include <LibWeb/HTML/Window.h> #include <LibWeb/HTML/Window.h>
namespace Web::CSS { namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CSSImportRule); GC_DEFINE_ALLOCATOR(CSSImportRule);
GC::Ref<CSSImportRule> CSSImportRule::create(URL::URL url, DOM::Document& document, RefPtr<Supports> supports, Vector<NonnullRefPtr<MediaQuery>> media_query_list) GC::Ref<CSSImportRule> CSSImportRule::create(JS::Realm& realm, URL url, GC::Ptr<DOM::Document> document, RefPtr<Supports> supports, Vector<NonnullRefPtr<MediaQuery>> media_query_list)
{ {
auto& realm = document.realm(); return realm.create<CSSImportRule>(realm, move(url), document, move(supports), move(media_query_list));
return realm.create<CSSImportRule>(move(url), document, supports, move(media_query_list));
} }
CSSImportRule::CSSImportRule(URL::URL url, DOM::Document& document, RefPtr<Supports> supports, Vector<NonnullRefPtr<MediaQuery>> media_query_list) CSSImportRule::CSSImportRule(JS::Realm& realm, URL url, GC::Ptr<DOM::Document> document, RefPtr<Supports> supports, Vector<NonnullRefPtr<MediaQuery>> media_query_list)
: CSSRule(document.realm(), Type::Import) : CSSRule(realm, Type::Import)
, m_url(move(url)) , m_url(move(url))
, m_document(document) , m_document(document)
, m_supports(supports) , m_supports(move(supports))
, m_media_query_list(move(media_query_list)) , m_media_query_list(move(media_query_list))
{ {
} }
@ -57,6 +56,9 @@ void CSSImportRule::set_parent_style_sheet(CSSStyleSheet* parent_style_sheet)
// Crude detection of whether we're already fetching. // Crude detection of whether we're already fetching.
if (m_style_sheet || m_document_load_event_delayer.has_value()) if (m_style_sheet || m_document_load_event_delayer.has_value())
return; return;
// Only try to fetch if we now have a parent
if (parent_style_sheet)
fetch(); fetch();
} }
@ -70,7 +72,7 @@ String CSSImportRule::serialized() const
builder.append("@import "sv); builder.append("@import "sv);
// 2. The result of performing serialize a URL on the rules location. // 2. The result of performing serialize a URL on the rules location.
serialize_a_url(builder, m_url.to_string()); builder.append(m_url.to_string());
// AD-HOC: Serialize the rule's supports condition if it exists. // AD-HOC: Serialize the rule's supports condition if it exists.
// This isn't currently specified, but major browsers include this in their serialization of import rules // This isn't currently specified, but major browsers include this in their serialization of import rules
@ -104,15 +106,18 @@ void CSSImportRule::fetch()
// 3. Let parsedUrl be the result of the URL parser steps with rules URL and parentStylesheets location. // 3. Let parsedUrl be the result of the URL parser steps with rules URL and parentStylesheets location.
// If the algorithm returns an error, return. [CSSOM] // If the algorithm returns an error, return. [CSSOM]
// FIXME: Stop producing a URL::URL when parsing the @import auto parsed_url = DOMURL::parse(href(), parent_style_sheet.location());
auto parsed_url = url().to_string(); if (!parsed_url.has_value()) {
dbgln("Unable to parse @import url `{}` parent location `{}` as a URL.", href(), parent_style_sheet.location());
return;
}
// FIXME: Figure out the "correct" way to delay the load event. // FIXME: Figure out the "correct" way to delay the load event.
m_document_load_event_delayer.emplace(*m_document); m_document_load_event_delayer.emplace(*m_document);
// 4. Fetch a style resource from parsedUrl, with stylesheet parentStylesheet, destination "style", CORS mode "no-cors", and processResponse being the following steps given response response and byte stream, null or failure byteStream: // 4. Fetch a style resource from parsedUrl, with stylesheet parentStylesheet, destination "style", CORS mode "no-cors", and processResponse being the following steps given response response and byte stream, null or failure byteStream:
fetch_a_style_resource(parsed_url, parent_style_sheet, Fetch::Infrastructure::Request::Destination::Style, CorsMode::NoCors, fetch_a_style_resource(parsed_url->to_string(), parent_style_sheet, Fetch::Infrastructure::Request::Destination::Style, CorsMode::NoCors,
[strong_this = GC::Ref { *this }, parent_style_sheet = GC::Ref { parent_style_sheet }](auto response, auto maybe_byte_stream) { [strong_this = GC::Ref { *this }, parent_style_sheet = GC::Ref { parent_style_sheet }, parsed_url = parsed_url.value()](auto response, auto maybe_byte_stream) {
// AD-HOC: Stop delaying the load event. // AD-HOC: Stop delaying the load event.
ScopeGuard guard = [strong_this] { ScopeGuard guard = [strong_this] {
strong_this->m_document_load_event_delayer.clear(); strong_this->m_document_load_event_delayer.clear();
@ -139,19 +144,19 @@ void CSSImportRule::fetch()
auto encoding = "utf-8"sv; auto encoding = "utf-8"sv;
auto maybe_decoder = TextCodec::decoder_for(encoding); auto maybe_decoder = TextCodec::decoder_for(encoding);
if (!maybe_decoder.has_value()) { if (!maybe_decoder.has_value()) {
dbgln_if(CSS_LOADER_DEBUG, "CSSImportRule: Failed to decode CSS file: {} Unsupported encoding: {}", strong_this->url(), encoding); dbgln_if(CSS_LOADER_DEBUG, "CSSImportRule: Failed to decode CSS file: {} Unsupported encoding: {}", parsed_url, encoding);
return; return;
} }
auto& decoder = maybe_decoder.release_value(); auto& decoder = maybe_decoder.release_value();
auto decoded_or_error = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(decoder, *byte_stream); auto decoded_or_error = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(decoder, *byte_stream);
if (decoded_or_error.is_error()) { if (decoded_or_error.is_error()) {
dbgln_if(CSS_LOADER_DEBUG, "CSSImportRule: Failed to decode CSS file: {} Encoding was: {}", strong_this->url(), encoding); dbgln_if(CSS_LOADER_DEBUG, "CSSImportRule: Failed to decode CSS file: {} Encoding was: {}", parsed_url, encoding);
return; return;
} }
auto decoded = decoded_or_error.release_value(); auto decoded = decoded_or_error.release_value();
auto* imported_style_sheet = parse_css_stylesheet(Parser::ParsingParams(*strong_this->m_document, strong_this->url()), decoded, strong_this->url(), strong_this->m_media_query_list); auto* imported_style_sheet = parse_css_stylesheet(Parser::ParsingParams(*strong_this->m_document, parsed_url), decoded, parsed_url, strong_this->m_media_query_list);
// 5. Set importedStylesheets origin-clean flag to parentStylesheets origin-clean flag. // 5. Set importedStylesheets origin-clean flag to parentStylesheets origin-clean flag.
imported_style_sheet->set_origin_clean(parent_style_sheet->is_origin_clean()); imported_style_sheet->set_origin_clean(parent_style_sheet->is_origin_clean());

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2021, the SerenityOS developers. * Copyright (c) 2021, the SerenityOS developers.
* Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org> * Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
@ -8,9 +8,9 @@
#pragma once #pragma once
#include <LibURL/URL.h>
#include <LibWeb/CSS/CSSRule.h> #include <LibWeb/CSS/CSSRule.h>
#include <LibWeb/CSS/CSSStyleSheet.h> #include <LibWeb/CSS/CSSStyleSheet.h>
#include <LibWeb/CSS/URL.h>
#include <LibWeb/DOM/DocumentLoadEventDelayer.h> #include <LibWeb/DOM/DocumentLoadEventDelayer.h>
namespace Web::CSS { namespace Web::CSS {
@ -21,13 +21,12 @@ class CSSImportRule final
GC_DECLARE_ALLOCATOR(CSSImportRule); GC_DECLARE_ALLOCATOR(CSSImportRule);
public: public:
[[nodiscard]] static GC::Ref<CSSImportRule> create(URL::URL, DOM::Document&, RefPtr<Supports>, Vector<NonnullRefPtr<MediaQuery>>); [[nodiscard]] static GC::Ref<CSSImportRule> create(JS::Realm&, URL, GC::Ptr<DOM::Document>, RefPtr<Supports>, Vector<NonnullRefPtr<MediaQuery>>);
virtual ~CSSImportRule() = default; virtual ~CSSImportRule() = default;
URL::URL const& url() const { return m_url; } URL const& url() const { return m_url; }
// FIXME: This should return only the specified part of the url. eg, "stuff/foo.css", not "https://example.com/stuff/foo.css". String href() const { return m_url.url(); }
String href() const { return m_url.to_string(); }
CSSStyleSheet* loaded_style_sheet() { return m_style_sheet; } CSSStyleSheet* loaded_style_sheet() { return m_style_sheet; }
CSSStyleSheet const* loaded_style_sheet() const { return m_style_sheet; } CSSStyleSheet const* loaded_style_sheet() const { return m_style_sheet; }
@ -37,7 +36,7 @@ public:
Optional<String> supports_text() const; Optional<String> supports_text() const;
private: private:
CSSImportRule(URL::URL, DOM::Document&, RefPtr<Supports>, Vector<NonnullRefPtr<MediaQuery>>); CSSImportRule(JS::Realm&, URL, GC::Ptr<DOM::Document>, RefPtr<Supports>, Vector<NonnullRefPtr<MediaQuery>>);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
@ -49,7 +48,7 @@ private:
void fetch(); void fetch();
void set_style_sheet(GC::Ref<CSSStyleSheet>); void set_style_sheet(GC::Ref<CSSStyleSheet>);
URL::URL m_url; URL m_url;
GC::Ptr<DOM::Document> m_document; GC::Ptr<DOM::Document> m_document;
RefPtr<Supports> m_supports; RefPtr<Supports> m_supports;
Vector<NonnullRefPtr<MediaQuery>> m_media_query_list; Vector<NonnullRefPtr<MediaQuery>> m_media_query_list;

View file

@ -6,7 +6,8 @@
[Exposed=Window] [Exposed=Window]
interface CSSImportRule : CSSRule { interface CSSImportRule : CSSRule {
readonly attribute USVString href; readonly attribute USVString href;
[SameObject, PutForwards=mediaText] readonly attribute MediaList media; // AD-HOC: media is null if styleSheet is null. Spec issue: https://github.com/w3c/csswg-drafts/issues/12063
[SameObject, PutForwards=mediaText] readonly attribute MediaList? media;
[SameObject, ImplementedAs=style_sheet_for_bindings] readonly attribute CSSStyleSheet? styleSheet; [SameObject, ImplementedAs=style_sheet_for_bindings] readonly attribute CSSStyleSheet? styleSheet;
[FIXME] readonly attribute CSSOMString? layerName; [FIXME] readonly attribute CSSOMString? layerName;
readonly attribute CSSOMString? supportsText; readonly attribute CSSOMString? supportsText;

View file

@ -1158,6 +1158,13 @@ WebIDL::ExceptionOr<void> CSSStyleProperties::set_css_text(StringView css_text)
// 4. Update style attribute for the CSS declaration block. // 4. Update style attribute for the CSS declaration block.
update_style_attribute(); update_style_attribute();
// Non-standard: Invalidate style for the owners of our containing sheet, if any.
if (auto rule = parent_rule()) {
if (auto sheet = rule->parent_style_sheet()) {
sheet->invalidate_owners(DOM::StyleInvalidationReason::CSSStylePropertiesTextChange);
}
}
return {}; return {};
} }

View file

@ -24,7 +24,7 @@ namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CSSStyleSheet); GC_DEFINE_ALLOCATOR(CSSStyleSheet);
GC::Ref<CSSStyleSheet> CSSStyleSheet::create(JS::Realm& realm, CSSRuleList& rules, MediaList& media, Optional<URL::URL> location) GC::Ref<CSSStyleSheet> CSSStyleSheet::create(JS::Realm& realm, CSSRuleList& rules, MediaList& media, Optional<::URL::URL> location)
{ {
return realm.create<CSSStyleSheet>(realm, rules, media, move(location)); return realm.create<CSSStyleSheet>(realm, rules, media, move(location));
} }
@ -37,16 +37,16 @@ WebIDL::ExceptionOr<GC::Ref<CSSStyleSheet>> CSSStyleSheet::construct_impl(JS::Re
// 2. Set sheets location to the base URL of the associated Document for the current principal global object. // 2. Set sheets location to the base URL of the associated Document for the current principal global object.
auto associated_document = as<HTML::Window>(HTML::current_principal_global_object()).document(); auto associated_document = as<HTML::Window>(HTML::current_principal_global_object()).document();
sheet->set_location(associated_document->base_url().to_string()); sheet->set_location(associated_document->base_url());
// 3. Set sheets stylesheet base URL to the baseURL attribute value from options. // 3. Set sheets stylesheet base URL to the baseURL attribute value from options.
if (options.has_value() && options->base_url.has_value()) { if (options.has_value() && options->base_url.has_value()) {
Optional<URL::URL> sheet_location_url; Optional<::URL::URL> sheet_location_url;
if (sheet->location().has_value()) if (sheet->location().has_value())
sheet_location_url = URL::Parser::basic_parse(sheet->location().release_value()); sheet_location_url = sheet->location().release_value();
// AD-HOC: This isn't explicitly mentioned in the specification, but multiple modern browsers do this. // AD-HOC: This isn't explicitly mentioned in the specification, but multiple modern browsers do this.
Optional<URL::URL> url = sheet->location().has_value() ? sheet_location_url->complete_url(options->base_url.value()) : URL::Parser::basic_parse(options->base_url.value()); Optional<::URL::URL> url = sheet->location().has_value() ? sheet_location_url->complete_url(options->base_url.value()) : ::URL::Parser::basic_parse(options->base_url.value());
if (!url.has_value()) if (!url.has_value())
return WebIDL::NotAllowedError::create(realm, "Constructed style sheets must have a valid base URL"_string); return WebIDL::NotAllowedError::create(realm, "Constructed style sheets must have a valid base URL"_string);
@ -95,12 +95,12 @@ WebIDL::ExceptionOr<GC::Ref<CSSStyleSheet>> CSSStyleSheet::construct_impl(JS::Re
return sheet; return sheet;
} }
CSSStyleSheet::CSSStyleSheet(JS::Realm& realm, CSSRuleList& rules, MediaList& media, Optional<URL::URL> location) CSSStyleSheet::CSSStyleSheet(JS::Realm& realm, CSSRuleList& rules, MediaList& media, Optional<::URL::URL> location)
: StyleSheet(realm, media) : StyleSheet(realm, media)
, m_rules(&rules) , m_rules(&rules)
{ {
if (location.has_value()) if (location.has_value())
set_location(location->to_string()); set_location(move(location));
for (auto& rule : *m_rules) for (auto& rule : *m_rules)
rule->set_parent_style_sheet(this); rule->set_parent_style_sheet(this);
@ -140,8 +140,7 @@ WebIDL::ExceptionOr<unsigned> CSSStyleSheet::insert_rule(StringView rule, unsign
return WebIDL::NotAllowedError::create(realm(), "Can't call insert_rule() on non-modifiable stylesheets."_string); return WebIDL::NotAllowedError::create(realm(), "Can't call insert_rule() on non-modifiable stylesheets."_string);
// 3. Let parsed rule be the return value of invoking parse a rule with rule. // 3. Let parsed rule be the return value of invoking parse a rule with rule.
auto context = !m_owning_documents_or_shadow_roots.is_empty() ? Parser::ParsingParams { (*m_owning_documents_or_shadow_roots.begin())->document() } : Parser::ParsingParams { realm() }; auto parsed_rule = parse_css_rule(make_parsing_params(), rule);
auto parsed_rule = parse_css_rule(context, rule);
// 4. If parsed rule is a syntax error, return parsed rule. // 4. If parsed rule is a syntax error, return parsed rule.
if (!parsed_rule) if (!parsed_rule)
@ -208,8 +207,7 @@ GC::Ref<WebIDL::Promise> CSSStyleSheet::replace(String text)
HTML::TemporaryExecutionContext execution_context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; HTML::TemporaryExecutionContext execution_context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
// 1. Let rules be the result of running parse a stylesheets contents from text. // 1. Let rules be the result of running parse a stylesheets contents from text.
auto context = !m_owning_documents_or_shadow_roots.is_empty() ? Parser::ParsingParams { (*m_owning_documents_or_shadow_roots.begin())->document() } : CSS::Parser::ParsingParams { realm }; auto* parsed_stylesheet = parse_css_stylesheet(make_parsing_params(), text);
auto* parsed_stylesheet = parse_css_stylesheet(context, text);
auto& rules = parsed_stylesheet->rules(); auto& rules = parsed_stylesheet->rules();
// 2. If rules contains one or more @import rules, remove those rules from rules. // 2. If rules contains one or more @import rules, remove those rules from rules.
@ -242,8 +240,7 @@ WebIDL::ExceptionOr<void> CSSStyleSheet::replace_sync(StringView text)
return WebIDL::NotAllowedError::create(realm(), "Can't call replaceSync() on non-modifiable stylesheets"_string); return WebIDL::NotAllowedError::create(realm(), "Can't call replaceSync() on non-modifiable stylesheets"_string);
// 2. Let rules be the result of running parse a stylesheets contents from text. // 2. Let rules be the result of running parse a stylesheets contents from text.
auto context = !m_owning_documents_or_shadow_roots.is_empty() ? Parser::ParsingParams { (*m_owning_documents_or_shadow_roots.begin())->document() } : CSS::Parser::ParsingParams { realm() }; auto* parsed_stylesheet = parse_css_stylesheet(make_parsing_params(), text);
auto* parsed_stylesheet = parse_css_stylesheet(context, text);
auto& rules = parsed_stylesheet->rules(); auto& rules = parsed_stylesheet->rules();
// 3. If rules contains one or more @import rules, remove those rules from rules. // 3. If rules contains one or more @import rules, remove those rules from rules.
@ -426,4 +423,11 @@ bool CSSStyleSheet::has_associated_font_loader(FontLoader& font_loader) const
return false; return false;
} }
Parser::ParsingParams CSSStyleSheet::make_parsing_params() const
{
if (!m_owning_documents_or_shadow_roots.is_empty())
return Parser::ParsingParams { (*m_owning_documents_or_shadow_roots.begin())->document() };
return Parser::ParsingParams { realm() };
}
} }

View file

@ -12,6 +12,7 @@
#include <LibWeb/CSS/CSSRule.h> #include <LibWeb/CSS/CSSRule.h>
#include <LibWeb/CSS/CSSRuleList.h> #include <LibWeb/CSS/CSSRuleList.h>
#include <LibWeb/CSS/CSSStyleRule.h> #include <LibWeb/CSS/CSSStyleRule.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleSheet.h> #include <LibWeb/CSS/StyleSheet.h>
#include <LibWeb/DOM/Node.h> #include <LibWeb/DOM/Node.h>
#include <LibWeb/WebIDL/Types.h> #include <LibWeb/WebIDL/Types.h>
@ -27,12 +28,13 @@ struct CSSStyleSheetInit {
bool disabled { false }; bool disabled { false };
}; };
// https://drafts.csswg.org/cssom-1/#cssstylesheet
class CSSStyleSheet final : public StyleSheet { class CSSStyleSheet final : public StyleSheet {
WEB_PLATFORM_OBJECT(CSSStyleSheet, StyleSheet); WEB_PLATFORM_OBJECT(CSSStyleSheet, StyleSheet);
GC_DECLARE_ALLOCATOR(CSSStyleSheet); GC_DECLARE_ALLOCATOR(CSSStyleSheet);
public: public:
[[nodiscard]] static GC::Ref<CSSStyleSheet> create(JS::Realm&, CSSRuleList&, MediaList&, Optional<URL::URL> location); [[nodiscard]] static GC::Ref<CSSStyleSheet> create(JS::Realm&, CSSRuleList&, MediaList&, Optional<::URL::URL> location);
static WebIDL::ExceptionOr<GC::Ref<CSSStyleSheet>> construct_impl(JS::Realm&, Optional<CSSStyleSheetInit> const& options = {}); static WebIDL::ExceptionOr<GC::Ref<CSSStyleSheet>> construct_impl(JS::Realm&, Optional<CSSStyleSheetInit> const& options = {});
virtual ~CSSStyleSheet() override = default; virtual ~CSSStyleSheet() override = default;
@ -74,8 +76,8 @@ public:
Vector<GC::Ref<CSSImportRule>> const& import_rules() const { return m_import_rules; } Vector<GC::Ref<CSSImportRule>> const& import_rules() const { return m_import_rules; }
Optional<URL::URL> base_url() const { return m_base_url; } Optional<::URL::URL> base_url() const { return m_base_url; }
void set_base_url(Optional<URL::URL> base_url) { m_base_url = move(base_url); } void set_base_url(Optional<::URL::URL> base_url) { m_base_url = move(base_url); }
bool constructed() const { return m_constructed; } bool constructed() const { return m_constructed; }
@ -94,7 +96,7 @@ public:
bool has_associated_font_loader(FontLoader& font_loader) const; bool has_associated_font_loader(FontLoader& font_loader) const;
private: private:
CSSStyleSheet(JS::Realm&, CSSRuleList&, MediaList&, Optional<URL::URL> location); CSSStyleSheet(JS::Realm&, CSSRuleList&, MediaList&, Optional<::URL::URL> location);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
@ -104,6 +106,8 @@ private:
void set_constructed(bool constructed) { m_constructed = constructed; } void set_constructed(bool constructed) { m_constructed = constructed; }
void set_disallow_modification(bool disallow_modification) { m_disallow_modification = disallow_modification; } void set_disallow_modification(bool disallow_modification) { m_disallow_modification = disallow_modification; }
Parser::ParsingParams make_parsing_params() const;
Optional<String> m_source_text; Optional<String> m_source_text;
GC::Ptr<CSSRuleList> m_rules; GC::Ptr<CSSRuleList> m_rules;
@ -113,7 +117,7 @@ private:
GC::Ptr<CSSRule> m_owner_css_rule; GC::Ptr<CSSRule> m_owner_css_rule;
Optional<URL::URL> m_base_url; Optional<::URL::URL> m_base_url;
GC::Ptr<DOM::Document const> m_constructor_document; GC::Ptr<DOM::Document const> m_constructor_document;
HashTable<GC::Ptr<DOM::Node>> m_owning_documents_or_shadow_roots; HashTable<GC::Ptr<DOM::Node>> m_owning_documents_or_shadow_roots;
bool m_constructed { false }; bool m_constructed { false };

View file

@ -225,40 +225,40 @@ public:
: m_value(color) : m_value(color)
{ {
} }
SVGPaint(URL::URL const& url) SVGPaint(::URL::URL const& url)
: m_value(url) : m_value(url)
{ {
} }
bool is_color() const { return m_value.has<Color>(); } bool is_color() const { return m_value.has<Color>(); }
bool is_url() const { return m_value.has<URL::URL>(); } bool is_url() const { return m_value.has<::URL::URL>(); }
Color as_color() const { return m_value.get<Color>(); } Color as_color() const { return m_value.get<Color>(); }
URL::URL const& as_url() const { return m_value.get<URL::URL>(); } ::URL::URL const& as_url() const { return m_value.get<::URL::URL>(); }
private: private:
Variant<URL::URL, Color> m_value; Variant<::URL::URL, Color> m_value;
}; };
// https://drafts.fxtf.org/css-masking-1/#typedef-mask-reference // https://drafts.fxtf.org/css-masking-1/#typedef-mask-reference
class MaskReference { class MaskReference {
public: public:
// TODO: Support other mask types. // TODO: Support other mask types.
MaskReference(URL::URL const& url) MaskReference(::URL::URL const& url)
: m_url(url) : m_url(url)
{ {
} }
URL::URL const& url() const { return m_url; } ::URL::URL const& url() const { return m_url; }
private: private:
URL::URL m_url; ::URL::URL m_url;
}; };
// https://drafts.fxtf.org/css-masking/#the-clip-path // https://drafts.fxtf.org/css-masking/#the-clip-path
// TODO: Support clip sources. // TODO: Support clip sources.
class ClipPathReference { class ClipPathReference {
public: public:
ClipPathReference(URL::URL const& url) ClipPathReference(::URL::URL const& url)
: m_clip_source(url) : m_clip_source(url)
{ {
} }
@ -270,16 +270,16 @@ public:
bool is_basic_shape() const { return m_clip_source.has<BasicShape>(); } bool is_basic_shape() const { return m_clip_source.has<BasicShape>(); }
bool is_url() const { return m_clip_source.has<URL::URL>(); } bool is_url() const { return m_clip_source.has<::URL::URL>(); }
URL::URL const& url() const { return m_clip_source.get<URL::URL>(); } ::URL::URL const& url() const { return m_clip_source.get<::URL::URL>(); }
BasicShapeStyleValue const& basic_shape() const { return *m_clip_source.get<BasicShape>(); } BasicShapeStyleValue const& basic_shape() const { return *m_clip_source.get<BasicShape>(); }
private: private:
using BasicShape = NonnullRefPtr<BasicShapeStyleValue const>; using BasicShape = NonnullRefPtr<BasicShapeStyleValue const>;
Variant<URL::URL, BasicShape> m_clip_source; Variant<::URL::URL, BasicShape> m_clip_source;
}; };
struct BackgroundLayerData { struct BackgroundLayerData {

View file

@ -79,7 +79,7 @@ input[type=range] {
width: 20ch; width: 20ch;
height: 16px; height: 16px;
&::track { &::slider-track {
display: block; display: block;
position: relative; position: relative;
height: 4px; height: 4px;
@ -89,14 +89,14 @@ input[type=range] {
border: 1px solid rgba(0, 0, 0, 0.5); border: 1px solid rgba(0, 0, 0, 0.5);
} }
&::fill { &::slider-fill {
display: block; display: block;
position: absolute; position: absolute;
height: 100%; height: 100%;
background-color: AccentColor; background-color: AccentColor;
} }
&::thumb { &::slider-thumb {
display: block; display: block;
margin-top: -6px; margin-top: -6px;
width: 16px; width: 16px;
@ -115,27 +115,27 @@ meter {
width: 300px; width: 300px;
height: 12px; height: 12px;
&::track { &::slider-track {
display: block; display: block;
height: 100%; height: 100%;
background-color: hsl(0, 0%, 96%); background-color: hsl(0, 0%, 96%);
border: 1px solid rgba(0, 0, 0, 0.5); border: 1px solid rgba(0, 0, 0, 0.5);
} }
&::fill { &::slider-fill {
display: block; display: block;
height: 100%; height: 100%;
} }
&:optimal-value::fill { &:optimal-value::slider-fill {
background-color: hsl(141, 53%, 53%); background-color: hsl(141, 53%, 53%);
} }
&:suboptimal-value::fill { &:suboptimal-value::slider-fill {
background-color: hsl(48, 100%, 67%); background-color: hsl(48, 100%, 67%);
} }
&:even-less-good-value::fill { &:even-less-good-value::slider-fill {
background-color: hsl(348, 100%, 61%); background-color: hsl(348, 100%, 61%);
} }
} }
@ -146,14 +146,14 @@ progress {
width: 300px; width: 300px;
height: 12px; height: 12px;
&::track { &::slider-track {
display: block; display: block;
height: 100%; height: 100%;
background-color: AccentColorText; background-color: AccentColorText;
border: 1px solid rgba(0, 0, 0, 0.5); border: 1px solid rgba(0, 0, 0, 0.5);
} }
&::fill { &::slider-fill {
display: block; display: block;
height: 100%; height: 100%;
background-color: AccentColor; background-color: AccentColor;

View file

@ -23,7 +23,7 @@ void fetch_a_style_resource(String const& url_value, CSSStyleSheet const& sheet,
auto base = sheet.base_url().value_or(environment_settings.api_base_url()); auto base = sheet.base_url().value_or(environment_settings.api_base_url());
// 3. Let parsedUrl be the result of the URL parser steps with urlValues url and base. If the algorithm returns an error, return. // 3. Let parsedUrl be the result of the URL parser steps with urlValues url and base. If the algorithm returns an error, return.
auto parsed_url = URL::Parser::basic_parse(url_value, base); auto parsed_url = ::URL::Parser::basic_parse(url_value, base);
if (!parsed_url.has_value()) if (!parsed_url.has_value())
return; return;

View file

@ -586,8 +586,13 @@ NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, Calc
layout_node = *node; layout_node = *node;
return CSSColorValue::create_from_color(interpolate_color(from.to_color(layout_node), to.to_color(layout_node), delta), ColorSyntax::Modern); return CSSColorValue::create_from_color(interpolate_color(from.to_color(layout_node), to.to_color(layout_node), delta), ColorSyntax::Modern);
} }
case CSSStyleValue::Type::Integer: case CSSStyleValue::Type::Integer: {
return IntegerStyleValue::create(interpolate_raw(from.as_integer().integer(), to.as_integer().integer(), delta)); // https://drafts.csswg.org/css-values/#combine-integers
// Interpolation of <integer> is defined as Vresult = round((1 - p) × VA + p × VB);
// that is, interpolation happens in the real number space as for <number>s, and the result is converted to an <integer> by rounding to the nearest integer.
auto interpolated_value = interpolate_raw(from.as_integer().value(), to.as_integer().value(), delta);
return IntegerStyleValue::create(round_to<i64>(interpolated_value));
}
case CSSStyleValue::Type::Length: { case CSSStyleValue::Type::Length: {
// FIXME: Absolutize values // FIXME: Absolutize values
auto const& from_length = from.as_length().length(); auto const& from_length = from.as_length().length();
@ -611,6 +616,11 @@ NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, Calc
auto from_ratio = from.as_ratio().ratio(); auto from_ratio = from.as_ratio().ratio();
auto to_ratio = to.as_ratio().ratio(); auto to_ratio = to.as_ratio().ratio();
// https://drafts.csswg.org/css-values/#combine-ratio
// If either <ratio> is degenerate, the values cannot be interpolated.
if (from_ratio.is_degenerate() || to_ratio.is_degenerate())
return delta >= 0.5f ? to : from;
// The interpolation of a <ratio> is defined by converting each <ratio> to a number by dividing the first value // The interpolation of a <ratio> is defined by converting each <ratio> to a number by dividing the first value
// by the second (so a ratio of 3 / 2 would become 1.5), taking the logarithm of that result (so the 1.5 would // by the second (so a ratio of 3 / 2 would become 1.5), taking the logarithm of that result (so the 1.5 would
// become approximately 0.176), then interpolating those values. The result during the interpolation is // become approximately 0.176), then interpolating those values. The result during the interpolation is

View file

@ -38,7 +38,7 @@ Vector<ParsedFontFace::Source> ParsedFontFace::sources_from_style_value(CSSStyle
[&](FontSourceStyleValue::Local const& local) { [&](FontSourceStyleValue::Local const& local) {
sources.empend(extract_font_name(local.name), OptionalNone {}); sources.empend(extract_font_name(local.name), OptionalNone {});
}, },
[&](URL::URL const& url) { [&](::URL::URL const& url) {
// FIXME: tech() // FIXME: tech()
sources.empend(url, font_source.format()); sources.empend(url, font_source.format());
}); });

View file

@ -19,7 +19,7 @@ namespace Web::CSS {
class ParsedFontFace { class ParsedFontFace {
public: public:
struct Source { struct Source {
Variant<FlyString, URL::URL> local_or_url; Variant<FlyString, ::URL::URL> local_or_url;
// FIXME: Do we need to keep this around, or is it only needed to discard unwanted formats during parsing? // FIXME: Do we need to keep this around, or is it only needed to discard unwanted formats during parsing?
Optional<FlyString> format; Optional<FlyString> format;
}; };

View file

@ -42,7 +42,7 @@ GC::Ref<JS::Realm> internal_css_realm()
return *realm; return *realm;
} }
CSS::CSSStyleSheet* parse_css_stylesheet(CSS::Parser::ParsingParams const& context, StringView css, Optional<URL::URL> location, Vector<NonnullRefPtr<CSS::MediaQuery>> media_query_list) CSS::CSSStyleSheet* parse_css_stylesheet(CSS::Parser::ParsingParams const& context, StringView css, Optional<::URL::URL> location, Vector<NonnullRefPtr<CSS::MediaQuery>> media_query_list)
{ {
if (css.is_empty()) { if (css.is_empty()) {
auto rule_list = CSS::CSSRuleList::create_empty(*context.realm); auto rule_list = CSS::CSSRuleList::create_empty(*context.realm);

View file

@ -45,14 +45,14 @@ ParsingParams::ParsingParams(JS::Realm& realm, ParsingMode mode)
{ {
} }
ParsingParams::ParsingParams(JS::Realm& realm, URL::URL url, ParsingMode mode) ParsingParams::ParsingParams(JS::Realm& realm, ::URL::URL url, ParsingMode mode)
: realm(realm) : realm(realm)
, url(move(url)) , url(move(url))
, mode(mode) , mode(mode)
{ {
} }
ParsingParams::ParsingParams(DOM::Document const& document, URL::URL url, ParsingMode mode) ParsingParams::ParsingParams(DOM::Document const& document, ::URL::URL url, ParsingMode mode)
: realm(const_cast<JS::Realm&>(document.realm())) : realm(const_cast<JS::Realm&>(document.realm()))
, document(&document) , document(&document)
, url(move(url)) , url(move(url))
@ -86,7 +86,7 @@ Parser::Parser(ParsingParams const& context, Vector<Token> tokens)
// https://drafts.csswg.org/css-syntax/#parse-stylesheet // https://drafts.csswg.org/css-syntax/#parse-stylesheet
template<typename T> template<typename T>
Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<T>& input, Optional<URL::URL> location) Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<T>& input, Optional<::URL::URL> location)
{ {
// To parse a stylesheet from an input given an optional url location: // To parse a stylesheet from an input given an optional url location:
@ -119,10 +119,10 @@ Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<T>& input)
} }
// https://drafts.csswg.org/css-syntax/#parse-a-css-stylesheet // https://drafts.csswg.org/css-syntax/#parse-a-css-stylesheet
CSSStyleSheet* Parser::parse_as_css_stylesheet(Optional<URL::URL> location, Vector<NonnullRefPtr<MediaQuery>> media_query_list) CSSStyleSheet* Parser::parse_as_css_stylesheet(Optional<::URL::URL> location, Vector<NonnullRefPtr<MediaQuery>> media_query_list)
{ {
// To parse a CSS stylesheet, first parse a stylesheet. // To parse a CSS stylesheet, first parse a stylesheet.
auto const& style_sheet = parse_a_stylesheet(m_token_stream, {}); auto const& style_sheet = parse_a_stylesheet(m_token_stream, location);
// Interpret all of the resulting top-level qualified rules as style rules, defined below. // Interpret all of the resulting top-level qualified rules as style rules, defined below.
GC::RootVector<CSSRule*> rules(realm().heap()); GC::RootVector<CSSRule*> rules(realm().heap());
@ -1772,8 +1772,8 @@ Parser::ContextType Parser::context_type_for_at_rule(FlyString const& name)
return ContextType::Unknown; return ContextType::Unknown;
} }
template Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<Token>&, Optional<URL::URL>); template Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<Token>&, Optional<::URL::URL>);
template Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<ComponentValue>&, Optional<URL::URL>); template Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<ComponentValue>&, Optional<::URL::URL>);
template Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<Token>& input); template Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<Token>& input);
template Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<ComponentValue>& input); template Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<ComponentValue>& input);
@ -1853,10 +1853,10 @@ bool Parser::is_parsing_svg_presentation_attribute() const
// https://www.w3.org/TR/css-values-4/#relative-urls // https://www.w3.org/TR/css-values-4/#relative-urls
// FIXME: URLs shouldn't be completed during parsing, but when used. // FIXME: URLs shouldn't be completed during parsing, but when used.
Optional<URL::URL> Parser::complete_url(StringView relative_url) const Optional<::URL::URL> Parser::complete_url(StringView relative_url) const
{ {
if (!m_url.is_valid()) if (!m_url.is_valid())
return URL::Parser::basic_parse(relative_url); return ::URL::Parser::basic_parse(relative_url);
return m_url.complete_url(relative_url); return m_url.complete_url(relative_url);
} }

View file

@ -32,6 +32,7 @@
#include <LibWeb/CSS/StyleValues/BasicShapeStyleValue.h> #include <LibWeb/CSS/StyleValues/BasicShapeStyleValue.h>
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h> #include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
#include <LibWeb/CSS/Supports.h> #include <LibWeb/CSS/Supports.h>
#include <LibWeb/CSS/URL.h>
#include <LibWeb/Forward.h> #include <LibWeb/Forward.h>
namespace Web::CSS::Parser { namespace Web::CSS::Parser {
@ -69,13 +70,13 @@ enum class ParsingMode {
struct ParsingParams { struct ParsingParams {
explicit ParsingParams(ParsingMode = ParsingMode::Normal); explicit ParsingParams(ParsingMode = ParsingMode::Normal);
explicit ParsingParams(JS::Realm&, ParsingMode = ParsingMode::Normal); explicit ParsingParams(JS::Realm&, ParsingMode = ParsingMode::Normal);
explicit ParsingParams(JS::Realm&, URL::URL, ParsingMode = ParsingMode::Normal); explicit ParsingParams(JS::Realm&, ::URL::URL, ParsingMode = ParsingMode::Normal);
explicit ParsingParams(DOM::Document const&, URL::URL, ParsingMode = ParsingMode::Normal); explicit ParsingParams(DOM::Document const&, ::URL::URL, ParsingMode = ParsingMode::Normal);
explicit ParsingParams(DOM::Document const&, ParsingMode = ParsingMode::Normal); explicit ParsingParams(DOM::Document const&, ParsingMode = ParsingMode::Normal);
GC::Ptr<JS::Realm> realm; GC::Ptr<JS::Realm> realm;
GC::Ptr<DOM::Document const> document; GC::Ptr<DOM::Document const> document;
URL::URL url; ::URL::URL url;
ParsingMode mode { ParsingMode::Normal }; ParsingMode mode { ParsingMode::Normal };
}; };
@ -89,7 +90,7 @@ class Parser {
public: public:
static Parser create(ParsingParams const&, StringView input, StringView encoding = "utf-8"sv); static Parser create(ParsingParams const&, StringView input, StringView encoding = "utf-8"sv);
CSSStyleSheet* parse_as_css_stylesheet(Optional<URL::URL> location, Vector<NonnullRefPtr<MediaQuery>> media_query_list = {}); CSSStyleSheet* parse_as_css_stylesheet(Optional<::URL::URL> location, Vector<NonnullRefPtr<MediaQuery>> media_query_list = {});
struct PropertiesAndCustomProperties { struct PropertiesAndCustomProperties {
Vector<StyleProperty> properties; Vector<StyleProperty> properties;
@ -142,11 +143,11 @@ private:
// "Parse a stylesheet" is intended to be the normal parser entry point, for parsing stylesheets. // "Parse a stylesheet" is intended to be the normal parser entry point, for parsing stylesheets.
struct ParsedStyleSheet { struct ParsedStyleSheet {
Optional<URL::URL> location; Optional<::URL::URL> location;
Vector<Rule> rules; Vector<Rule> rules;
}; };
template<typename T> template<typename T>
ParsedStyleSheet parse_a_stylesheet(TokenStream<T>&, Optional<URL::URL> location); ParsedStyleSheet parse_a_stylesheet(TokenStream<T>&, Optional<::URL::URL> location);
// "Parse a stylesheets contents" is intended for use by the CSSStyleSheet replace() method, and similar, which parse text into the contents of an existing stylesheet. // "Parse a stylesheets contents" is intended for use by the CSSStyleSheet replace() method, and similar, which parse text into the contents of an existing stylesheet.
template<typename T> template<typename T>
@ -276,7 +277,7 @@ private:
Optional<GridRepeat> parse_repeat(Vector<ComponentValue> const&); Optional<GridRepeat> parse_repeat(Vector<ComponentValue> const&);
Optional<ExplicitGridTrack> parse_track_sizing_function(ComponentValue const&); Optional<ExplicitGridTrack> parse_track_sizing_function(ComponentValue const&);
Optional<URL::URL> parse_url_function(TokenStream<ComponentValue>&); Optional<URL> parse_url_function(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_url_value(TokenStream<ComponentValue>&); RefPtr<CSSStyleValue> parse_url_value(TokenStream<ComponentValue>&);
Optional<ShapeRadius> parse_shape_radius(TokenStream<ComponentValue>&); Optional<ShapeRadius> parse_shape_radius(TokenStream<ComponentValue>&);
@ -471,11 +472,11 @@ private:
JS::Realm& realm() const; JS::Realm& realm() const;
bool in_quirks_mode() const; bool in_quirks_mode() const;
bool is_parsing_svg_presentation_attribute() const; bool is_parsing_svg_presentation_attribute() const;
Optional<URL::URL> complete_url(StringView) const; Optional<::URL::URL> complete_url(StringView) const;
GC::Ptr<DOM::Document const> m_document; GC::Ptr<DOM::Document const> m_document;
GC::Ptr<JS::Realm> m_realm; GC::Ptr<JS::Realm> m_realm;
URL::URL m_url; ::URL::URL m_url;
ParsingMode m_parsing_mode { ParsingMode::Normal }; ParsingMode m_parsing_mode { ParsingMode::Normal };
Vector<Token> m_tokens; Vector<Token> m_tokens;
@ -519,7 +520,7 @@ private:
namespace Web { namespace Web {
CSS::CSSStyleSheet* parse_css_stylesheet(CSS::Parser::ParsingParams const&, StringView, Optional<URL::URL> location = {}, Vector<NonnullRefPtr<CSS::MediaQuery>> = {}); CSS::CSSStyleSheet* parse_css_stylesheet(CSS::Parser::ParsingParams const&, StringView, Optional<::URL::URL> location = {}, Vector<NonnullRefPtr<CSS::MediaQuery>> = {});
CSS::Parser::Parser::PropertiesAndCustomProperties parse_css_style_attribute(CSS::Parser::ParsingParams const&, StringView); CSS::Parser::Parser::PropertiesAndCustomProperties parse_css_style_attribute(CSS::Parser::ParsingParams const&, StringView);
Vector<CSS::Descriptor> parse_css_list_of_descriptors(CSS::Parser::ParsingParams const&, CSS::AtRuleID, StringView); Vector<CSS::Descriptor> parse_css_list_of_descriptors(CSS::Parser::ParsingParams const&, CSS::AtRuleID, StringView);
RefPtr<CSS::CSSStyleValue> parse_css_value(CSS::Parser::ParsingParams const&, StringView, CSS::PropertyID property_id = CSS::PropertyID::Invalid); RefPtr<CSS::CSSStyleValue> parse_css_value(CSS::Parser::ParsingParams const&, StringView, CSS::PropertyID property_id = CSS::PropertyID::Invalid);

View file

@ -153,9 +153,9 @@ GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
TokenStream tokens { rule.prelude }; TokenStream tokens { rule.prelude };
tokens.discard_whitespace(); tokens.discard_whitespace();
Optional<URL::URL> url = parse_url_function(tokens); Optional<URL> url = parse_url_function(tokens);
if (!url.has_value() && tokens.next_token().is(Token::Type::String)) if (!url.has_value() && tokens.next_token().is(Token::Type::String))
url = complete_url(tokens.consume_a_token().token().string()); url = URL { tokens.consume_a_token().token().string().to_string() };
if (!url.has_value()) { if (!url.has_value()) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Unable to parse `{}` as URL.", tokens.next_token().to_debug_string()); dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Unable to parse `{}` as URL.", tokens.next_token().to_debug_string());
@ -191,7 +191,7 @@ GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
return {}; return {};
} }
return CSSImportRule::create(url.value(), const_cast<DOM::Document&>(*document()), supports, move(media_query_list)); return CSSImportRule::create(realm(), url.release_value(), const_cast<DOM::Document*>(m_document.ptr()), supports, move(media_query_list));
} }
Optional<FlyString> Parser::parse_layer_name(TokenStream<ComponentValue>& tokens, AllowBlankLayerName allow_blank_layer_name) Optional<FlyString> Parser::parse_layer_name(TokenStream<ComponentValue>& tokens, AllowBlankLayerName allow_blank_layer_name)
@ -435,7 +435,10 @@ GC::Ptr<CSSNamespaceRule> Parser::convert_to_namespace_rule(AtRule const& rule)
FlyString namespace_uri; FlyString namespace_uri;
if (auto url = parse_url_function(tokens); url.has_value()) { if (auto url = parse_url_function(tokens); url.has_value()) {
namespace_uri = url.value().to_string(); // "A URI string parsed from the URI syntax must be treated as a literal string: as with the STRING syntax, no
// URI-specific normalization is applied."
// https://drafts.csswg.org/css-namespaces/#syntax
namespace_uri = url->url();
} else if (auto& url_token = tokens.consume_a_token(); url_token.is(Token::Type::String)) { } else if (auto& url_token = tokens.consume_a_token(); url_token.is(Token::Type::String)) {
namespace_uri = url_token.token().string(); namespace_uri = url_token.token().string();
} else { } else {

View file

@ -15,7 +15,6 @@
#include <AK/Debug.h> #include <AK/Debug.h>
#include <AK/GenericLexer.h> #include <AK/GenericLexer.h>
#include <AK/TemporaryChange.h> #include <AK/TemporaryChange.h>
#include <LibURL/URL.h>
#include <LibWeb/CSS/FontFace.h> #include <LibWeb/CSS/FontFace.h>
#include <LibWeb/CSS/Parser/Parser.h> #include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/PropertyName.h> #include <LibWeb/CSS/PropertyName.h>
@ -2014,9 +2013,13 @@ RefPtr<AbstractImageStyleValue> Parser::parse_image_value(TokenStream<ComponentV
if (url.has_value()) { if (url.has_value()) {
// If the value is a 'url(..)' parse as image, but if it is just a reference 'url(#xx)', leave it alone, // If the value is a 'url(..)' parse as image, but if it is just a reference 'url(#xx)', leave it alone,
// so we can parse as URL further on. These URLs are used as references inside SVG documents for masks. // so we can parse as URL further on. These URLs are used as references inside SVG documents for masks.
if (!url.value().equals(m_url, URL::ExcludeFragment::Yes)) { if (!url->url().starts_with('#')) {
// FIXME: Stop completing the URL here
auto completed_url = complete_url(url->url());
if (completed_url.has_value()) {
tokens.discard_a_mark(); tokens.discard_a_mark();
return ImageStyleValue::create(url.value()); return ImageStyleValue::create(completed_url.release_value());
}
} }
tokens.restore_a_mark(); tokens.restore_a_mark();
return nullptr; return nullptr;
@ -2562,24 +2565,16 @@ RefPtr<CSSStyleValue> Parser::parse_easing_value(TokenStream<ComponentValue>& to
return nullptr; return nullptr;
} }
Optional<URL::URL> Parser::parse_url_function(TokenStream<ComponentValue>& tokens) Optional<URL> Parser::parse_url_function(TokenStream<ComponentValue>& tokens)
{ {
auto transaction = tokens.begin_transaction(); auto transaction = tokens.begin_transaction();
auto& component_value = tokens.consume_a_token(); auto const& component_value = tokens.consume_a_token();
auto convert_string_to_url = [&](StringView url_string) -> Optional<URL::URL> {
auto url = complete_url(url_string);
if (url.has_value()) {
transaction.commit();
return url;
}
return {};
};
if (component_value.is(Token::Type::Url)) { if (component_value.is(Token::Type::Url)) {
auto url_string = component_value.token().url(); transaction.commit();
return convert_string_to_url(url_string); return URL { component_value.token().url().to_string() };
} }
if (component_value.is_function("url"sv)) { if (component_value.is_function("url"sv)) {
auto const& function_values = component_value.function().value; auto const& function_values = component_value.function().value;
// FIXME: Handle url-modifiers. https://www.w3.org/TR/css-values-4/#url-modifiers // FIXME: Handle url-modifiers. https://www.w3.org/TR/css-values-4/#url-modifiers
@ -2588,8 +2583,8 @@ Optional<URL::URL> Parser::parse_url_function(TokenStream<ComponentValue>& token
if (value.is(Token::Type::Whitespace)) if (value.is(Token::Type::Whitespace))
continue; continue;
if (value.is(Token::Type::String)) { if (value.is(Token::Type::String)) {
auto url_string = value.token().string(); transaction.commit();
return convert_string_to_url(url_string); return URL { value.token().string().to_string() };
} }
break; break;
} }
@ -2603,7 +2598,11 @@ RefPtr<CSSStyleValue> Parser::parse_url_value(TokenStream<ComponentValue>& token
auto url = parse_url_function(tokens); auto url = parse_url_function(tokens);
if (!url.has_value()) if (!url.has_value())
return nullptr; return nullptr;
return URLStyleValue::create(*url); // FIXME: Stop completing the URL here
auto completed_url = complete_url(url->url());
if (!completed_url.has_value())
return nullptr;
return URLStyleValue::create(completed_url.release_value());
} }
// https://www.w3.org/TR/css-shapes-1/#typedef-shape-radius // https://www.w3.org/TR/css-shapes-1/#typedef-shape-radius
@ -3681,7 +3680,11 @@ RefPtr<FontSourceStyleValue> Parser::parse_font_source_value(TokenStream<Compone
// <url> [ format(<font-format>)]? [ tech( <font-tech>#)]? // <url> [ format(<font-format>)]? [ tech( <font-tech>#)]?
auto url = parse_url_function(tokens); auto url = parse_url_function(tokens);
if (!url.has_value() || !url->is_valid()) if (!url.has_value())
return nullptr;
// FIXME: Stop completing the URL here
auto completed_url = complete_url(url->url());
if (!completed_url.has_value())
return nullptr; return nullptr;
Optional<FlyString> format; Optional<FlyString> format;
@ -3719,7 +3722,7 @@ RefPtr<FontSourceStyleValue> Parser::parse_font_source_value(TokenStream<Compone
// FIXME: [ tech( <font-tech>#)]? // FIXME: [ tech( <font-tech>#)]?
transaction.commit(); transaction.commit();
return FontSourceStyleValue::create(url.release_value(), move(format)); return FontSourceStyleValue::create(completed_url.release_value(), move(format));
} }
NonnullRefPtr<CSSStyleValue> Parser::resolve_unresolved_style_value(ParsingParams const& context, DOM::Element& element, Optional<PseudoElement> pseudo_element, PropertyID property_id, UnresolvedStyleValue const& unresolved) NonnullRefPtr<CSSStyleValue> Parser::resolve_unresolved_style_value(ParsingParams const& context, DOM::Element& element, Optional<PseudoElement> pseudo_element, PropertyID property_id, UnresolvedStyleValue const& unresolved)

View file

@ -1,33 +1,33 @@
{ {
"-moz-meter-bar": { "-moz-meter-bar": {
"alias-for": "fill" "alias-for": "slider-fill"
}, },
"-moz-progress-bar": { "-moz-progress-bar": {
"alias-for": "fill" "alias-for": "slider-fill"
}, },
"-moz-range-progress": { "-moz-range-progress": {
"alias-for": "fill" "alias-for": "slider-fill"
}, },
"-moz-range-track": { "-moz-range-track": {
"alias-for": "track" "alias-for": "slider-track"
}, },
"-moz-range-thumb": { "-moz-range-thumb": {
"alias-for": "thumb" "alias-for": "slider-thumb"
}, },
"-webkit-meter-bar": { "-webkit-meter-bar": {
"alias-for": "track" "alias-for": "slider-track"
}, },
"-webkit-progress-bar": { "-webkit-progress-bar": {
"alias-for": "track" "alias-for": "slider-track"
}, },
"-webkit-progress-value": { "-webkit-progress-value": {
"alias-for": "fill" "alias-for": "slider-fill"
}, },
"-webkit-slider-runnable-track": { "-webkit-slider-runnable-track": {
"alias-for": "track" "alias-for": "slider-track"
}, },
"-webkit-slider-thumb": { "-webkit-slider-thumb": {
"alias-for": "thumb" "alias-for": "slider-thumb"
}, },
"after": { "after": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-after", "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-after",
@ -47,9 +47,6 @@
"file-selector-button": { "file-selector-button": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-file-selector-button" "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-file-selector-button"
}, },
"fill": {
"spec": "https://drafts.csswg.org/css-forms-1/#selectordef-fill"
},
"first-letter": { "first-letter": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-first-letter", "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-first-letter",
"property-whitelist": [ "property-whitelist": [
@ -107,11 +104,14 @@
"#custom-properties" "#custom-properties"
] ]
}, },
"thumb": { "slider-fill": {
"spec": "https://drafts.csswg.org/css-forms-1/#selectordef-thumb" "spec": "https://drafts.csswg.org/css-forms-1/#selectordef-slider-fill"
}, },
"track": { "slider-thumb": {
"spec": "https://drafts.csswg.org/css-forms-1/#selectordef-track" "spec": "https://drafts.csswg.org/css-forms-1/#selectordef-slider-thumb"
},
"slider-track": {
"spec": "https://drafts.csswg.org/css-forms-1/#selectordef-slider-track"
}, },
"view-transition": { "view-transition": {
"spec": "https://drafts.csswg.org/css-view-transitions-1/#selectordef-view-transition" "spec": "https://drafts.csswg.org/css-view-transitions-1/#selectordef-view-transition"

View file

@ -185,7 +185,7 @@ StyleComputer::StyleComputer(DOM::Document& document)
StyleComputer::~StyleComputer() = default; StyleComputer::~StyleComputer() = default;
FontLoader::FontLoader(StyleComputer& style_computer, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<URL::URL> urls, Function<void(FontLoader const&)> on_load, Function<void()> on_fail) FontLoader::FontLoader(StyleComputer& style_computer, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<::URL::URL> urls, Function<void(FontLoader const&)> on_load, Function<void()> on_fail)
: m_style_computer(style_computer) : m_style_computer(style_computer)
, m_family_name(move(family_name)) , m_family_name(move(family_name))
, m_unicode_ranges(move(unicode_ranges)) , m_unicode_ranges(move(unicode_ranges))
@ -3028,11 +3028,11 @@ Optional<FontLoader&> StyleComputer::load_font_face(ParsedFontFace const& font_f
.slope = font_face.slope().value_or(0), .slope = font_face.slope().value_or(0),
}; };
Vector<URL::URL> urls; Vector<::URL::URL> urls;
for (auto const& source : font_face.sources()) { for (auto const& source : font_face.sources()) {
// FIXME: These should be loaded relative to the stylesheet URL instead of the document URL. // FIXME: These should be loaded relative to the stylesheet URL instead of the document URL.
if (source.local_or_url.has<URL::URL>()) if (source.local_or_url.has<::URL::URL>())
urls.append(*m_document->encoding_parse_url(source.local_or_url.get<URL::URL>().to_string())); urls.append(*m_document->encoding_parse_url(source.local_or_url.get<::URL::URL>().to_string()));
// FIXME: Handle local() // FIXME: Handle local()
} }

View file

@ -315,7 +315,7 @@ private:
class FontLoader : public ResourceClient { class FontLoader : public ResourceClient {
public: public:
FontLoader(StyleComputer& style_computer, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<URL::URL> urls, ESCAPING Function<void(FontLoader const&)> on_load = {}, ESCAPING Function<void()> on_fail = {}); FontLoader(StyleComputer& style_computer, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<::URL::URL> urls, ESCAPING Function<void(FontLoader const&)> on_load = {}, ESCAPING Function<void()> on_fail = {});
virtual ~FontLoader() override; virtual ~FontLoader() override;
@ -340,7 +340,7 @@ private:
FlyString m_family_name; FlyString m_family_name;
Vector<Gfx::UnicodeRange> m_unicode_ranges; Vector<Gfx::UnicodeRange> m_unicode_ranges;
RefPtr<Gfx::Typeface> m_vector_font; RefPtr<Gfx::Typeface> m_vector_font;
Vector<URL::URL> m_urls; Vector<::URL::URL> m_urls;
Function<void(FontLoader const&)> m_on_load; Function<void(FontLoader const&)> m_on_load;
Function<void()> m_on_fail; Function<void()> m_on_fail;
}; };

View file

@ -27,6 +27,13 @@ void StyleSheet::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_media); visitor.visit(m_media);
} }
Optional<String> StyleSheet::href() const
{
if (m_location.has_value())
return m_location->to_string();
return {};
}
void StyleSheet::set_owner_node(DOM::Element* element) void StyleSheet::set_owner_node(DOM::Element* element)
{ {
m_owner_node = element; m_owner_node = element;

View file

@ -13,6 +13,7 @@
namespace Web::CSS { namespace Web::CSS {
// https://drafts.csswg.org/cssom-1/#the-stylesheet-interface
class StyleSheet : public Bindings::PlatformObject { class StyleSheet : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(StyleSheet, Bindings::PlatformObject); WEB_PLATFORM_OBJECT(StyleSheet, Bindings::PlatformObject);
@ -24,10 +25,10 @@ public:
DOM::Element* owner_node() { return m_owner_node; } DOM::Element* owner_node() { return m_owner_node; }
void set_owner_node(DOM::Element*); void set_owner_node(DOM::Element*);
Optional<String> href() const { return m_location; } Optional<String> href() const;
Optional<String> location() const { return m_location; } Optional<::URL::URL> location() const { return m_location; }
void set_location(Optional<String> location) { m_location = move(location); } void set_location(Optional<::URL::URL> location) { m_location = move(location); }
String title() const { return m_title; } String title() const { return m_title; }
Optional<String> title_for_bindings() const; Optional<String> title_for_bindings() const;
@ -35,7 +36,7 @@ public:
void set_type(String type) { m_type_string = move(type); } void set_type(String type) { m_type_string = move(type); }
MediaList* media() const GC::Ref<MediaList> media() const
{ {
return m_media; return m_media;
} }
@ -67,7 +68,7 @@ private:
GC::Ptr<DOM::Element> m_owner_node; GC::Ptr<DOM::Element> m_owner_node;
GC::Ptr<CSSStyleSheet> m_parent_style_sheet; GC::Ptr<CSSStyleSheet> m_parent_style_sheet;
Optional<String> m_location; Optional<::URL::URL> m_location;
String m_title; String m_title;
String m_type_string; String m_type_string;

View file

@ -60,7 +60,7 @@ void StyleSheetList::add_a_css_style_sheet(CSS::CSSStyleSheet& sheet)
} }
// https://www.w3.org/TR/cssom/#create-a-css-style-sheet // https://www.w3.org/TR/cssom/#create-a-css-style-sheet
void StyleSheetList::create_a_css_style_sheet(String type, DOM::Element* owner_node, String media, String title, bool alternate, bool origin_clean, Optional<String> location, CSS::CSSStyleSheet* parent_style_sheet, CSS::CSSRule* owner_rule, CSS::CSSStyleSheet& sheet) void StyleSheetList::create_a_css_style_sheet(String type, DOM::Element* owner_node, String media, String title, bool alternate, bool origin_clean, Optional<::URL::URL> location, CSS::CSSStyleSheet* parent_style_sheet, CSS::CSSRule* owner_rule, CSS::CSSStyleSheet& sheet)
{ {
// 1. Create a new CSS style sheet object and set its properties as specified. // 1. Create a new CSS style sheet object and set its properties as specified.
// FIXME: We receive `sheet` from the caller already. This is weird. // FIXME: We receive `sheet` from the caller already. This is weird.

View file

@ -21,7 +21,7 @@ public:
void add_a_css_style_sheet(CSS::CSSStyleSheet&); void add_a_css_style_sheet(CSS::CSSStyleSheet&);
void remove_a_css_style_sheet(CSS::CSSStyleSheet&); void remove_a_css_style_sheet(CSS::CSSStyleSheet&);
void create_a_css_style_sheet(String type, DOM::Element* owner_node, String media, String title, bool alternate, bool origin_clean, Optional<String> location, CSS::CSSStyleSheet* parent_style_sheet, CSS::CSSRule* owner_rule, CSS::CSSStyleSheet&); void create_a_css_style_sheet(String type, DOM::Element* owner_node, String media, String title, bool alternate, bool origin_clean, Optional<::URL::URL> location, CSS::CSSStyleSheet* parent_style_sheet, CSS::CSSRule* owner_rule, CSS::CSSStyleSheet&);
Vector<GC::Ref<CSSStyleSheet>> const& sheets() const { return m_sheets; } Vector<GC::Ref<CSSStyleSheet>> const& sheets() const { return m_sheets; }
Vector<GC::Ref<CSSStyleSheet>>& sheets() { return m_sheets; } Vector<GC::Ref<CSSStyleSheet>>& sheets() { return m_sheets; }

View file

@ -94,7 +94,8 @@ Optional<Gfx::ImageCursor> CursorStyleValue::make_image_cursor(Layout::NodeWithS
case DisplayListPlayerType::SkiaCPU: { case DisplayListPlayerType::SkiaCPU: {
auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(bitmap); auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(bitmap);
Painting::DisplayListPlayerSkia display_list_player; Painting::DisplayListPlayerSkia display_list_player;
display_list_player.execute(*display_list, painting_surface); Painting::ScrollStateSnapshot scroll_state_snapshot;
display_list_player.execute(*display_list, scroll_state_snapshot, painting_surface);
break; break;
} }
} }

View file

@ -34,7 +34,7 @@ String FontSourceStyleValue::to_string(SerializationMode) const
builder.append(')'); builder.append(')');
return builder.to_string_without_validation(); return builder.to_string_without_validation();
}, },
[this](URL::URL const& url) { [this](::URL::URL const& url) {
// <url> [ format(<font-format>)]? [ tech( <font-tech>#)]? // <url> [ format(<font-format>)]? [ tech( <font-tech>#)]?
// FIXME: tech() // FIXME: tech()
StringBuilder builder; StringBuilder builder;
@ -59,8 +59,8 @@ bool FontSourceStyleValue::properties_equal(FontSourceStyleValue const& other) c
} }
return false; return false;
}, },
[&other](URL::URL const& url) { [&other](::URL::URL const& url) {
if (auto* other_url = other.m_source.get_pointer<URL::URL>()) { if (auto* other_url = other.m_source.get_pointer<::URL::URL>()) {
return url == *other_url; return url == *other_url;
} }
return false; return false;

View file

@ -16,7 +16,7 @@ public:
struct Local { struct Local {
NonnullRefPtr<CSSStyleValue> name; NonnullRefPtr<CSSStyleValue> name;
}; };
using Source = Variant<Local, URL::URL>; using Source = Variant<Local, ::URL::URL>;
static ValueComparingNonnullRefPtr<FontSourceStyleValue> create(Source source, Optional<FlyString> format) static ValueComparingNonnullRefPtr<FontSourceStyleValue> create(Source source, Optional<FlyString> format)
{ {

View file

@ -20,7 +20,7 @@
namespace Web::CSS { namespace Web::CSS {
ImageStyleValue::ImageStyleValue(URL::URL const& url) ImageStyleValue::ImageStyleValue(::URL::URL const& url)
: AbstractImageStyleValue(Type::Image) : AbstractImageStyleValue(Type::Image)
, m_url(url) , m_url(url)
{ {

View file

@ -25,7 +25,7 @@ class ImageStyleValue final
using Base = AbstractImageStyleValue; using Base = AbstractImageStyleValue;
public: public:
static ValueComparingNonnullRefPtr<ImageStyleValue> create(URL::URL const& url) static ValueComparingNonnullRefPtr<ImageStyleValue> create(::URL::URL const& url)
{ {
return adopt_ref(*new (nothrow) ImageStyleValue(url)); return adopt_ref(*new (nothrow) ImageStyleValue(url));
} }
@ -53,14 +53,14 @@ public:
GC::Ptr<HTML::DecodedImageData> image_data() const; GC::Ptr<HTML::DecodedImageData> image_data() const;
private: private:
ImageStyleValue(URL::URL const&); ImageStyleValue(::URL::URL const&);
GC::Ptr<HTML::SharedResourceRequest> m_resource_request; GC::Ptr<HTML::SharedResourceRequest> m_resource_request;
void animate(); void animate();
Gfx::ImmutableBitmap const* bitmap(size_t frame_index, Gfx::IntSize = {}) const; Gfx::ImmutableBitmap const* bitmap(size_t frame_index, Gfx::IntSize = {}) const;
URL::URL m_url; ::URL::URL m_url;
WeakPtr<DOM::Document> m_document; WeakPtr<DOM::Document> m_document;
size_t m_current_frame_index { 0 }; size_t m_current_frame_index { 0 };

View file

@ -14,14 +14,14 @@ namespace Web::CSS {
class URLStyleValue final : public StyleValueWithDefaultOperators<URLStyleValue> { class URLStyleValue final : public StyleValueWithDefaultOperators<URLStyleValue> {
public: public:
static ValueComparingNonnullRefPtr<URLStyleValue> create(URL::URL const& url) static ValueComparingNonnullRefPtr<URLStyleValue> create(::URL::URL const& url)
{ {
return adopt_ref(*new (nothrow) URLStyleValue(url)); return adopt_ref(*new (nothrow) URLStyleValue(url));
} }
virtual ~URLStyleValue() override = default; virtual ~URLStyleValue() override = default;
URL::URL const& url() const { return m_url; } ::URL::URL const& url() const { return m_url; }
bool properties_equal(URLStyleValue const& other) const { return m_url == other.m_url; } bool properties_equal(URLStyleValue const& other) const { return m_url == other.m_url; }
@ -31,13 +31,13 @@ public:
} }
private: private:
URLStyleValue(URL::URL const& url) URLStyleValue(::URL::URL const& url)
: StyleValueWithDefaultOperators(Type::URL) : StyleValueWithDefaultOperators(Type::URL)
, m_url(url) , m_url(url)
{ {
} }
URL::URL m_url; ::URL::URL m_url;
}; };
} }

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CSS/Serialize.h>
#include <LibWeb/CSS/URL.h>
namespace Web::CSS {
URL::URL(String url)
: m_url(move(url))
{
}
// https://drafts.csswg.org/cssom-1/#serialize-a-url
String URL::to_string() const
{
// To serialize a URL means to create a string represented by "url(", followed by the serialization of the URL as a string, followed by ")".
StringBuilder builder;
builder.append("url("sv);
serialize_a_string(builder, m_url);
builder.append(')');
return builder.to_string_without_validation();
}
bool URL::operator==(URL const&) const = default;
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/String.h>
namespace Web::CSS {
// https://drafts.csswg.org/css-values-4/#urls
class URL {
public:
URL(String url);
String const& url() const { return m_url; }
String to_string() const;
bool operator==(URL const&) const;
private:
String m_url;
};
}

View file

@ -30,6 +30,7 @@
#include <LibJS/Runtime/ArrayBuffer.h> #include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/DataView.h> #include <LibJS/Runtime/DataView.h>
#include <LibJS/Runtime/TypedArray.h> #include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h> #include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Crypto/CryptoAlgorithms.h> #include <LibWeb/Crypto/CryptoAlgorithms.h>
#include <LibWeb/Crypto/KeyAlgorithms.h> #include <LibWeb/Crypto/KeyAlgorithms.h>

View file

@ -11,6 +11,7 @@
#include <LibCrypto/Hash/HashManager.h> #include <LibCrypto/Hash/HashManager.h>
#include <LibJS/Runtime/ArrayBuffer.h> #include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/JSONObject.h> #include <LibJS/Runtime/JSONObject.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h> #include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h> #include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/SubtleCryptoPrototype.h> #include <LibWeb/Bindings/SubtleCryptoPrototype.h>

View file

@ -35,14 +35,22 @@ void AbortSignal::initialize(JS::Realm& realm)
} }
// https://dom.spec.whatwg.org/#abortsignal-add // https://dom.spec.whatwg.org/#abortsignal-add
void AbortSignal::add_abort_algorithm(Function<void()> abort_algorithm) Optional<AbortSignal::AbortAlgorithmID> AbortSignal::add_abort_algorithm(Function<void()> abort_algorithm)
{ {
// 1. If signal is aborted, then return. // 1. If signal is aborted, then return.
if (aborted()) if (aborted())
return; return {};
// 2. Append algorithm to signals abort algorithms. // 2. Append algorithm to signals abort algorithms.
m_abort_algorithms.append(GC::create_function(vm().heap(), move(abort_algorithm))); m_abort_algorithms.set(++m_next_abort_algorithm_id, GC::create_function(vm().heap(), move(abort_algorithm)));
return m_next_abort_algorithm_id;
}
// https://dom.spec.whatwg.org/#abortsignal-remove
void AbortSignal::remove_abort_algorithm(AbortAlgorithmID id)
{
// To remove an algorithm algorithm from an AbortSignal signal, remove algorithm from signals abort algorithms.
m_abort_algorithms.remove(id);
} }
// https://dom.spec.whatwg.org/#abortsignal-signal-abort // https://dom.spec.whatwg.org/#abortsignal-signal-abort
@ -76,8 +84,8 @@ void AbortSignal::signal_abort(JS::Value reason)
// https://dom.spec.whatwg.org/#run-the-abort-steps // https://dom.spec.whatwg.org/#run-the-abort-steps
auto run_the_abort_steps = [](auto& signal) { auto run_the_abort_steps = [](auto& signal) {
// 1. For each algorithm in signals abort algorithms: run algorithm. // 1. For each algorithm in signals abort algorithms: run algorithm.
for (auto& algorithm : signal.m_abort_algorithms) for (auto const& algorithm : signal.m_abort_algorithms)
algorithm->function()(); algorithm.value->function()();
// 2. Empty signals abort algorithms. // 2. Empty signals abort algorithms.
signal.m_abort_algorithms.clear(); signal.m_abort_algorithms.clear();

View file

@ -7,6 +7,7 @@
#pragma once #pragma once
#include <AK/HashMap.h>
#include <AK/RefCounted.h> #include <AK/RefCounted.h>
#include <AK/Weakable.h> #include <AK/Weakable.h>
#include <LibGC/Function.h> #include <LibGC/Function.h>
@ -26,7 +27,9 @@ public:
virtual ~AbortSignal() override = default; virtual ~AbortSignal() override = default;
void add_abort_algorithm(ESCAPING Function<void()>); using AbortAlgorithmID = u64;
Optional<AbortAlgorithmID> add_abort_algorithm(Function<void()>);
void remove_abort_algorithm(AbortAlgorithmID);
// https://dom.spec.whatwg.org/#dom-abortsignal-aborted // https://dom.spec.whatwg.org/#dom-abortsignal-aborted
// An AbortSignal object is aborted when its abort reason is not undefined. // An AbortSignal object is aborted when its abort reason is not undefined.
@ -68,8 +71,8 @@ private:
JS::Value m_abort_reason { JS::js_undefined() }; JS::Value m_abort_reason { JS::js_undefined() };
// https://dom.spec.whatwg.org/#abortsignal-abort-algorithms // https://dom.spec.whatwg.org/#abortsignal-abort-algorithms
// FIXME: This should be a set. OrderedHashMap<AbortAlgorithmID, GC::Ref<GC::Function<void()>>> m_abort_algorithms;
Vector<GC::Ref<GC::Function<void()>>> m_abort_algorithms; AbortAlgorithmID m_next_abort_algorithm_id { 0 };
// https://dom.spec.whatwg.org/#abortsignal-source-signals // https://dom.spec.whatwg.org/#abortsignal-source-signals
// An AbortSignal object has associated source signals (a weak set of AbortSignal objects that the object is dependent on for its aborted state), which is initially empty. // An AbortSignal object has associated source signals (a weak set of AbortSignal objects that the object is dependent on for its aborted state), which is initially empty.

View file

@ -584,7 +584,8 @@ void Document::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_adopted_style_sheets); visitor.visit(m_adopted_style_sheets);
visitor.visit(m_shadow_roots); for (auto& shadow_root : m_shadow_roots)
visitor.visit(shadow_root);
visitor.visit(m_top_layer_elements); visitor.visit(m_top_layer_elements);
visitor.visit(m_top_layer_pending_removals); visitor.visit(m_top_layer_pending_removals);
@ -5879,7 +5880,7 @@ void Document::for_each_active_css_style_sheet(Function<void(CSS::CSSStyleSheet&
static Optional<CSS::CSSStyleSheet&> find_style_sheet_with_url(String const& url, CSS::CSSStyleSheet& style_sheet) static Optional<CSS::CSSStyleSheet&> find_style_sheet_with_url(String const& url, CSS::CSSStyleSheet& style_sheet)
{ {
if (style_sheet.location() == url) if (style_sheet.href() == url)
return style_sheet; return style_sheet;
for (auto& import_rule : style_sheet.import_rules()) { for (auto& import_rule : style_sheet.import_rules()) {
@ -5953,9 +5954,7 @@ void Document::register_shadow_root(Badge<DOM::ShadowRoot>, DOM::ShadowRoot& sha
void Document::unregister_shadow_root(Badge<DOM::ShadowRoot>, DOM::ShadowRoot& shadow_root) void Document::unregister_shadow_root(Badge<DOM::ShadowRoot>, DOM::ShadowRoot& shadow_root)
{ {
m_shadow_roots.remove_all_matching([&](auto& item) { m_shadow_roots.remove(shadow_root);
return item.ptr() == &shadow_root;
});
} }
void Document::for_each_shadow_root(Function<void(DOM::ShadowRoot&)>&& callback) void Document::for_each_shadow_root(Function<void(DOM::ShadowRoot&)>&& callback)
@ -5967,7 +5966,7 @@ void Document::for_each_shadow_root(Function<void(DOM::ShadowRoot&)>&& callback)
void Document::for_each_shadow_root(Function<void(DOM::ShadowRoot&)>&& callback) const void Document::for_each_shadow_root(Function<void(DOM::ShadowRoot&)>&& callback) const
{ {
for (auto& shadow_root : m_shadow_roots) for (auto& shadow_root : m_shadow_roots)
callback(shadow_root); callback(const_cast<ShadowRoot&>(shadow_root));
} }
bool Document::is_decoded_svg() const bool Document::is_decoded_svg() const
@ -6297,8 +6296,9 @@ void Document::invalidate_display_list()
RefPtr<Painting::DisplayList> Document::record_display_list(PaintConfig config) RefPtr<Painting::DisplayList> Document::record_display_list(PaintConfig config)
{ {
if (m_cached_display_list && m_cached_display_list_paint_config == config) if (m_cached_display_list && m_cached_display_list_paint_config == config) {
return m_cached_display_list; return m_cached_display_list;
}
auto display_list = Painting::DisplayList::create(); auto display_list = Painting::DisplayList::create();
Painting::DisplayListRecorder display_list_recorder(display_list); Painting::DisplayListRecorder display_list_recorder(display_list);
@ -6355,7 +6355,6 @@ RefPtr<Painting::DisplayList> Document::record_display_list(PaintConfig config)
viewport_paintable.paint_all_phases(context); viewport_paintable.paint_all_phases(context);
display_list->set_device_pixels_per_css_pixel(page().client().device_pixels_per_css_pixel()); display_list->set_device_pixels_per_css_pixel(page().client().device_pixels_per_css_pixel());
display_list->set_scroll_state(viewport_paintable.scroll_state());
m_cached_display_list = display_list; m_cached_display_list = display_list;
m_cached_display_list_paint_config = config; m_cached_display_list_paint_config = config;

View file

@ -26,6 +26,7 @@
#include <LibWeb/CSS/StyleSheetList.h> #include <LibWeb/CSS/StyleSheetList.h>
#include <LibWeb/Cookie/Cookie.h> #include <LibWeb/Cookie/Cookie.h>
#include <LibWeb/DOM/ParentNode.h> #include <LibWeb/DOM/ParentNode.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/CrossOrigin/OpenerPolicy.h> #include <LibWeb/HTML/CrossOrigin/OpenerPolicy.h>
#include <LibWeb/HTML/DocumentReadyState.h> #include <LibWeb/HTML/DocumentReadyState.h>
@ -1180,7 +1181,7 @@ private:
mutable GC::Ptr<WebIDL::ObservableArray> m_adopted_style_sheets; mutable GC::Ptr<WebIDL::ObservableArray> m_adopted_style_sheets;
Vector<GC::Ref<DOM::ShadowRoot>> m_shadow_roots; ShadowRoot::DocumentShadowRootList m_shadow_roots;
Optional<Core::DateTime> m_last_modified; Optional<Core::DateTime> m_last_modified;

View file

@ -448,7 +448,7 @@ GC::Ptr<DOM::Document> load_document(HTML::NavigationParams const& navigation_pa
// sourceSnapshotParams, and initiatorOrigin. // sourceSnapshotParams, and initiatorOrigin.
} }
// -> A supported image, video, or audio type // -> a supported image, video, or audio type
if (type.is_image() if (type.is_image()
|| type.is_audio_or_video()) { || type.is_audio_or_video()) {
// Return the result of loading a media document given navigationParams and type. // Return the result of loading a media document given navigationParams and type.

View file

@ -2548,7 +2548,7 @@ JS::ThrowCompletionOr<void> Element::upgrade_element(GC::Ref<HTML::CustomElement
// FIXME: 9. If element is a form-associated custom element, then: // FIXME: 9. If element is a form-associated custom element, then:
// 1. Reset the form owner of element. If element is associated with a form element, then enqueue a custom element callback reaction with element, callback name "formAssociatedCallback", and « the associated form ». // 1. Reset the form owner of element. If element is associated with a form element, then enqueue a custom element callback reaction with element, callback name "formAssociatedCallback", and « the associated form ».
// 2. If element is disabled, then enqueue a custom element callback reaction with element, callback name "formDisabledCallback" and « true ». // 2. If element is disabled, then enqueue a custom element callback reaction with element, callback name "formDisabledCallback", and « true ».
// 10. Set element's custom element state to "custom". // 10. Set element's custom element state to "custom".
set_custom_element_state(CustomElementState::Custom); set_custom_element_state(CustomElementState::Custom);

View file

@ -120,6 +120,10 @@ interface Element : Node {
// FIXME: [CEReactions] undefined insertAdjacentHTML(DOMString position, (TrustedHTML or DOMString) string); // FIXME: [CEReactions] undefined insertAdjacentHTML(DOMString position, (TrustedHTML or DOMString) string);
[CEReactions] undefined insertAdjacentHTML(DOMString position, DOMString text); [CEReactions] undefined insertAdjacentHTML(DOMString position, DOMString text);
// https://w3c.github.io/pointerevents/#extensions-to-the-element-interface
[FIXME] undefined setPointerCapture(long pointerId);
[FIXME] undefined releasePointerCapture(long pointerId);
[FIXME] boolean hasPointerCapture(long pointerId);
}; };
dictionary GetHTMLOptions { dictionary GetHTMLOptions {

View file

@ -231,7 +231,7 @@ void EventTarget::add_an_event_listener(DOMEventListener& listener)
// 6. If listeners signal is not null, then add the following abort steps to it: // 6. If listeners signal is not null, then add the following abort steps to it:
if (listener.signal) { if (listener.signal) {
// NOTE: `this` and `listener` are protected by AbortSignal using GC::HeapFunction. // NOTE: `this` and `listener` are protected by AbortSignal using GC::HeapFunction.
listener.signal->add_abort_algorithm([this, &listener] { (void)listener.signal->add_abort_algorithm([this, &listener] {
// 1. Remove an event listener with eventTarget and listener. // 1. Remove an event listener with eventTarget and listener.
remove_an_event_listener(listener); remove_an_event_listener(listener);
}); });

View file

@ -53,6 +53,7 @@ enum class ShouldComputeRole {
X(AdoptedStyleSheetsList) \ X(AdoptedStyleSheetsList) \
X(CSSFontLoaded) \ X(CSSFontLoaded) \
X(CSSImportRule) \ X(CSSImportRule) \
X(CSSStylePropertiesTextChange) \
X(CustomElementStateChange) \ X(CustomElementStateChange) \
X(DidLoseFocus) \ X(DidLoseFocus) \
X(DidReceiveFocus) \ X(DidReceiveFocus) \

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/NodeIteratorPrototype.h> #include <LibWeb/Bindings/NodeIteratorPrototype.h>
#include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Node.h> #include <LibWeb/DOM/Node.h>

View file

@ -97,6 +97,11 @@ private:
GC::Ptr<CSS::StyleSheetList> m_style_sheets; GC::Ptr<CSS::StyleSheetList> m_style_sheets;
mutable GC::Ptr<WebIDL::ObservableArray> m_adopted_style_sheets; mutable GC::Ptr<WebIDL::ObservableArray> m_adopted_style_sheets;
IntrusiveListNode<ShadowRoot> m_list_node;
public:
using DocumentShadowRootList = IntrusiveList<&ShadowRoot::m_list_node>;
}; };
template<> template<>

View file

@ -54,7 +54,8 @@ void StyleElementUtils::update_a_style_block(DOM::Element& style_element)
// FIXME: This is a bit awkward, as the spec doesn't actually tell us when to parse the CSS text, // FIXME: This is a bit awkward, as the spec doesn't actually tell us when to parse the CSS text,
// so we just do it here and pass the parsed sheet to create_a_css_style_sheet(). // so we just do it here and pass the parsed sheet to create_a_css_style_sheet().
auto* sheet = parse_css_stylesheet(CSS::Parser::ParsingParams(style_element.document()), style_element.text_content().value_or(String {})); // AD-HOC: Are we supposed to use the document's URL for the stylesheet's location? Not doing it breaks things.
auto* sheet = parse_css_stylesheet(CSS::Parser::ParsingParams(style_element.document()), style_element.text_content().value_or(String {}), style_element.document().url());
if (!sheet) if (!sheet)
return; return;

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/Intrinsics.h> #include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/TreeWalkerPrototype.h> #include <LibWeb/Bindings/TreeWalkerPrototype.h>
#include <LibWeb/DOM/Node.h> #include <LibWeb/DOM/Node.h>

View file

@ -790,7 +790,7 @@ void dump_font_face_rule(StringBuilder& builder, CSS::CSSFontFaceRule const& rul
void dump_import_rule(StringBuilder& builder, CSS::CSSImportRule const& rule, int indent_levels) void dump_import_rule(StringBuilder& builder, CSS::CSSImportRule const& rule, int indent_levels)
{ {
indent(builder, indent_levels); indent(builder, indent_levels);
builder.appendff(" Document URL: {}\n", rule.url()); builder.appendff(" Document URL: {}\n", rule.url().to_string());
} }
void dump_layer_block_rule(StringBuilder& builder, CSS::CSSLayerBlockRule const& layer_block, int indent_levels) void dump_layer_block_rule(StringBuilder& builder, CSS::CSSLayerBlockRule const& layer_block, int indent_levels)

View file

@ -36,13 +36,13 @@ FlyString const& PerformanceEventTiming::entry_type() const
HighResolutionTime::DOMHighResTimeStamp PerformanceEventTiming::processing_end() const HighResolutionTime::DOMHighResTimeStamp PerformanceEventTiming::processing_end() const
{ {
dbgln("FIXME: Implement PeformanceEventTiming processing_end()"); dbgln("FIXME: Implement PerformanceEventTiming processing_end()");
return 0; return 0;
} }
HighResolutionTime::DOMHighResTimeStamp PerformanceEventTiming::processing_start() const HighResolutionTime::DOMHighResTimeStamp PerformanceEventTiming::processing_start() const
{ {
dbgln("FIXME: Implement PeformanceEventTiming processing_start()"); dbgln("FIXME: Implement PerformanceEventTiming processing_start()");
return 0; return 0;
} }
@ -53,20 +53,20 @@ bool PerformanceEventTiming::cancelable() const
JS::ThrowCompletionOr<GC::Ptr<DOM::Node>> PerformanceEventTiming::target() JS::ThrowCompletionOr<GC::Ptr<DOM::Node>> PerformanceEventTiming::target()
{ {
dbgln("FIXME: Implement PerformanceEventTiming::PeformanceEventTiming target()"); dbgln("FIXME: Implement PerformanceEventTiming::PerformanceEventTiming target()");
return nullptr; return nullptr;
} }
unsigned long long PerformanceEventTiming::interaction_id() unsigned long long PerformanceEventTiming::interaction_id()
{ {
dbgln("FIXME: Implement PeformanceEventTiming interaction_id()"); dbgln("FIXME: Implement PerformanceEventTiming interaction_id()");
return 0; return 0;
} }
// https://www.w3.org/TR/event-timing/#sec-should-add-performanceeventtiming // https://www.w3.org/TR/event-timing/#sec-should-add-performanceeventtiming
PerformanceTimeline::ShouldAddEntry PerformanceEventTiming::should_add_performance_event_timing() const PerformanceTimeline::ShouldAddEntry PerformanceEventTiming::should_add_performance_event_timing() const
{ {
dbgln("FIXME: Implement PeformanceEventTiming should_add_performance_event_timing()"); dbgln("FIXME: Implement PerformanceEventTiming should_add_performance_event_timing()");
// 1. If entrys entryType attribute value equals to "first-input", return true. // 1. If entrys entryType attribute value equals to "first-input", return true.
if (entry_type() == "first-input") if (entry_type() == "first-input")
return PerformanceTimeline::ShouldAddEntry::Yes; return PerformanceTimeline::ShouldAddEntry::Yes;
@ -89,7 +89,7 @@ PerformanceTimeline::ShouldAddEntry PerformanceEventTiming::should_add_performan
// the commented out if statement won't compile // the commented out if statement won't compile
PerformanceTimeline::AvailableFromTimeline PerformanceEventTiming::available_from_timeline() PerformanceTimeline::AvailableFromTimeline PerformanceEventTiming::available_from_timeline()
{ {
dbgln("FIXME: Implement PeformanceEventTiming available_from_timeline()"); dbgln("FIXME: Implement PerformanceEventTiming available_from_timeline()");
// if (entry_type() == "first-input") // if (entry_type() == "first-input")
return PerformanceTimeline::AvailableFromTimeline::Yes; return PerformanceTimeline::AvailableFromTimeline::Yes;
} }
@ -98,7 +98,7 @@ PerformanceTimeline::AvailableFromTimeline PerformanceEventTiming::available_fro
// FIXME: Same issue as available_from_timeline() above // FIXME: Same issue as available_from_timeline() above
Optional<u64> PerformanceEventTiming::max_buffer_size() Optional<u64> PerformanceEventTiming::max_buffer_size()
{ {
dbgln("FIXME: Implement PeformanceEventTiming max_buffer_size()"); dbgln("FIXME: Implement PerformanceEventTiming max_buffer_size()");
if (true) //(entry_type() == "first-input") if (true) //(entry_type() == "first-input")
return 1; return 1;
// else return 150; // else return 150;

View file

@ -130,7 +130,7 @@ GC::Ref<WebIDL::Promise> fetch(JS::VM& vm, RequestInfo const& input, RequestInit
})))); }))));
// 11. Add the following abort steps to requestObjects signal: // 11. Add the following abort steps to requestObjects signal:
request_object->signal()->add_abort_algorithm([locally_aborted, request, controller_holder, promise_capability, request_object, response_object, &relevant_realm] { (void)request_object->signal()->add_abort_algorithm([locally_aborted, request, controller_holder, promise_capability, request_object, response_object, &relevant_realm] {
dbgln_if(WEB_FETCH_DEBUG, "Fetch: Request object signal's abort algorithm called"); dbgln_if(WEB_FETCH_DEBUG, "Fetch: Request object signal's abort algorithm called");
// 1. Set locallyAborted to true. // 1. Set locallyAborted to true.

View file

@ -269,6 +269,7 @@ class TransformationStyleValue;
class TransitionStyleValue; class TransitionStyleValue;
class UnicodeRangeStyleValue; class UnicodeRangeStyleValue;
class UnresolvedStyleValue; class UnresolvedStyleValue;
class URL;
class URLStyleValue; class URLStyleValue;
class VisualViewport; class VisualViewport;
@ -624,6 +625,7 @@ class IDBOpenDBRequest;
class IDBRequest; class IDBRequest;
class IDBTransaction; class IDBTransaction;
class IDBVersionChangeEvent; class IDBVersionChangeEvent;
class Index;
class ObjectStore; class ObjectStore;
class RequestList; class RequestList;
} }

View file

@ -103,17 +103,17 @@ GC::Ref<DOMRect> DOMQuad::get_bounds() const
} }
// https://drafts.fxtf.org/geometry/#structured-serialization // https://drafts.fxtf.org/geometry/#structured-serialization
WebIDL::ExceptionOr<void> DOMQuad::serialization_steps(HTML::SerializationRecord& serialzied, bool for_storage, HTML::SerializationMemory& memory) WebIDL::ExceptionOr<void> DOMQuad::serialization_steps(HTML::SerializationRecord& serialized, bool for_storage, HTML::SerializationMemory& memory)
{ {
auto& vm = this->vm(); auto& vm = this->vm();
// 1. Set serialized.[[P1]] to the sub-serialization of values point 1. // 1. Set serialized.[[P1]] to the sub-serialization of values point 1.
serialzied.extend(TRY(HTML::structured_serialize_internal(vm, m_p1, for_storage, memory))); serialized.extend(TRY(HTML::structured_serialize_internal(vm, m_p1, for_storage, memory)));
// 2. Set serialized.[[P2]] to the sub-serialization of values point 2. // 2. Set serialized.[[P2]] to the sub-serialization of values point 2.
serialzied.extend(TRY(HTML::structured_serialize_internal(vm, m_p2, for_storage, memory))); serialized.extend(TRY(HTML::structured_serialize_internal(vm, m_p2, for_storage, memory)));
// 3. Set serialized.[[P3]] to the sub-serialization of values point 3. // 3. Set serialized.[[P3]] to the sub-serialization of values point 3.
serialzied.extend(TRY(HTML::structured_serialize_internal(vm, m_p3, for_storage, memory))); serialized.extend(TRY(HTML::structured_serialize_internal(vm, m_p3, for_storage, memory)));
// 4. Set serialized.[[P4]] to the sub-serialization of values point 4. // 4. Set serialized.[[P4]] to the sub-serialization of values point 4.
serialzied.extend(TRY(HTML::structured_serialize_internal(vm, m_p4, for_storage, memory))); serialized.extend(TRY(HTML::structured_serialize_internal(vm, m_p4, for_storage, memory)));
return {}; return {};
} }

View file

@ -81,7 +81,7 @@ void CanvasPath::bezier_curve_to(double cp1x, double cp1y, double cp2x, double c
// 2. Ensure there is a subpath for (cp1x, cp1y) // 2. Ensure there is a subpath for (cp1x, cp1y)
ensure_subpath(cp1x, cp1y); ensure_subpath(cp1x, cp1y);
// 3. Connect the last point in the subpath to the given point (x, y) using a cubic Bézier curve with control poits (cp1x, cp1y) and (cp2x, cp2y). // 3. Connect the last point in the subpath to the given point (x, y) using a cubic Bézier curve with control points (cp1x, cp1y) and (cp2x, cp2y).
// 4. Add the point (x, y) to the subpath. // 4. Add the point (x, y) to the subpath.
m_path.cubic_bezier_curve_to( m_path.cubic_bezier_curve_to(
Gfx::FloatPoint { cp1x, cp1y }, Gfx::FloatPoint { cp2x, cp2y }, Gfx::FloatPoint { x, y }); Gfx::FloatPoint { cp1x, cp1y }, Gfx::FloatPoint { cp2x, cp2y }, Gfx::FloatPoint { x, y });

View file

@ -263,10 +263,10 @@ Gfx::Path CanvasRenderingContext2D::text_path(StringView text, float x, float y,
} }
// Apply text baseline // Apply text baseline
// FIXME: Implement CanvasTextBasline::Hanging, Bindings::CanvasTextAlign::Alphabetic and Bindings::CanvasTextAlign::Ideographic for real // FIXME: Implement CanvasTextBaseline::Hanging, Bindings::CanvasTextAlign::Alphabetic and Bindings::CanvasTextAlign::Ideographic for real
// right now they are just handled as textBaseline = top or bottom. // right now they are just handled as textBaseline = top or bottom.
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-textbaseline-hanging // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-textbaseline-hanging
// Default baseline of draw_text is top so do nothing by CanvasTextBaseline::Top and CanvasTextBasline::Hanging // Default baseline of draw_text is top so do nothing by CanvasTextBaseline::Top and CanvasTextBaseline::Hanging
if (drawing_state.text_baseline == Bindings::CanvasTextBaseline::Middle) { if (drawing_state.text_baseline == Bindings::CanvasTextBaseline::Middle) {
transform = Gfx::AffineTransform {}.set_translation({ 0, font->pixel_size() / 2 }).multiply(transform); transform = Gfx::AffineTransform {}.set_translation({ 0, font->pixel_size() / 2 }).multiply(transform);
} }

View file

@ -65,7 +65,7 @@ WebIDL::ExceptionOr<GC::Ref<CloseWatcher>> CloseWatcher::construct_impl(JS::Real
} }
// 3.2 Add the following steps to options["signal"]: // 3.2 Add the following steps to options["signal"]:
signal->add_abort_algorithm([close_watcher] { (void)signal->add_abort_algorithm([close_watcher] {
// 3.2.1 Destroy closeWatcher. // 3.2.1 Destroy closeWatcher.
close_watcher->destroy(); close_watcher->destroy();
}); });

View file

@ -233,7 +233,7 @@ static Optional<YearAndMonth> parse_a_month_component(GenericLexer& input)
{ {
// 1. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is // 1. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is
// not at least four characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. // not at least four characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer.
// Let that number be the year. // Let year be that number.
auto year_string = input.consume_while(is_ascii_digit); auto year_string = input.consume_while(is_ascii_digit);
if (year_string.length() < 4) if (year_string.length() < 4)
return {}; return {};
@ -252,8 +252,8 @@ static Optional<YearAndMonth> parse_a_month_component(GenericLexer& input)
return {}; return {};
// 4. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is not // 4. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is not
// exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. Let that // exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. Let month
// number be the month. // be that number.
auto month_string = input.consume_while(is_ascii_digit); auto month_string = input.consume_while(is_ascii_digit);
if (month_string.length() != 2) if (month_string.length() != 2)
return {}; return {};
@ -301,7 +301,7 @@ Optional<WeekYearAndWeek> parse_a_week_string(StringView input_view)
// 3. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is // 3. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is
// not at least four characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. // not at least four characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer.
// Let that number be the year. // Let year be that number.
auto year_string = input.consume_while(is_ascii_digit); auto year_string = input.consume_while(is_ascii_digit);
if (year_string.length() < 4) if (year_string.length() < 4)
return {}; return {};
@ -325,8 +325,8 @@ Optional<WeekYearAndWeek> parse_a_week_string(StringView input_view)
return {}; return {};
// 7. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is not // 7. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is not
// exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. Let that // exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. Let week
// number be the week. // be that number.
auto week_string = input.consume_while(is_ascii_digit); auto week_string = input.consume_while(is_ascii_digit);
if (week_string.length() != 2) if (week_string.length() != 2)
return {}; return {};
@ -365,8 +365,8 @@ static Optional<YearMonthDay> parse_a_date_component(GenericLexer& input)
return {}; return {};
// 4. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is not // 4. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is not
// exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. Let that // exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. Let day
// number be the day. // be that number.
auto day_string = input.consume_while(is_ascii_digit); auto day_string = input.consume_while(is_ascii_digit);
if (day_string.length() != 2) if (day_string.length() != 2)
return {}; return {};
@ -406,7 +406,7 @@ static Optional<HourMinuteSecond> parse_a_time_component(GenericLexer& input)
{ {
// 1. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence // 1. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence
// is not exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten // is not exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten
// integer. Let that number be the hour. // integer. Let hour be that number.
auto hour_string = input.consume_while(is_ascii_digit); auto hour_string = input.consume_while(is_ascii_digit);
if (hour_string.length() != 2) if (hour_string.length() != 2)
return {}; return {};
@ -426,7 +426,7 @@ static Optional<HourMinuteSecond> parse_a_time_component(GenericLexer& input)
// 4. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence // 4. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence
// is not exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. // is not exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer.
// Let that number be the minute. // Let minute be that number.
auto minute_string = input.consume_while(is_ascii_digit); auto minute_string = input.consume_while(is_ascii_digit);
if (minute_string.length() != 2) if (minute_string.length() != 2)
return {}; return {};

View file

@ -389,7 +389,7 @@ void EventSource::process_field(StringView field, StringView value)
{ {
// -> If the field name is "event" // -> If the field name is "event"
if (field == "event"sv) { if (field == "event"sv) {
// Set the event type buffer to field value. // Set the event type buffer to the field value.
m_event_type = MUST(String::from_utf8(value)); m_event_type = MUST(String::from_utf8(value));
} }
// -> If the field name is "data" // -> If the field name is "data"

View file

@ -1121,7 +1121,7 @@ WebIDL::ExceptionOr<bool> HTMLElement::check_popover_validity(ExpectedToBeShowin
// - ignoreDomState is false and element is not connected; // - ignoreDomState is false and element is not connected;
// - element's node document is not fully active; // - element's node document is not fully active;
// - ignoreDomState is false and expectedDocument is not null and element's node document is not expectedDocument; // - ignoreDomState is false and expectedDocument is not null and element's node document is not expectedDocument;
// - element is a dialog element and its is modal flage is set to true; or // - element is a dialog element and its is modal flag is set to true; or
// - FIXME: element's fullscreen flag is set, // - FIXME: element's fullscreen flag is set,
// then: // then:
// 3.1 If throwExceptions is true, then throw an "InvalidStateError" DOMException. // 3.1 If throwExceptions is true, then throw an "InvalidStateError" DOMException.

View file

@ -145,7 +145,7 @@ void HTMLInputElement::adjust_computed_style(CSS::ComputedProperties& style)
style.set_property(CSS::PropertyID::Width, CSS::LengthStyleValue::create(CSS::Length(size(), CSS::Length::Type::Ch))); style.set_property(CSS::PropertyID::Width, CSS::LengthStyleValue::create(CSS::Length(size(), CSS::Length::Type::Ch)));
} }
// NOTE: The following line-height check is done for web compatability and usability reasons. // NOTE: The following line-height check is done for web compatibility and usability reasons.
// FIXME: The "normal" line-height value should be calculated but assume 1.0 for now. // FIXME: The "normal" line-height value should be calculated but assume 1.0 for now.
double normal_line_height = 1.0; double normal_line_height = 1.0;
double current_line_height = style.line_height().to_double(); double current_line_height = style.line_height().to_double();
@ -1217,15 +1217,15 @@ void HTMLInputElement::create_range_input_shadow_tree()
set_shadow_root(shadow_root); set_shadow_root(shadow_root);
m_slider_runnable_track = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); m_slider_runnable_track = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
m_slider_runnable_track->set_use_pseudo_element(CSS::PseudoElement::Track); m_slider_runnable_track->set_use_pseudo_element(CSS::PseudoElement::SliderTrack);
MUST(shadow_root->append_child(*m_slider_runnable_track)); MUST(shadow_root->append_child(*m_slider_runnable_track));
m_slider_progress_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); m_slider_progress_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
m_slider_progress_element->set_use_pseudo_element(CSS::PseudoElement::Fill); m_slider_progress_element->set_use_pseudo_element(CSS::PseudoElement::SliderFill);
MUST(m_slider_runnable_track->append_child(*m_slider_progress_element)); MUST(m_slider_runnable_track->append_child(*m_slider_progress_element));
m_slider_thumb = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); m_slider_thumb = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
m_slider_thumb->set_use_pseudo_element(CSS::PseudoElement::Thumb); m_slider_thumb->set_use_pseudo_element(CSS::PseudoElement::SliderThumb);
MUST(m_slider_runnable_track->append_child(*m_slider_thumb)); MUST(m_slider_runnable_track->append_child(*m_slider_thumb));
update_slider_shadow_tree_elements(); update_slider_shadow_tree_elements();
@ -2404,7 +2404,7 @@ WebIDL::ExceptionOr<GC::Ptr<JS::Date>> HTMLInputElement::convert_string_to_date(
} }
// https://html.spec.whatwg.org/multipage/input.html#concept-input-value-date-string // https://html.spec.whatwg.org/multipage/input.html#concept-input-value-date-string
String HTMLInputElement::covert_date_to_string(GC::Ref<JS::Date> input) const String HTMLInputElement::convert_date_to_string(GC::Ref<JS::Date> input) const
{ {
// https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):concept-input-value-date-string // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):concept-input-value-date-string
if (type_state() == TypeAttributeState::Date) { if (type_state() == TypeAttributeState::Date) {
@ -2420,7 +2420,7 @@ String HTMLInputElement::covert_date_to_string(GC::Ref<JS::Date> input) const
return convert_number_to_time_string(input->date_value()); return convert_number_to_time_string(input->date_value());
} }
dbgln("HTMLInputElement::covert_date_to_string() not implemented for input type {}", type()); dbgln("HTMLInputElement::convert_date_to_string() not implemented for input type {}", type());
return {}; return {};
} }
@ -2589,7 +2589,7 @@ WebIDL::ExceptionOr<void> HTMLInputElement::set_value_as_date(Optional<GC::Root<
} }
// otherwise, run the algorithm to convert a Date object to a string, as defined for that state, on the new value, and set the value of the element to the resulting string. // otherwise, run the algorithm to convert a Date object to a string, as defined for that state, on the new value, and set the value of the element to the resulting string.
TRY(set_value(covert_date_to_string(date))); TRY(set_value(convert_date_to_string(date)));
return {}; return {};
} }

View file

@ -289,7 +289,7 @@ private:
String convert_number_to_string(double input) const; String convert_number_to_string(double input) const;
WebIDL::ExceptionOr<GC::Ptr<JS::Date>> convert_string_to_date(StringView input) const; WebIDL::ExceptionOr<GC::Ptr<JS::Date>> convert_string_to_date(StringView input) const;
String covert_date_to_string(GC::Ref<JS::Date> input) const; String convert_date_to_string(GC::Ref<JS::Date> input) const;
Optional<double> min() const; Optional<double> min() const;
Optional<double> max() const; Optional<double> max() const;

View file

@ -280,7 +280,7 @@ HTMLLinkElement::LinkProcessingOptions HTMLLinkElement::create_link_options()
options.policy_container = document.policy_container(); options.policy_container = document.policy_container();
// document document // document document
options.document = &document; options.document = &document;
// FIXME: cryptographic nonce metadata The current value of el's [[CryptographicNonce]] internal slot // FIXME: cryptographic nonce metadata the current value of el's [[CryptographicNonce]] internal slot
// fetch priority the state of el's fetchpriority content attribute // fetch priority the state of el's fetchpriority content attribute
options.fetch_priority = Fetch::Infrastructure::request_priority_from_string(get_attribute_value(HTML::AttributeNames::fetchpriority)).value_or(Fetch::Infrastructure::Request::Priority::Auto); options.fetch_priority = Fetch::Infrastructure::request_priority_from_string(get_attribute_value(HTML::AttributeNames::fetchpriority)).value_or(Fetch::Infrastructure::Request::Priority::Auto);
@ -490,9 +490,9 @@ void HTMLLinkElement::process_stylesheet_resource(bool success, Fetch::Infrastru
m_loaded_style_sheet = parse_css_stylesheet(CSS::Parser::ParsingParams(document(), *response.url()), decoded_string); m_loaded_style_sheet = parse_css_stylesheet(CSS::Parser::ParsingParams(document(), *response.url()), decoded_string);
if (m_loaded_style_sheet) { if (m_loaded_style_sheet) {
Optional<String> location; Optional<::URL::URL> location;
if (!response.url_list().is_empty()) if (!response.url_list().is_empty())
location = response.url_list().first().to_string(); location = response.url_list().first();
document_or_shadow_root_style_sheets().create_a_css_style_sheet( document_or_shadow_root_style_sheets().create_a_css_style_sheet(
"text/css"_string, "text/css"_string,

View file

@ -637,11 +637,11 @@ public:
{ {
// 2. ⌛ Process candidate: If candidate does not have a src attribute, or if its src attribute's value is the // 2. ⌛ Process candidate: If candidate does not have a src attribute, or if its src attribute's value is the
// empty string, then end the synchronous section, and jump down to the failed with elements step below. // empty string, then end the synchronous section, and jump down to the failed with elements step below.
String candiate_src; String candidate_src;
if (auto maybe_src = m_candidate->get_attribute(HTML::AttributeNames::src); maybe_src.has_value()) if (auto maybe_src = m_candidate->get_attribute(HTML::AttributeNames::src); maybe_src.has_value())
candiate_src = *maybe_src; candidate_src = *maybe_src;
if (candiate_src.is_empty()) { if (candidate_src.is_empty()) {
TRY(failed_with_elements()); TRY(failed_with_elements());
return {}; return {};
} }
@ -649,7 +649,7 @@ public:
// 3. ⌛ Let urlString and urlRecord be the resulting URL string and the resulting URL record, respectively, that // 3. ⌛ Let urlString and urlRecord be the resulting URL string and the resulting URL record, respectively, that
// would have resulted from parsing the URL specified by candidate's src attribute's value relative to the // would have resulted from parsing the URL specified by candidate's src attribute's value relative to the
// candidate's node document when the src attribute was last changed. // candidate's node document when the src attribute was last changed.
auto url_record = m_candidate->document().parse_url(candiate_src); auto url_record = m_candidate->document().parse_url(candidate_src);
// 4. ⌛ If urlString was not obtained successfully, then end the synchronous section, and jump down to the failed // 4. ⌛ If urlString was not obtained successfully, then end the synchronous section, and jump down to the failed
// with elements step below. // with elements step below.
@ -1008,7 +1008,7 @@ WebIDL::ExceptionOr<void> HTMLMediaElement::fetch_resource(URL::URL const& url_r
// 6. Let byteRange, which is "entire resource" or a (number, number or "until end") tuple, be the byte range required to satisfy missing data in // 6. Let byteRange, which is "entire resource" or a (number, number or "until end") tuple, be the byte range required to satisfy missing data in
// media data. This value is implementation-defined and may rely on codec, network conditions or other heuristics. The user-agent may determine // media data. This value is implementation-defined and may rely on codec, network conditions or other heuristics. The user-agent may determine
// to fetch the resource in full, in which case byteRange would be "entire resource", to fetch from a byte offset until the end, in which case // to fetch the resource in full, in which case byteRange would be "entire resource", to fetch from a byte offset until the end, in which case
// byteRange would be (number, "until end"), or to fetch a range between two byte offsets, im which case byteRange would be a (number, number) // byteRange would be (number, "until end"), or to fetch a range between two byte offsets, in which case byteRange would be a (number, number)
// tuple representing the two offsets. // tuple representing the two offsets.
ByteRange byte_range = EntireResource {}; ByteRange byte_range = EntireResource {};

View file

@ -197,11 +197,11 @@ void HTMLMeterElement::create_shadow_tree_if_needed()
set_shadow_root(shadow_root); set_shadow_root(shadow_root);
auto meter_bar_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); auto meter_bar_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
meter_bar_element->set_use_pseudo_element(CSS::PseudoElement::Track); meter_bar_element->set_use_pseudo_element(CSS::PseudoElement::SliderTrack);
MUST(shadow_root->append_child(*meter_bar_element)); MUST(shadow_root->append_child(*meter_bar_element));
m_meter_value_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); m_meter_value_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
m_meter_value_element->set_use_pseudo_element(CSS::PseudoElement::Fill); m_meter_value_element->set_use_pseudo_element(CSS::PseudoElement::SliderFill);
MUST(meter_bar_element->append_child(*m_meter_value_element)); MUST(meter_bar_element->append_child(*m_meter_value_element));
update_meter_value_element(); update_meter_value_element();
} }

Some files were not shown because too many files have changed in this diff Show more