Previously, `AnonymousBuffer::create_with_size(0)` returned an error
because POSIX `mmap` rejects a zero length with `EINVAL`, and Windows
`CreateFileMapping` rejects a zero maximum size for an anonymous
mapping. This caused a crash when using `--headless=text` with zero
size pages like `about:blank`.
This is the closest Windows equivalent to integrating process exit
handlers into the event loop.
Linux could also integrate register_process() into the poll() based
Unix event loop via the SYS_pidfd_open syscall; however, macOS requires
a kqueue. So for now register_process will only be used by Windows to
implement WebView::ProcessMonitor.
Co-authored-by: Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
Bug: Ladybird crashes on launch on macOS 26.4+ with “UNEXPECTED ERROR:
close: Bad file descriptor” in SharedVersion.cpp — because
AnonymousBuffer creation fails.
Cause: anon_create() passed O_CLOEXEC in the oflag argument to shm_open.
macOS 26.4+ rejects that with EINVAL. POSIX only specifies O_RDONLY,
O_RDWR, O_CREAT, O_EXCL, and O_TRUNC as valid shm_open flags; on earlier
macOS versions, O_CLOEXEC just happened to also be silently accepted.
Fix: Filter O_CLOEXEC from the oflag argument we pass to shm_open flags,
and instead set FD_CLOEXEC via fcntl — after opening.
Move MachPortServer from LibWebView into LibIPC as MachBootstrapListener
and move the Mach message structs from MachMessageTypes.h into LibIPC.
These types are IPC infrastructure, not UI or platform concerns.
Consolidating them in LibIPC keeps the Mach bootstrap handshake
self-contained in a single library and removes LibWebView's dependency
on LibThreading.
Now that LibIPC uses Mach ports for transport on macOS, IOSurface port
rights can be sent as regular IPC message attachments instead of through
a separate ad-hoc Mach message side-channel. Introduce
Web::SharedBackingStore that wraps either a MachPort (macOS) or
ShareableBitmap (other platforms) with IPC encode/decode support,
unifying backing store allocation into the existing
did_allocate_backing_stores IPC message.
On macOS, use Mach port messaging instead of Unix domain sockets for
all IPC transport. This makes the transport capable of carrying Mach
port rights as message attachments, which is a prerequisite for sending
IOSurface handles over the main IPC channel (currently sent via a
separate out-of-band path). It also avoids the need for the FD
acknowledgement protocol that TransportSocket requires, since Mach port
right transfers are atomic in the kernel.
Three connection establishment patterns:
- Spawned helper processes (WebContent, RequestServer, etc.) use the
existing MachPortServer: the child sends its task port with a reply
port, and the parent responds with a pre-created port pair.
- Socket-bootstrapped connections (WebDriver, BrowserProcess) exchange
Mach port names over the socket, then drop the socket.
- Pre-created pairs for IPC tests and in-message transport transfer.
Attachment on macOS now wraps a MachPort instead of a file descriptor,
converting between the two via fileport_makeport()/fileport_makefd().
The LibIPC socket transport tests are disabled on macOS since they are
socket-specific.
On macOS, Objective-C methods frequently return autoreleased objects
that accumulate until an autorelease pool is drained. Our event loop
(Core::EventLoop) and rendering thread both lacked autorelease pools,
causing unbounded accumulation of autoreleased objects.
The rendering thread was the worst offender: every Skia flush triggers
Metal resource allocation which sets labels on GPU textures via
-[IOGPUMetalResource setLabel:], creating autoreleased CFData objects.
With ~1M+ such objects at 112 bytes each, this leaked ~121MB. Metal
command buffer objects (_MTLCommandBufferEncoderInfo, etc.) also
accumulated, adding another ~128MB.
Add Core::ScopedAutoreleasePool, a RAII wrapper around the ObjC runtime
autorelease pool (no-op on non-macOS), and drain it:
- Every event loop pump (like NSRunLoop does)
- Every compositor loop iteration on the rendering thread
handle_signal() called ThreadData::the() which can acquire a write
lock on s_thread_data_lock if the thread hasn't initialized its
ThreadData yet. If the signal interrupts a thread that already holds
a read lock on s_thread_data_lock (e.g. in unregister_notifier()),
this deadlocks — the write lock waits for the read lock, but the
read lock holder is blocked in the signal handler.
Fix by accessing the thread-local s_this_thread_data directly. If
the thread has no ThreadData, there's no wake pipe to write to, so
we just return.
The generic unlock() wrote to m_write_locked from every thread
regardless of whether a read or write lock was held. When multiple
threads held concurrent read locks, their unlock() calls would race
on the non-atomic m_write_locked and m_read_locked_with_write_lock
fields.
Split unlock() into unlock_read() and unlock_write() so that read
unlocks never touch the write-lock tracking fields. The RWLockLocker
template dispatches at compile time based on LockMode.
Inheriting from AtomicRefCounted and Weakable is asking for misuse of
WeakPtr to result in TOCTOU-caused UAFs.
In order to ensure we're not misusing EventReceiver across threads, I
ran test-web and some sites with a temporary hack to verify that ref()s
and unref()s are always called from the same thread on the class.
We were holding a reference to ThreadData, which could be destroyed
earlier than the EventLoop itself, causing other threads to UAF trying
to signal a wake.
If exit() is called on a thread with an EventLoop in the stack, the
ThreadData storing the array of wake pipes will be destroyed first.
Threads can still take a strong reference to the EventLoop after that,
and will read the fds from freed memory.
Instead, take a copy of the write fd, and swallow EBADF when writing to
it, since that only indicates that the thread and event loop are
exiting, so there's nothing to do with the wake.
This will allow sharing e.g. document cookie versions between the UI and
WebContent processes, and safely accessing those versions.
Core::AnonymousBuffer internally creates a minimum buffer of PAGE_SIZE
bytes. This is much more than the size of a single version, but this
affords us the opportunity to share multiple versions in a single buffer
between processes. With a PAGE_SIZE of 4096, we can share up to 512
versions in a single buffer.
This commit stops using deprecated WSA functions. While the ANSI
versions are most likely not going anywhere, Windows is natively UTF-16
so it has to convert to ANSI internally. All the ANSI functions in
Winsock are marked as deprecated. The macro suppressing the warnings is
no longer defined.
Add a thread-safe deferred_invoke() API on WeakEventLoopReference that
queues work onto the owning thread's event queue and wakes that thread
via EventLoopManager hooks. This avoids calling wake() from foreign
threads during teardown.
Implement current_thread_handle()/wake_thread() in each backend and
track per-thread data so handles are validated before waking:
- Unix: wake via per-thread wake pipe
- Windows: wake via thread wake event
- macOS: wake via stored CFRunLoopRef
- Qt: wake via event target or QEventLoop::wakeUp()
- Android: wake via stored ALooper