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
#endif
#ifndef IDB_DEBUG
# cmakedefine01 IDB_DEBUG
#endif
#ifndef IDL_DEBUG
# cmakedefine01 IDL_DEBUG
#endif

View file

@ -9,6 +9,7 @@
#include <LibCore/EventLoopImplementationWindows.h>
#include <LibCore/Notifier.h>
#include <LibCore/ThreadEventQueue.h>
#include <LibCore/Timer.h>
#include <AK/Windows.h>
@ -55,10 +56,12 @@ struct EventLoopTimer {
};
struct ThreadData {
static ThreadData& the()
static ThreadData* the()
{
thread_local OwnPtr<ThreadData> thread_data = make<ThreadData>();
return *thread_data;
if (thread_data)
return &*thread_data;
return nullptr;
}
ThreadData()
@ -76,7 +79,7 @@ struct ThreadData {
};
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)
{
auto& thread_data = ThreadData::the();
auto& notifiers = thread_data.notifiers;
auto& timers = thread_data.timers;
auto& event_queue = ThreadEventQueue::current();
auto* thread_data = ThreadData::the();
auto& notifiers = thread_data->notifiers;
auto& timers = thread_data->timers;
size_t event_count = 1 + notifiers.size() + timers.size();
// 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);
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)
event_handles.append(entry.key.handle);
for (auto& entry : timers)
event_handles.append(entry.key.handle);
DWORD result = WaitForMultipleObjects(event_count, event_handles.data(), FALSE, INFINITE);
size_t index = result - WAIT_OBJECT_0;
VERIFY(index < event_count);
if (index != 0) {
if (index <= notifiers.size()) {
Notifier* notifier = *notifiers.get(event_handles[index]);
ThreadEventQueue::current().post_event(*notifier, make<NotifierActivationEvent>(notifier->fd(), notifier->type()));
} else {
auto& timer = *timers.get(event_handles[index]);
if (auto strong_owner = timer.owner.strong_ref())
if (timer.fire_when_not_visible == TimerShouldFireWhenNotVisible::Yes || strong_owner->is_visible_for_timer_purposes())
ThreadEventQueue::current().post_event(*strong_owner, make<TimerEvent>());
bool has_pending_events = event_queue.has_pending_events();
int timeout = has_pending_events ? 0 : INFINITE;
DWORD result = WaitForMultipleObjects(event_count, event_handles.data(), FALSE, timeout);
if (result == WAIT_TIMEOUT) {
// FIXME: This verification sometimes fails with ERROR_INVALID_HANDLE, but when I check
// the handles they all seem to be valid.
// VERIFY(GetLastError() == ERROR_SUCCESS || GetLastError() == ERROR_IO_PENDING);
} else {
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 (timer.fire_when_not_visible == TimerShouldFireWhenNotVisible::Yes || strong_owner->is_visible_for_timer_purposes())
event_queue.post_event(*strong_owner, make<TimerEvent>());
}
}
}
}
return ThreadEventQueue::current().process();
return event_queue.process();
}
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()));
VERIFY(!rc);
auto& notifiers = ThreadData::the().notifiers;
auto& notifiers = ThreadData::the()->notifiers;
VERIFY(!notifiers.get(event).has_value());
notifiers.set(Handle(event), &notifier);
}
@ -175,13 +190,16 @@ void EventLoopManagerWindows::register_notifier(Notifier& notifier)
void EventLoopManagerWindows::unregister_notifier(Notifier& notifier)
{
// 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)
{
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);
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);
VERIFY(rc);
auto& timers = ThreadData::the().timers;
auto& timers = ThreadData::the()->timers;
VERIFY(!timers.get(timer).has_value());
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)
{
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)

View file

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

View file

@ -12,7 +12,6 @@
#include <LibIPC/Connection.h>
#include <LibIPC/Message.h>
#include <LibIPC/Stub.h>
#include <LibIPC/UnprocessedFileDescriptors.h>
namespace IPC {
@ -40,21 +39,16 @@ bool ConnectionBase::is_open() const
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,
// the socket will be closed. Don't try to send more messages.
if (!m_transport->is_open())
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));
m_responsiveness_timer->start();
@ -85,7 +79,7 @@ void ConnectionBase::handle_messages()
}
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());
}
}
@ -100,24 +94,11 @@ void ConnectionBase::wait_for_transport_to_become_readable()
ErrorOr<void> ConnectionBase::drain_messages_from_peer()
{
auto schedule_shutdown = m_transport->read_as_many_messages_as_possible_without_blocking([&](auto&& unparsed_message) {
auto const& bytes = unparsed_message.bytes;
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;
}
auto schedule_shutdown = m_transport->read_as_many_messages_as_possible_without_blocking([&](auto&& raw_message) {
if (auto message = try_parse_message(raw_message.bytes, raw_message.fds)) {
m_unprocessed_messages.append(message.release_nonnull());
} else {
dbgln("Failed to parse IPC message {:hex-dump}", bytes);
dbgln("Failed to parse IPC message {:hex-dump}", raw_message.bytes);
VERIFY_NOT_REACHED();
}
});

View file

@ -15,10 +15,6 @@
#include <LibIPC/Forward.h>
#include <LibIPC/Message.h>
#include <LibIPC/Transport.h>
#include <LibIPC/UnprocessedFileDescriptors.h>
#include <LibThreading/ConditionVariable.h>
#include <LibThreading/MutexProtected.h>
#include <LibThreading/Thread.h>
namespace IPC {
@ -30,7 +26,7 @@ public:
[[nodiscard]] bool is_open() const;
ErrorOr<void> post_message(Message const&);
ErrorOr<void> post_message(u32 endpoint_magic, MessageBuffer);
ErrorOr<void> post_message(MessageBuffer);
void shutdown();
virtual void die() { }
@ -43,7 +39,7 @@ protected:
virtual void may_have_become_unresponsive() { }
virtual void did_become_responsive() { }
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);
void wait_for_transport_to_become_readable();
@ -102,7 +98,7 @@ protected:
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);
if (!local_message.is_error())

View file

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

View file

@ -6,7 +6,6 @@
#include <AK/Checked.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <LibIPC/Message.h>
namespace IPC {
@ -47,53 +46,4 @@ ErrorOr<void> MessageBuffer::transfer_message(Transport& transport)
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
#include <AK/Error.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/Vector.h>
#include <LibCore/AnonymousBuffer.h>
#include <LibCore/Forward.h>
#include <LibCore/System.h>
#include <LibIPC/Transport.h>
#include <LibIPC/UnprocessedFileDescriptors.h>
namespace IPC {
@ -67,30 +61,4 @@ protected:
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 {
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)
: m_socket(move(socket))
{
m_send_queue = adopt_ref(*new SendQueue);
m_send_thread = Threading::Thread::construct([this, send_queue = m_send_queue]() -> intptr_t {
for (;;) {
send_queue->mutex.lock();
while (send_queue->messages.is_empty() && send_queue->running)
send_queue->condition.wait();
if (!send_queue->running) {
send_queue->mutex.unlock();
if (send_queue->block_until_message_enqueued() == SendQueue::Running::No)
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();
send_queue->mutex.unlock();
if (!remaining_to_send_bytes.is_empty() || !fds.is_empty()) {
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()) {
dbgln("TransportSocket::send_thread: {}", result.error());
if (!m_socket->is_open())
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;
@ -45,32 +99,32 @@ TransportSocket::TransportSocket(NonnullOwnPtr<Core::LocalSocket> socket)
TransportSocket::~TransportSocket()
{
{
Threading::MutexLocker locker(m_send_queue->mutex);
m_send_queue->running = false;
m_send_queue->condition.signal();
}
m_send_queue->stop();
(void)m_send_thread->join();
}
void TransportSocket::set_up_read_hook(Function<void()> hook)
{
Threading::RWLockLocker<Threading::LockMode::Write> lock(m_socket_rw_lock);
VERIFY(m_socket->is_open());
m_socket->on_ready_to_read = move(hook);
}
bool TransportSocket::is_open() const
{
Threading::RWLockLocker<Threading::LockMode::Read> lock(m_socket_rw_lock);
return m_socket->is_open();
}
void TransportSocket::close()
{
Threading::RWLockLocker<Threading::LockMode::Write> lock(m_socket_rw_lock);
m_socket->close();
}
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);
if (maybe_did_become_readable.is_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
{
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)
ErrorOr<void> TransportSocket::send_message(Core::LocalSocket& socket, ReadonlyBytes& bytes_to_write, Vector<int>& unowned_fds)
{
auto num_fds_to_transfer = unowned_fds.size();
while (!bytes_to_write.is_empty()) {
ErrorOr<ssize_t> maybe_nwritten = 0;
if (num_fds_to_transfer > 0) {
maybe_nwritten = socket.send_message(bytes_to_write, 0, unowned_fds);
if (!maybe_nwritten.is_error())
num_fds_to_transfer = 0;
} else {
maybe_nwritten = socket.write_some(bytes_to_write);
}
if (maybe_nwritten.is_error()) {
if (auto error = maybe_nwritten.release_error(); error.is_errno() && (error.code() == EAGAIN || error.code() == EWOULDBLOCK)) {
// 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());
}
if (auto error = maybe_nwritten.release_error(); error.is_errno() && (error.code() == EAGAIN || error.code() == EWOULDBLOCK || error.code() == EINTR)) {
return {};
} else {
return error;
}
}
bytes_to_write = bytes_to_write.slice(maybe_nwritten.value());
num_fds_to_transfer = 0;
unowned_fds.clear();
}
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;
while (is_open()) {
u8 buffer[4096];
@ -222,7 +248,7 @@ TransportSocket::ShouldShutdown TransportSocket::read_as_many_messages_as_possib
Message message;
received_fd_count += header.fd_count;
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);
callback(move(message));
} 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.type = MessageHeader::Type::FileDescriptorAcknowledgement;
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()) {
@ -267,11 +293,13 @@ TransportSocket::ShouldShutdown TransportSocket::read_as_many_messages_as_possib
ErrorOr<int> TransportSocket::release_underlying_transport_for_transfer()
{
Threading::RWLockLocker<Threading::LockMode::Write> lock(m_socket_rw_lock);
return m_socket->release_fd();
}
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());
}

View file

@ -9,9 +9,9 @@
#include <AK/Queue.h>
#include <LibCore/Socket.h>
#include <LibIPC/UnprocessedFileDescriptors.h>
#include <LibThreading/ConditionVariable.h>
#include <LibThreading/MutexProtected.h>
#include <LibThreading/RWLock.h>
#include <LibThreading/Thread.h>
namespace IPC {
@ -42,6 +42,31 @@ private:
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 {
AK_MAKE_NONCOPYABLE(TransportSocket);
AK_MAKE_NONMOVABLE(TransportSocket);
@ -66,9 +91,9 @@ public:
};
struct Message {
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.
ErrorOr<int> release_underlying_transport_for_transfer();
@ -76,30 +101,20 @@ public:
ErrorOr<IPC::File> clone_for_transfer();
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;
mutable Threading::RWLock m_socket_rw_lock;
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.
// 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
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<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())
return;
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);
}

View file

@ -1212,14 +1212,14 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
switch (kind) {
case Op::PropertyKind::Getter: {
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)));
object->define_direct_accessor(name, &function, nullptr, Attribute::Configurable | Attribute::Enumerable);
break;
}
case Op::PropertyKind::Setter: {
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)));
object->define_direct_accessor(name, nullptr, &function, Attribute::Configurable | Attribute::Enumerable);
break;

View file

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

View file

@ -39,8 +39,6 @@ BoundFunction::BoundFunction(Realm& realm, FunctionObject& bound_target_function
, m_bound_target_function(&bound_target_function)
, m_bound_this(bound_this)
, 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<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 has_constructor() const override { return m_bound_target_function->has_constructor(); }
@ -39,8 +38,6 @@ private:
GC::Ptr<FunctionObject> m_bound_target_function; // [[BoundTargetFunction]]
Value m_bound_this; // [[BoundThis]]
Vector<Value> m_bound_arguments; // [[BoundArguments]]
FlyString m_name;
};
}

View file

@ -426,10 +426,9 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(
, m_shared_data(move(shared_data))
, m_environment(parent_environment)
, m_private_environment(private_environment)
, m_realm(&prototype.shape().realm())
{
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().
m_script_or_module = vm().get_active_script_or_module();
@ -643,7 +642,6 @@ void ECMAScriptFunctionObject::visit_edges(Visitor& visitor)
Base::visit_edges(visitor);
visitor.visit(m_environment);
visitor.visit(m_private_environment);
visitor.visit(m_realm);
visitor.visit(m_home_object);
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.
// 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.
callee_context.function = this;
callee_context.function_name = m_name_string;
// 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.
callee_context.realm = callee_realm;
callee_context.realm = realm();
// 6. Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]].
callee_context.script_or_module = m_script_or_module;
@ -758,15 +742,7 @@ void ECMAScriptFunctionObject::ordinary_call_bind_this(ExecutionContext& callee_
return;
// 3. 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);
auto callee_realm = realm();
// 4. Let localEnv be the LexicalEnvironment of calleeContext.
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; }
[[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_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; }
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; }
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
GC::Ptr<Environment> m_environment; // [[Environment]]
GC::Ptr<PrivateEnvironment> m_private_environment; // [[PrivateEnvironment]]
GC::Ptr<Realm> m_realm; // [[Realm]]
ScriptOrModule m_script_or_module; // [[ScriptOrModule]]
GC::Ptr<Object> m_home_object; // [[HomeObject]]
struct ClassData {

View file

@ -26,8 +26,6 @@ public:
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 FlyString const& name() const = 0;
void set_function_name(Variant<PropertyKey, PrivateName> const& name_arg, Optional<StringView> const& prefix = {});
void set_function_length(double length);

View file

@ -19,7 +19,6 @@ public:
virtual ~FunctionPrototype() override = default;
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override;
virtual FlyString const& name() const override { return m_name; }
private:
explicit FunctionPrototype(Realm&);
@ -29,9 +28,6 @@ private:
JS_DECLARE_NATIVE_FUNCTION(call);
JS_DECLARE_NATIVE_FUNCTION(to_string);
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<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 has_constructor() const override { return false; }
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_bigint_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_proxy_object() 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();
}
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
{
if (property_key.is_symbol())

View file

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

View file

@ -897,10 +897,4 @@ void ProxyObject::visit_edges(Cell::Visitor& visitor)
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 FlyString const& name() const override;
virtual bool has_constructor() const override;
Object const& target() const { return m_target; }

View file

@ -759,13 +759,6 @@ void VM::pop_execution_context()
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)
{
// native function

View file

@ -926,6 +926,26 @@ ThrowCompletionOr<PropertyKey> Value::to_property_key(VM& vm) const
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
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).
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𝔽.
if (!isfinite(number) || number == 0)
return 0;
@ -951,42 +974,7 @@ ThrowCompletionOr<i32> Value::to_i32_slow_case(VM& vm) const
if (int32bit >= 2147483648.0)
int32bit -= 4294967296.0;
return static_cast<i32>(int32bit);
}
// 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));
#endif
}
// 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
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.
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.

View file

@ -22,9 +22,6 @@ public:
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; }
FunctionObject const& wrapped_target_function() const { return m_wrapped_target_function; }

View file

@ -25,9 +25,9 @@ public:
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:
@ -187,17 +187,18 @@ int TestSuite::run(Vector<NonnullRefPtr<TestCase>> const& tests)
m_current_test_result = TestResult::NotRun;
enable_reporting();
u64 total_time = 0;
AK::Duration total_time;
u64 sum_of_squared_times = 0;
u64 min_time = NumericLimits<u64>::max();
u64 max_time = 0;
AK::Duration min_time = AK::Duration::max();
AK::Duration max_time;
for (u64 i = 0; i < repetitions; ++i) {
TestElapsedTimer timer;
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;
sum_of_squared_times += iteration_time * iteration_time;
sum_of_squared_times += iteration_ms * iteration_ms;
min_time = min(min_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;
}
auto const total_time_ms = total_time.to_milliseconds();
if (repetitions != 1) {
double average = total_time / double(repetitions);
double average = total_time_ms / static_cast<double>(repetitions);
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)",
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 {
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()) {
m_benchtime += total_time;
m_bench_time += total_time;
benchmark_count++;
switch (m_current_test_result) {
@ -233,7 +240,7 @@ int TestSuite::run(Vector<NonnullRefPtr<TestCase>> const& tests)
break;
}
} else {
m_testtime += total_time;
m_test_time += total_time;
test_count++;
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).",
test_count,
benchmark_count,
global_timer.elapsed_milliseconds(),
m_testtime,
m_benchtime,
global_timer.elapsed_milliseconds() - (m_testtime + m_benchtime));
elapsed.to_truncated_milliseconds(),
m_test_time.to_truncated_milliseconds(),
m_bench_time.to_truncated_milliseconds(),
(elapsed - runtime).to_truncated_milliseconds());
if (test_count != 0) {
if (test_passed_count == test_count) {

View file

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

View file

@ -246,6 +246,18 @@ String URL::serialize_path() const
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
String URL::serialize(ExcludeFragment exclude_fragment) const
{

View file

@ -122,6 +122,7 @@ public:
}
String serialize_path() const;
ByteString file_path() const;
String serialize(ExcludeFragment = ExcludeFragment::No) const;
ByteString serialize_for_display() const;
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
{
auto icu_boundary = align_boundary(boundary);
if (!icu_boundary.has_value())
return {};
if (inclusive == Inclusive::Yes) {
if (static_cast<bool>(m_segmenter->isBoundary(*icu_boundary)))
return static_cast<size_t>(*icu_boundary);
if (static_cast<bool>(m_segmenter->isBoundary(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 {};
@ -104,15 +102,13 @@ public:
virtual Optional<size_t> next_boundary(size_t boundary, Inclusive inclusive) override
{
auto icu_boundary = align_boundary(boundary);
if (!icu_boundary.has_value())
return {};
if (inclusive == Inclusive::Yes) {
if (static_cast<bool>(m_segmenter->isBoundary(*icu_boundary)))
return static_cast<size_t>(*icu_boundary);
if (static_cast<bool>(m_segmenter->isBoundary(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 {};
@ -177,25 +173,25 @@ public:
}
private:
Optional<i32> align_boundary(size_t boundary)
i32 align_boundary(size_t boundary)
{
auto icu_boundary = static_cast<i32>(boundary);
return m_segmented_text.visit(
[&](String const& text) -> Optional<i32> {
[&](String const& text) {
if (boundary >= text.byte_count())
return {};
return static_cast<i32>(text.byte_count());
U8_SET_CP_START(text.bytes().data(), 0, icu_boundary);
return icu_boundary;
},
[&](icu::UnicodeString const& text) -> Optional<i32> {
[&](icu::UnicodeString const& text) {
if (icu_boundary >= text.length())
return {};
return text.length();
return text.getChar32Start(icu_boundary);
},
[](Empty) -> Optional<i32> { VERIFY_NOT_REACHED(); });
[](Empty) -> i32 { VERIFY_NOT_REACHED(); });
}
void for_each_boundary(SegmentationCallback callback)

View file

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

View file

@ -195,6 +195,7 @@ set(SOURCES
CSS/Time.cpp
CSS/Transformation.cpp
CSS/TransitionEvent.cpp
CSS/URL.cpp
CSS/VisualViewport.cpp
Cookie/Cookie.cpp
Cookie/ParsedCookie.cpp
@ -566,6 +567,7 @@ set(SOURCES
IndexedDB/IDBVersionChangeEvent.cpp
IndexedDB/Internal/Algorithms.cpp
IndexedDB/Internal/Database.cpp
IndexedDB/Internal/Index.cpp
IndexedDB/Internal/Key.cpp
IndexedDB/Internal/ObjectStore.cpp
IndexedDB/Internal/RequestList.cpp
@ -689,6 +691,7 @@ set(SOURCES
Painting/SVGPaintable.cpp
Painting/SVGSVGPaintable.cpp
Painting/ScrollFrame.cpp
Painting/ScrollState.cpp
Painting/ShadowPainting.cpp
Painting/StackingContext.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) };
// 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
VERIFY(!owning_element() == !other->owning_element());

View file

@ -9,7 +9,6 @@
#include <AK/Debug.h>
#include <AK/ScopeGuard.h>
#include <LibTextCodec/Decoder.h>
#include <LibURL/URL.h>
#include <LibWeb/Bindings/CSSImportRulePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSImportRule.h>
@ -17,23 +16,23 @@
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOMURL/DOMURL.h>
#include <LibWeb/HTML/Window.h>
namespace Web::CSS {
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>(move(url), document, supports, move(media_query_list));
return realm.create<CSSImportRule>(realm, move(url), document, move(supports), move(media_query_list));
}
CSSImportRule::CSSImportRule(URL::URL url, DOM::Document& document, RefPtr<Supports> supports, Vector<NonnullRefPtr<MediaQuery>> media_query_list)
: CSSRule(document.realm(), Type::Import)
CSSImportRule::CSSImportRule(JS::Realm& realm, URL url, GC::Ptr<DOM::Document> document, RefPtr<Supports> supports, Vector<NonnullRefPtr<MediaQuery>> media_query_list)
: CSSRule(realm, Type::Import)
, m_url(move(url))
, m_document(document)
, m_supports(supports)
, m_supports(move(supports))
, m_media_query_list(move(media_query_list))
{
}
@ -57,7 +56,10 @@ void CSSImportRule::set_parent_style_sheet(CSSStyleSheet* parent_style_sheet)
// Crude detection of whether we're already fetching.
if (m_style_sheet || m_document_load_event_delayer.has_value())
return;
fetch();
// Only try to fetch if we now have a parent
if (parent_style_sheet)
fetch();
}
// https://www.w3.org/TR/cssom/#serialize-a-css-rule
@ -70,7 +72,7 @@ String CSSImportRule::serialized() const
builder.append("@import "sv);
// 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.
// 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.
// If the algorithm returns an error, return. [CSSOM]
// FIXME: Stop producing a URL::URL when parsing the @import
auto parsed_url = url().to_string();
auto parsed_url = DOMURL::parse(href(), parent_style_sheet.location());
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.
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:
fetch_a_style_resource(parsed_url, 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) {
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 }, parsed_url = parsed_url.value()](auto response, auto maybe_byte_stream) {
// AD-HOC: Stop delaying the load event.
ScopeGuard guard = [strong_this] {
strong_this->m_document_load_event_delayer.clear();
@ -139,19 +144,19 @@ void CSSImportRule::fetch()
auto encoding = "utf-8"sv;
auto maybe_decoder = TextCodec::decoder_for(encoding);
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;
}
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);
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;
}
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.
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-2024, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -8,9 +8,9 @@
#pragma once
#include <LibURL/URL.h>
#include <LibWeb/CSS/CSSRule.h>
#include <LibWeb/CSS/CSSStyleSheet.h>
#include <LibWeb/CSS/URL.h>
#include <LibWeb/DOM/DocumentLoadEventDelayer.h>
namespace Web::CSS {
@ -21,13 +21,12 @@ class CSSImportRule final
GC_DECLARE_ALLOCATOR(CSSImportRule);
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;
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.to_string(); }
URL const& url() const { return m_url; }
String href() const { return m_url.url(); }
CSSStyleSheet* loaded_style_sheet() { return m_style_sheet; }
CSSStyleSheet const* loaded_style_sheet() const { return m_style_sheet; }
@ -37,7 +36,7 @@ public:
Optional<String> supports_text() const;
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 visit_edges(Cell::Visitor&) override;
@ -49,7 +48,7 @@ private:
void fetch();
void set_style_sheet(GC::Ref<CSSStyleSheet>);
URL::URL m_url;
URL m_url;
GC::Ptr<DOM::Document> m_document;
RefPtr<Supports> m_supports;
Vector<NonnullRefPtr<MediaQuery>> m_media_query_list;

View file

@ -6,7 +6,8 @@
[Exposed=Window]
interface CSSImportRule : CSSRule {
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;
[FIXME] readonly attribute CSSOMString? layerName;
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.
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 {};
}

View file

@ -24,7 +24,7 @@ namespace Web::CSS {
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));
}
@ -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.
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.
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())
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.
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())
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;
}
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)
, m_rules(&rules)
{
if (location.has_value())
set_location(location->to_string());
set_location(move(location));
for (auto& rule : *m_rules)
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);
// 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(context, rule);
auto parsed_rule = parse_css_rule(make_parsing_params(), rule);
// 4. If parsed rule is a syntax error, return 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 };
// 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(context, text);
auto* parsed_stylesheet = parse_css_stylesheet(make_parsing_params(), text);
auto& rules = parsed_stylesheet->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);
// 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(context, text);
auto* parsed_stylesheet = parse_css_stylesheet(make_parsing_params(), text);
auto& rules = parsed_stylesheet->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;
}
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/CSSRuleList.h>
#include <LibWeb/CSS/CSSStyleRule.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleSheet.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/WebIDL/Types.h>
@ -27,12 +28,13 @@ struct CSSStyleSheetInit {
bool disabled { false };
};
// https://drafts.csswg.org/cssom-1/#cssstylesheet
class CSSStyleSheet final : public StyleSheet {
WEB_PLATFORM_OBJECT(CSSStyleSheet, StyleSheet);
GC_DECLARE_ALLOCATOR(CSSStyleSheet);
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 = {});
virtual ~CSSStyleSheet() override = default;
@ -74,8 +76,8 @@ public:
Vector<GC::Ref<CSSImportRule>> const& import_rules() const { return m_import_rules; }
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); }
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); }
bool constructed() const { return m_constructed; }
@ -94,7 +96,7 @@ public:
bool has_associated_font_loader(FontLoader& font_loader) const;
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 visit_edges(Cell::Visitor&) override;
@ -104,6 +106,8 @@ private:
void set_constructed(bool constructed) { m_constructed = constructed; }
void set_disallow_modification(bool disallow_modification) { m_disallow_modification = disallow_modification; }
Parser::ParsingParams make_parsing_params() const;
Optional<String> m_source_text;
GC::Ptr<CSSRuleList> m_rules;
@ -113,7 +117,7 @@ private:
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;
HashTable<GC::Ptr<DOM::Node>> m_owning_documents_or_shadow_roots;
bool m_constructed { false };

View file

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

View file

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

View file

@ -586,8 +586,13 @@ NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, Calc
layout_node = *node;
return CSSColorValue::create_from_color(interpolate_color(from.to_color(layout_node), to.to_color(layout_node), delta), ColorSyntax::Modern);
}
case CSSStyleValue::Type::Integer:
return IntegerStyleValue::create(interpolate_raw(from.as_integer().integer(), to.as_integer().integer(), delta));
case CSSStyleValue::Type::Integer: {
// 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: {
// FIXME: Absolutize values
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 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
// 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

View file

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

View file

@ -19,7 +19,7 @@ namespace Web::CSS {
class ParsedFontFace {
public:
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?
Optional<FlyString> format;
};

View file

@ -42,7 +42,7 @@ GC::Ref<JS::Realm> internal_css_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()) {
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)
, url(move(url))
, 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()))
, document(&document)
, url(move(url))
@ -86,7 +86,7 @@ Parser::Parser(ParsingParams const& context, Vector<Token> tokens)
// https://drafts.csswg.org/css-syntax/#parse-stylesheet
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:
@ -119,10 +119,10 @@ Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<T>& input)
}
// 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.
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.
GC::RootVector<CSSRule*> rules(realm().heap());
@ -1772,8 +1772,8 @@ Parser::ContextType Parser::context_type_for_at_rule(FlyString const& name)
return ContextType::Unknown;
}
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<Token>&, 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<ComponentValue>& input);
@ -1853,10 +1853,10 @@ bool Parser::is_parsing_svg_presentation_attribute() const
// https://www.w3.org/TR/css-values-4/#relative-urls
// 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())
return URL::Parser::basic_parse(relative_url);
return ::URL::Parser::basic_parse(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/CalculatedStyleValue.h>
#include <LibWeb/CSS/Supports.h>
#include <LibWeb/CSS/URL.h>
#include <LibWeb/Forward.h>
namespace Web::CSS::Parser {
@ -69,13 +70,13 @@ enum class ParsingMode {
struct ParsingParams {
explicit ParsingParams(ParsingMode = ParsingMode::Normal);
explicit ParsingParams(JS::Realm&, ParsingMode = ParsingMode::Normal);
explicit ParsingParams(JS::Realm&, URL::URL, ParsingMode = ParsingMode::Normal);
explicit ParsingParams(DOM::Document const&, 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&, ParsingMode = ParsingMode::Normal);
GC::Ptr<JS::Realm> realm;
GC::Ptr<DOM::Document const> document;
URL::URL url;
::URL::URL url;
ParsingMode mode { ParsingMode::Normal };
};
@ -89,7 +90,7 @@ class Parser {
public:
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 {
Vector<StyleProperty> properties;
@ -142,11 +143,11 @@ private:
// "Parse a stylesheet" is intended to be the normal parser entry point, for parsing stylesheets.
struct ParsedStyleSheet {
Optional<URL::URL> location;
Optional<::URL::URL> location;
Vector<Rule> rules;
};
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.
template<typename T>
@ -276,7 +277,7 @@ private:
Optional<GridRepeat> parse_repeat(Vector<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>&);
Optional<ShapeRadius> parse_shape_radius(TokenStream<ComponentValue>&);
@ -471,11 +472,11 @@ private:
JS::Realm& realm() const;
bool in_quirks_mode() 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<JS::Realm> m_realm;
URL::URL m_url;
::URL::URL m_url;
ParsingMode m_parsing_mode { ParsingMode::Normal };
Vector<Token> m_tokens;
@ -519,7 +520,7 @@ private:
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);
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);

View file

@ -153,9 +153,9 @@ GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
TokenStream tokens { rule.prelude };
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))
url = complete_url(tokens.consume_a_token().token().string());
url = URL { tokens.consume_a_token().token().string().to_string() };
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());
@ -191,7 +191,7 @@ GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
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)
@ -435,7 +435,10 @@ GC::Ptr<CSSNamespaceRule> Parser::convert_to_namespace_rule(AtRule const& rule)
FlyString namespace_uri;
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)) {
namespace_uri = url_token.token().string();
} else {

View file

@ -15,7 +15,6 @@
#include <AK/Debug.h>
#include <AK/GenericLexer.h>
#include <AK/TemporaryChange.h>
#include <LibURL/URL.h>
#include <LibWeb/CSS/FontFace.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/PropertyName.h>
@ -2014,9 +2013,13 @@ RefPtr<AbstractImageStyleValue> Parser::parse_image_value(TokenStream<ComponentV
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,
// 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)) {
tokens.discard_a_mark();
return ImageStyleValue::create(url.value());
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();
return ImageStyleValue::create(completed_url.release_value());
}
}
tokens.restore_a_mark();
return nullptr;
@ -2562,24 +2565,16 @@ RefPtr<CSSStyleValue> Parser::parse_easing_value(TokenStream<ComponentValue>& to
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& 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 {};
};
auto const& component_value = tokens.consume_a_token();
if (component_value.is(Token::Type::Url)) {
auto url_string = component_value.token().url();
return convert_string_to_url(url_string);
transaction.commit();
return URL { component_value.token().url().to_string() };
}
if (component_value.is_function("url"sv)) {
auto const& function_values = component_value.function().value;
// 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))
continue;
if (value.is(Token::Type::String)) {
auto url_string = value.token().string();
return convert_string_to_url(url_string);
transaction.commit();
return URL { value.token().string().to_string() };
}
break;
}
@ -2603,7 +2598,11 @@ RefPtr<CSSStyleValue> Parser::parse_url_value(TokenStream<ComponentValue>& token
auto url = parse_url_function(tokens);
if (!url.has_value())
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
@ -3681,7 +3680,11 @@ RefPtr<FontSourceStyleValue> Parser::parse_font_source_value(TokenStream<Compone
// <url> [ format(<font-format>)]? [ tech( <font-tech>#)]?
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;
Optional<FlyString> format;
@ -3719,7 +3722,7 @@ RefPtr<FontSourceStyleValue> Parser::parse_font_source_value(TokenStream<Compone
// FIXME: [ tech( <font-tech>#)]?
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)

View file

@ -1,33 +1,33 @@
{
"-moz-meter-bar": {
"alias-for": "fill"
"alias-for": "slider-fill"
},
"-moz-progress-bar": {
"alias-for": "fill"
"alias-for": "slider-fill"
},
"-moz-range-progress": {
"alias-for": "fill"
"alias-for": "slider-fill"
},
"-moz-range-track": {
"alias-for": "track"
"alias-for": "slider-track"
},
"-moz-range-thumb": {
"alias-for": "thumb"
"alias-for": "slider-thumb"
},
"-webkit-meter-bar": {
"alias-for": "track"
"alias-for": "slider-track"
},
"-webkit-progress-bar": {
"alias-for": "track"
"alias-for": "slider-track"
},
"-webkit-progress-value": {
"alias-for": "fill"
"alias-for": "slider-fill"
},
"-webkit-slider-runnable-track": {
"alias-for": "track"
"alias-for": "slider-track"
},
"-webkit-slider-thumb": {
"alias-for": "thumb"
"alias-for": "slider-thumb"
},
"after": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-after",
@ -47,9 +47,6 @@
"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": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-first-letter",
"property-whitelist": [
@ -107,11 +104,14 @@
"#custom-properties"
]
},
"thumb": {
"spec": "https://drafts.csswg.org/css-forms-1/#selectordef-thumb"
"slider-fill": {
"spec": "https://drafts.csswg.org/css-forms-1/#selectordef-slider-fill"
},
"track": {
"spec": "https://drafts.csswg.org/css-forms-1/#selectordef-track"
"slider-thumb": {
"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": {
"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;
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_family_name(move(family_name))
, 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),
};
Vector<URL::URL> urls;
Vector<::URL::URL> urls;
for (auto const& source : font_face.sources()) {
// FIXME: These should be loaded relative to the stylesheet URL instead of the document 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()));
if (source.local_or_url.has<::URL::URL>())
urls.append(*m_document->encoding_parse_url(source.local_or_url.get<::URL::URL>().to_string()));
// FIXME: Handle local()
}

View file

@ -315,7 +315,7 @@ private:
class FontLoader : public ResourceClient {
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;
@ -340,7 +340,7 @@ private:
FlyString m_family_name;
Vector<Gfx::UnicodeRange> m_unicode_ranges;
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()> m_on_fail;
};

View file

@ -27,6 +27,13 @@ void StyleSheet::visit_edges(Cell::Visitor& visitor)
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)
{
m_owner_node = element;

View file

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

View file

@ -94,7 +94,8 @@ Optional<Gfx::ImageCursor> CursorStyleValue::make_image_cursor(Layout::NodeWithS
case DisplayListPlayerType::SkiaCPU: {
auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(bitmap);
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;
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -14,14 +14,14 @@ namespace Web::CSS {
class URLStyleValue final : public StyleValueWithDefaultOperators<URLStyleValue> {
public:
static ValueComparingNonnullRefPtr<URLStyleValue> create(URL::URL const& url)
static ValueComparingNonnullRefPtr<URLStyleValue> create(::URL::URL const& url)
{
return adopt_ref(*new (nothrow) URLStyleValue(url));
}
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; }
@ -31,13 +31,13 @@ public:
}
private:
URLStyleValue(URL::URL const& url)
URLStyleValue(::URL::URL const& url)
: StyleValueWithDefaultOperators(Type::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/DataView.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Crypto/CryptoAlgorithms.h>
#include <LibWeb/Crypto/KeyAlgorithms.h>

View file

@ -11,6 +11,7 @@
#include <LibCrypto/Hash/HashManager.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/JSONObject.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.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
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.
if (aborted())
return;
return {};
// 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
@ -76,8 +84,8 @@ void AbortSignal::signal_abort(JS::Value reason)
// https://dom.spec.whatwg.org/#run-the-abort-steps
auto run_the_abort_steps = [](auto& signal) {
// 1. For each algorithm in signals abort algorithms: run algorithm.
for (auto& algorithm : signal.m_abort_algorithms)
algorithm->function()();
for (auto const& algorithm : signal.m_abort_algorithms)
algorithm.value->function()();
// 2. Empty signals abort algorithms.
signal.m_abort_algorithms.clear();

View file

@ -7,6 +7,7 @@
#pragma once
#include <AK/HashMap.h>
#include <AK/RefCounted.h>
#include <AK/Weakable.h>
#include <LibGC/Function.h>
@ -26,7 +27,9 @@ public:
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
// 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() };
// https://dom.spec.whatwg.org/#abortsignal-abort-algorithms
// FIXME: This should be a set.
Vector<GC::Ref<GC::Function<void()>>> m_abort_algorithms;
OrderedHashMap<AbortAlgorithmID, GC::Ref<GC::Function<void()>>> m_abort_algorithms;
AbortAlgorithmID m_next_abort_algorithm_id { 0 };
// 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.

View file

@ -584,7 +584,8 @@ void Document::visit_edges(Cell::Visitor& visitor)
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_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)
{
if (style_sheet.location() == url)
if (style_sheet.href() == url)
return style_sheet;
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)
{
m_shadow_roots.remove_all_matching([&](auto& item) {
return item.ptr() == &shadow_root;
});
m_shadow_roots.remove(shadow_root);
}
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
{
for (auto& shadow_root : m_shadow_roots)
callback(shadow_root);
callback(const_cast<ShadowRoot&>(shadow_root));
}
bool Document::is_decoded_svg() const
@ -6297,8 +6296,9 @@ void Document::invalidate_display_list()
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;
}
auto display_list = Painting::DisplayList::create();
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);
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_paint_config = config;

View file

@ -26,6 +26,7 @@
#include <LibWeb/CSS/StyleSheetList.h>
#include <LibWeb/Cookie/Cookie.h>
#include <LibWeb/DOM/ParentNode.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/CrossOrigin/OpenerPolicy.h>
#include <LibWeb/HTML/DocumentReadyState.h>
@ -1180,7 +1181,7 @@ private:
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;

View file

@ -448,7 +448,7 @@ GC::Ptr<DOM::Document> load_document(HTML::NavigationParams const& navigation_pa
// sourceSnapshotParams, and initiatorOrigin.
}
// -> A supported image, video, or audio type
// -> a supported image, video, or audio type
if (type.is_image()
|| type.is_audio_or_video()) {
// 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:
// 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".
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);
[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 {

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:
if (listener.signal) {
// 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.
remove_an_event_listener(listener);
});

View file

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

View file

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

View file

@ -97,6 +97,11 @@ private:
GC::Ptr<CSS::StyleSheetList> m_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<>

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,
// 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)
return;

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/TreeWalkerPrototype.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)
{
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)

View file

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

View file

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

View file

@ -103,17 +103,17 @@ GC::Ref<DOMRect> DOMQuad::get_bounds() const
}
// 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();
// 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.
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.
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.
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 {};
}

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)
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.
m_path.cubic_bezier_curve_to(
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
// 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.
// 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) {
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"]:
signal->add_abort_algorithm([close_watcher] {
(void)signal->add_abort_algorithm([close_watcher] {
// 3.2.1 Destroy closeWatcher.
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
// 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);
if (year_string.length() < 4)
return {};
@ -252,8 +252,8 @@ static Optional<YearAndMonth> parse_a_month_component(GenericLexer& input)
return {};
// 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
// number be the month.
// exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. Let month
// be that number.
auto month_string = input.consume_while(is_ascii_digit);
if (month_string.length() != 2)
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
// 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);
if (year_string.length() < 4)
return {};
@ -325,8 +325,8 @@ Optional<WeekYearAndWeek> parse_a_week_string(StringView input_view)
return {};
// 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
// number be the week.
// exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. Let week
// be that number.
auto week_string = input.consume_while(is_ascii_digit);
if (week_string.length() != 2)
return {};
@ -365,8 +365,8 @@ static Optional<YearMonthDay> parse_a_date_component(GenericLexer& input)
return {};
// 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
// number be the day.
// exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. Let day
// be that number.
auto day_string = input.consume_while(is_ascii_digit);
if (day_string.length() != 2)
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
// 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);
if (hour_string.length() != 2)
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
// 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);
if (minute_string.length() != 2)
return {};

View file

@ -389,7 +389,7 @@ void EventSource::process_field(StringView field, StringView value)
{
// -> If the field name is "event"
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));
}
// -> 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;
// - element's node document is not fully active;
// - 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,
// then:
// 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)));
}
// 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.
double normal_line_height = 1.0;
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);
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));
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));
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));
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
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
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());
}
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 {};
}
@ -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.
TRY(set_value(covert_date_to_string(date)));
TRY(set_value(convert_date_to_string(date)));
return {};
}

View file

@ -289,7 +289,7 @@ private:
String convert_number_to_string(double 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> max() const;

View file

@ -280,7 +280,7 @@ HTMLLinkElement::LinkProcessingOptions HTMLLinkElement::create_link_options()
options.policy_container = document.policy_container();
// 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
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);
if (m_loaded_style_sheet) {
Optional<String> location;
Optional<::URL::URL> location;
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(
"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
// 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())
candiate_src = *maybe_src;
candidate_src = *maybe_src;
if (candiate_src.is_empty()) {
if (candidate_src.is_empty()) {
TRY(failed_with_elements());
return {};
}
@ -649,7 +649,7 @@ public:
// 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
// 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
// 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
// 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
// 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.
ByteRange byte_range = EntireResource {};

View file

@ -197,11 +197,11 @@ void HTMLMeterElement::create_shadow_tree_if_needed()
set_shadow_root(shadow_root);
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));
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));
update_meter_value_element();
}

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