Keep the outgoing front bitmap as the backup image before replacing the
WebContent process for cross-site navigation. This lets the view keep
painting the old page until the new process presents its first frame,
rather than falling back to a flat background during loading.
Remember the preferred color scheme selected through the debug menu when
choosing the web view fallback background. Keep using an explicit forced
scheme even if the platform theme changes underneath it.
Seed the page background fallback from the platform color scheme before
WebContent has painted. Update the same fallback when the platform theme
changes, and when the preferred color scheme is forced through the debug
menu, so unpainted web view areas use the matching Canvas color.
Add IPC structures and requests for inspecting grid layout data from
WebContent.
WebView forwards the async replies to LibDevTools and exposes the grid
highlighter hooks used by the protocol layer.
Parse Firefox grid highlighter options in WebContent and store them with
the active grid highlight.
Send hidden and visible system visibility updates from explicit tab
switch paths instead of relying only on widget show and hide events. Tab
containers can keep inactive web views mapped, which leaves documents
visible from WebContent's perspective and lets animated images keep
running in background tabs.
Use AppKit window occlusion state rather than the app-wide active tab.
That keeps still-visible background windows in the visible document
state. Avoid resending duplicate visibility updates from
ViewImplementation so existing widget visibility notifications remain
harmless.
Track whether WebContent still needs a beforeunload check and let the
frontends immediately remove a tab or window when no prompt can be
shown. WebContent still receives the close request so pagehide, unload,
and cleanup steps can run.
When the visible view is removed immediately, keep detached ownership of
the WebContent page until it reports that the top-level traversable
closed. If no acknowledgement arrives, release detached ownership and
ask ProcessManager to shut down the unused WebContent process.
Tell the compositor that viewport updates from handle_resize are part of
a resize operation. This keeps the compositor backing store manager on
its padded live-resize path instead of forcing an exact-size backing
store allocation for every browser window resize tick.
Remove the command-line option that allowed Browser to start
WebContent with the in-process compositor thread instead of the
Compositor helper process. The compositor process is now the default
path, so the opt-out flag and the matching WebContent selector only kept
the old thread mode reachable.
With that option gone, simplify Browser and WebContent startup to wire
each WebContent process to the Compositor process directly. The old
local Browser-to-WebContent compositor IPC setup and its fallback input
and ready-to-paint branches are no longer needed.
Notify the UI process with the solid canvas background color recorded
for the top-level document. This is the Canvas system color with the
effective document background composited over it, matching the color
used before normal painting.
Store that color on the view and use it when AppKit, Qt, and Gtk need
to fill areas exposed while an older bitmap is still on screen during a
window resize.
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
We are moving toward an architecture where the browser owns a single
process that holds the GPU context, so Skia resources can be shared
across every renderer and WebContent processes can be sandboxed away
from direct GPU access. A dedicated Compositor helper process is the
first step. The earlier commits laid the foundation; this one turns the
helper on as a selectable backend, gated behind
--enable-compositor-process so the existing in-process path stays the
shipping default.
Default topology -- one compositor per WebContent, in-process:
+---------+
| Browser |
+---------+
/ | \
v v v
+---+ +---+ +---+ each WebContent runs its own
|WC | |WC | |WC | CompositorThread on a dedicated
|+C+| |+C+| |+C+| thread, with its own GPU context.
+---+ +---+ +---+
Opt-in topology -- one single-threaded Compositor, shared by all WCs:
+---------+ control +-------------------+
| Browser |<----------->| Compositor |
+---------+ | (single thread, |
/ | \ | single GPU ctx, |
v v v data | shared by all |
[WC] [WC] [WC] ---------->| connected WCs) |
+-------------------+
Three IPC channels carry the work in the opt-in topology. The
existing Browser<->WebContent channel gains context allocation.
A new Browser<->Compositor channel carries context lifetime,
viewport, UI input, and presentation acks plus backing-store and
frame upcalls. A new WebContent<->Compositor channel carries
display lists, scroll state, video, compositor surfaces, async
scrolling, presentation, and screenshots, with upcalls for
delegated input and compositor loss.
Add a repeatable blocker-list option that reads local list files in the
browser process. The files are concatenated into one buffer and shared
with WebContent through the content blocker IPC path when view options
are applied.
Parse the buffer in WebContent and reject malformed UTF-8 without
replacing the currently installed rules.
Load the browser-generated crash page as a synthetic response for the
URL that was active when WebContent exited. This keeps the session
history entry, response URL, and created Document aligned with the same
navigation URL, so reload targets the original page without creating a
local document for an HTTP(S) history entry.
Suppress history metadata updates from the generated page and declare
an inert rel=icon. Fallback favicon loading now follows the HTML
condition that no link with the icon keyword exists, which avoids the
credentialed /favicon.ico request from the crashed origin.
Route mouse events over the compositor IPC path while async scrolling is
enabled, and let the compositor capture viewport scrollbar drags before
the main-thread event path sees them. Dragging mutates the compositor
scroll tree and schedules an async-scroll present, so the viewport thumb
and content can move without a main-thread round trip.
Keep normal main-thread event handling for misses and preserve the wheel
bypass behavior for page events.
The synchronous wheel path treats wheel deltas as CSS-pixel scroll
distances, while the async compositor scroll tree mutates scroll state
stored in device pixels. Passing the same unscaled delta into the
compositor made async scrolling advance too little whenever
device pixels per CSSPixel was greater than 1, so scrolling felt slower
than with async scrolling disabled.
Convert wheel deltas before crossing the compositor boundary and make
the compositor IPC carry only the device-pixel position and delta it
needs. This keeps AsyncScrollTree device-pixel native and makes async
viewport scrolling match the synchronous path across high-DPI displays
and page zoom levels.
Trace the compositor state transitions that matter once presentation and
async scrolling share one path: page registration, backing-store swaps,
did_paint delivery, ready_to_paint acknowledgements, wheel admission,
and deferred presents waiting for reusable bitmaps.
Keep the logs under COMPOSITOR_DEBUG and use the [Compositor] prefix
consistently. The trace is about ownership and backpressure instead of
dumping every wheel event detail, so stalls can be diagnosed without
reintroducing noisy async-scrolling-only logging.
Send the experimental UI-process wheel bypass to WebContent over the
Compositor IPC connection instead of adding a separate async scrolling
transport. This keeps bypass admission on the channel that owns bitmaps
and ready_to_paint acknowledgements.
If the compositor rejects the wheel, LibWebView falls back to regular
input delivery. If it accepts, mark the queued DOM event as having
already performed its default action so dispatch preserves ordering
without scrolling twice.
Introduce a dedicated Compositor IPC channel between the UI process and
WebContent. Use it for backing-store setup, presented bitmap delivery,
and bitmap-specific ready_to_paint acknowledgements.
This makes CompositorThread the single owner of frame presentation
bookkeeping before async scrolling starts producing frames without the
main thread.
Remove old paint and backing-store messages from WebContentClient and
PageClient so the UI process no longer observes two presentation
protocols.
Remember the zoom level for each host so that returning to a site
restores the zoom the user previously chose, matching what other
browsers have done for years.
When the user zooms in, zooms out, or resets the zoom, the resulting
level is written to Settings keyed by the current page's host. On
navigation, when a view's URL host changes, the stored level for the
new host is applied (or the global default if there is no override).
Per-host zoom changes are broadcast through the SettingsObserver so
that two tabs viewing the same host stay in sync as soon as the user
adjusts zoom in either one. Zoom changes from within the page (such
as internals.setBrowserZoom hook) and the WebContent restart path do
not persist, only user-initiated zoom changes do.
Previously, if search was disabled, entering non-URL text would just
silently drop the search query (and on Qt, we would reload the current
URL). We now detect that the query did not result in a navigation and
load an error page instead, which directs the user to enable search.
Record visits as soon as a page produces useful metadata such as a
title or favicon so pages that never finish loading still become
autocomplete candidates.
Store favicons in the history schema from the start instead of
introducing an upgrade path inside this series, and cover persisted
metadata behavior in TestHistoryStore.
Generalize the backing store sharing abstraction into SharedImage, which
represents shared GPU memory independently of Skia and can be used to
share memory between different processes or different GPU contexts.
When a page opens a popup, the child tab shares the parent's
WebContent process via initialize_client_as_child(). Both tabs
register as views on the same WebContentClient.
If the child tab then does a cross-site navigation,
create_new_process_for_cross_site_navigation() would call
unregister_view() (correct) and then unconditionally send
CloseServer (wrong). This killed the WebContent process even
though the parent tab was still using it.
The unconditional async_close_server() predates the shared-process
popup model. It is no longer needed since unregister_view() already
sends CloseServer when the last view is removed.
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.
Registering multiple Mach port names with the bootstrap server at
runtime is not how macOS expects it to be used — the bootstrap server
is meant for static services, and the only reason we used it originally
was so child processes could reach back to the UI process.
Remove bootstrap_transport_over_socket(), which had both sides register
dynamic names with the bootstrap server and exchange them over a socket.
Instead, WebDriver and BrowserProcess connections now go through
MachPortServer instances directly. When a non-child process contacts a
MachPortServer, the server creates a port pair on demand (detected via
sysctl ppid check) and returns the local half immediately. This keeps
bootstrap server usage limited to the one original case: child processes
looking up their parent's MachPortServer.
WebDriver Session now runs its own MachPortServer per session.
--webdriver-content-path becomes --webdriver-mach-server-name on macOS.
Spare WebContent launches are skipped when a WebDriver session is active
to avoid bootstrap races.
The set_viewport_size and set_device_pixel_ratio IPC messages were sent
separately, potentially causing a race condition when the DPR changes
(e.g. moving a window between screens): the DPR message would arrive
and use a stale viewport size, computing a temporarily wrong CSS
viewport. Combine both into a single set_viewport IPC that updates the
device viewport size and DPR together.
Commit 84db5d8c1c introduced the ability
to load tests over an http:// URL instead of a file:// URL. Each time
this happens, we switch to a new WebContent process due to site
isolation. Our WebContent output capture was not handling this.
For some reason, this was causing a wide array of test failures and
timeouts. Often, the failures were accompanied by the content of the
files loaded over HTTP being dumped to stdout. It's not quite clear
what was going on here.
Instead of copying the Bitmap that wraps the IOSurface, we can just
present the IOSurface directly. This significantly reduces CPU usage in
the UI process, particularly at high refresh rates such as 120Hz where
it would saturate a full CPU core.
This is done by using CAMetalLayer and blitting the IOSurface to the
next drawable buffer, which handles triple buffering, locking the
IOSurface and vsync automatically. This also allows the Metal HUD to
work, but the only accurate stat is the frame intervals/FPS because
it's in the UI layer, not WebContent. However, that's still useful
to detect frame drops.
This will allow the UI to request WebContent to properly close the top
level traversable when closing a tab. For example, this allows the site
to ask if the user is sure they want to leave, closes WebSocket
connections and more.
This patch introduces a cookie cache in the WebContent process to reduce
blocking IPC calls when JS accesses document.cookie. The UI process now
maintains a cookie version counter per-domain in shared memory. When JS
reads document.cookie, we check whether we have a valid cached cookie by
comparing the current shared version to the last used version. If they
match, the cached cookie is returned without IPC.
This optimization is based on Chromium's shared versioning, in which it
was observed that 87% of document.cookie accesses were redundant. See:
https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html
Note that this cache only supports document.cookie, not HTTP Cookie
headers. HTTP cookies are attached to requests with varying URLs and
paths. The cookies that match the document URL might not match the
request URL, which we wouldn't know from WebContent. So attaching the
cached document cookie would be incorrect.
On https://twinings.co.uk, we see approximately 600 document.cookie
requests while the page loads. This patch reduces the time spent in
the document.cookie getter from ~45ms to 2-3ms.
This introduces a simple FileDownloader to download files in the UI
process from RequestServer. We use this to download the context menu
image - this download is likely to hit the disk cache.
When dumping a GC graph, we now write the output as a .js file
containing `var GC_GRAPH_DUMP = <json>;` instead of raw JSON.
This allows gc-heap-explorer.html to load the dump via a
dynamically created <script> element, avoiding CORS restrictions
that prevent file:// pages from fetching other file:// URLs.
After dumping, both the browser and test-web print a clickable
file:// URL that opens the heap explorer with the dump pre-loaded.
The heap explorer's drag-and-drop file picker also accepts both
the new .js format and plain .json files.
These can get very large, exceeding the new IPC message size limits.
Instead of serializing them into messages (which was silly anyway)
we now send them as Core::AnonymousBuffer which uses shared memory.
When cookies change or expire, we currently send a list of all changed
cookies to all WebContent processes. We then filter that list in the
WebContent process for cookies that match the page's URL before sending
out cookie change events to JS.
We now perform this filtering in the UI process, so each WebContent
process only receives the cookies it would be interested in, if any.
This serves two purposes:
1. Less IPC chatter.
2. This will let each ViewImplementation know that its cookie value has
actually changed.
(2) is for an upcoming change that will introduce a cookie cache, and
will allow each view to know it should bust that cache.
Note that for this filtering to work, we must iterate ViewImplementation
instances rather than WebContentClient in order to have the view's URL.
We must then associate the IPC with the view's page ID.
No changes to the /cookiestore WPT subtests.
If a did_paint message is in-flight and we create a new process when
navigating to another website, we would still have the view registered
with the client when eventually the stale did_paint message is handled.
In server_did_paint, we retrieve the client which points to the new
process, not the original process the view was registered for.
At that point, there might not be any queued rasterization tasks in the
RenderingThread for the new process, causing a crash because of:
VERIFY(m_queued_rasterization_tasks >= 1 && ..);
Fix this by unregistering the view before proceeding with a new
WebContent process.
To avoid unnecessary IPC traffic, we now only send network response
bodies when a DevTools client is connected.
This requires tracking DevTools connection state in ViewImplementation
so we can propagate it to new WebContent processes created during
cross-site navigation.
Previously, console messages were sent using an index-based system where
DevTools would be notified of new message indices and then request them
in batches. This created synchronization issues during page navigation
when the WebContent process resets while DevTools still has stale index
state.
This changes to a push-based model where console messages are sent
immediately as resources when they are logged, matching how Firefox
DevTools handles console messages. Each message is pushed through IPC
and forwarded to DevTools as a "console-message" or "error-message"
resource.
This eliminates the need for index tracking in FrameActor and simplifies
the entire console message pipeline from WebContent through to DevTools.
When a page navigates, send document-event resources with
"will-navigate" and tabNavigated messages so Firefox DevTools
can follow along and clear the Network panel appropriately.