Use the normal light chrome surface for the focused location editor
background instead of reusing the hover fill. Dark mode keeps its
slightly distinct focused fill so the input remains readable there.
Bring horizontal tab cards one pixel closer together and make the
horizontal New Tab button a square with the same height as tabs.
Remove the layout gap between the tab bar and New Tab button so the
painted card gap matches the gap between adjacent tabs.
Use the same icon-only card-shaped New Tab button in horizontal tab mode
that collapsed vertical tabs use. This keeps the New Tab affordance in
the same visual language as the tab cards instead of using a standard
flat tool button.
Bring horizontal tab card visuals one pixel closer together by reducing
the painted card inset by half a pixel on each side. Keep vertical tab
geometry unchanged.
Vertical tab mode always keeps the toolbar in the full-width chrome row.
Remove the older split path that could place the toolbar in the page
column, and simplify the window-control visibility checks around that
single layout model.
Add fixed empty toolbar gaps on both sides of the location editor. This
slightly reduces the omnibox width and leaves reliable toolbar chrome
space next to it for window dragging.
Use the full-width toolbar layout for vertical tabs on every platform.
Also use the same collapsed vertical tabs width everywhere so collapsed
tab shapes remain square.
Make the vertical tab overlay column and resize handle native only on
macOS. This preserves stacking over native web views there without
breaking input routing on non-macOS platforms.
Do not clear the Qt window mask from the non-macOS corner update path.
The custom rounded-corner implementation is macOS-only, so the Linux
resize path should not touch native mask state on every resize event.
Also make dynamic property updates idempotent for tab chrome widgets.
Vertical tab resize and hover updates now only repolish when a property
actually changes, and avoid reapplying unchanged New Tab button text,
style, and size.
Declare the AppKit window-drag and window-control helpers only on
macOS. The implementation is already compiled only for Apple targets,
and call sites are guarded, so keep the header aligned with that
platform boundary.
Remove dead vertical-tab separator and sidebar traffic-light paths from
the unified chrome layout. Keep the toolbar stack owned by TabWidget so
layout changes no longer move each tab toolbar in and out of its page.
Share the vertical New Tab button setup between initial layout and
hover expansion, and keep window-control sizing helpers private.
Treat the toolbar, tab strip, and vertical sidebar as one browser
chrome surface. Add an explicit layout policy for platform window
controls and sidebar widths. This lets macOS use left-side traffic
lights while other platforms keep right-side custom controls.
Keep the macOS toolbar stable in vertical-tabs mode. Traffic lights and
navigation controls stay in the top row, and the sidebar sits below it.
Draw chrome/content separators explicitly so the content edge remains
clear without adding seams inside the chrome surface.
Retune tab, toolbar, and omnibox styling for the unified chrome. Tabs
use card-like selected states, quiet inactive states, and matching
horizontal and vertical geometry.
Make hover-expanded vertical tabs draw as a chrome overlay so the web
content does not relayout. Keep the overlay above native web views and
route macOS window drags through AppKit events for reliable dragging
from custom chrome.
We previously just hid this icons as a future TODO. We now draw a tab
close icon on hover in the upper-left corner of the tab, and an audio
state icon when audio is playing on the lower-right corner. Just like
expanded tabs, the audio state icon is interactable and may be used to
mute the page.
We currently rely on QStyle icons to draw the audio state icons. But
these icons are system dependent. The macOS icons look particularly
out-of-place here. So let's draw chrome icons.
The new tab button would jump around a bit when switching between
collapsed and expanded vertical tabs. This is easier to notice when
expand-on-hover is enabled.
We now:
* Consistently draw the separator above the new tab button.
* Draw the new tab icon with a consistent icon size and placement.
This as a pesudo third mode to vertical tabs. Now when the vertical tabs
are collapsed, users can enable a setting that will expand the vertical
tab area on hover. Unlike full expansion, the hovered vertical tab area
will cover the web content area to avoid pushing the web content area
around.
Using the status bar for this meant that the "DevTools is enabled on
port X" message would get overwritten whenever the user hovered over a
menu item. We don't otherwise use or need a status bar, so replace it
with an explicit banner widget that's only used for this purpose. Keep
the existing styling so it looks the same as before.
After commit a7e68ef, When switching from vertical tabs to horizontal
tabs, the QTabBar could keep its stale narrow vertical width. This made
horizontal tabs render too small and, with multiple tabs, caused scroll
buttons to appear even though there was enough room in the tab strip.
Make the horizontal tab bar width explicit from the same available-width
calculation used for tab size hints.
These were needed before commit 24e767a in order to propagate dynamic
property changes to Qt's CSS engine (for the verticalTabsExpanded
property). We now handle this in C++, so these calls aren't needed.
Commits a7e68ef and 24e767a shuffled things around in that they ended up
performing manual updates to the toolbar buttons in order to update the
collapse/expand vertical tabs button. However, this isn't needed - that
button's state is totally controlled by LibWebView already (through the
WebView::Action infrastructure).
Reduce the horizontal margins and shape inset used by expanded vertical
tabs so the sidebar content uses more of the available space. Keep the
collapsed sidebar margin unchanged so icon-only tabs remain centered.
Share the expanded-tab geometry through helpers so painting, button
placement, and the new-tab button stay in sync.
Move tab button geometry updates out of TabBar::paintEvent(). That path
mutates child widgets, and Qt schedules more updates for those buttons.
During painting, that can keep the tab bar dirty and churn CPU.
Update buttons from layout, hover, and current-tab changes instead. Keep
hover bookkeeping in one helper, and skip widget mutators when they are
already in the desired state.
Stop forcing RHI for every QWidget on macOS. The web content view is
the only widget that needs Metal, and QRhiWidget already requests that.
Make the web content view a native child before parenting it into the
tab UI so Qt does not propagate its RHI config to the browser window
backing store. This keeps chrome repaints on the normal Cocoa backing
store path instead of copying the full window backing store into a
texture.
The macOS rounded-corner helper needs the native view to be attached to
an NSWindow. The constructor can run too early for that, leaving startup
windows square even though the setting is enabled.
Reapply the corner state after Qt creates the platform surface, deferred
until Cocoa has attached the view to the native window.
Support Command+Shift+[ and Command+Shift+] for moving between
Qt tabs on macOS. Also support the physical Control+Tab and
Control+Shift+Tab shortcuts accepted by other browsers.
Treat these as browser-reserved shortcuts so WebContent does not
consume the shortcut override before the browser action can run.
Qt swaps Command into ControlModifier and Control into MetaModifier on
macOS so shortcut definitions can stay portable. Convert those modifiers
back to Web-facing semantics before sending input events to LibWeb.
This lets Command-click behave as platform-control click instead of
being mistaken for Control-click, while physical Control-click still
opens the context menu on macOS.
Platform wheel deltas describe movement in the view, not in page CSS
pixels. Convert them by page zoom before dispatch so physical scrolling
covers a smaller page area when zoomed in.
Do this before the UI-side async scrolling shortcut so compositor
scrolling and main-thread fallback scrolling use the same delta. Also
avoid applying the Qt device pixel ratio while converting angle-only
wheel input, since LibWeb later handles device-pixel scaling itself.
Translate Qt native zoom gestures into Web::PinchEvent for the web
content view. This gives the Qt UI the same pinch-to-zoom input path as
the AppKit UI while leaving ordinary wheel scrolling unchanged.
Map the Qt native gesture screen position back into WebContentView
before creating the event, since macOS delivers the gesture through the
top-level widget window after forwarding. Keeping the pinch focal point
in content coordinates lets visual viewport zoom preserve the user's
focus point.
Use QRhiWidget for the Qt web content view on macOS and render the
current IOSurface-backed shared image into the widget's Metal render
target. This keeps normal presentation on the GPU instead of painting a
QImage wrapper over the shared bitmap.
Force Qt Widgets' RHI backing store to Metal before QApplication is
created so QRhiWidget can obtain the top-level QRhi even when the native
window is created before the web content widget enters the hierarchy.
Other platforms keep the existing QWidget and QPainter path.
TabBar::paintEvent() drew a fresh globe icon for every tab without a
favicon. Creating a chrome icon renders several pixmaps for multiple
states and device pixel ratios, which is far too expensive for a paint
hot path.
Cache the fallback tab icon on TabBar and refresh it alongside the other
chrome icons when the palette changes.
When focusing the omnibox with the mouse, keep delaying expansion from
the display URL to the serialized URL until mouse release.
If the release leaves only a caret instead of a selection, select the
full serialized URL so a plain click behaves like normal browser omnibox
behavior.
Add an advanced setting for rounded browser window corners. On macOS,
Qt browser windows now use a layer-backed corner radius, and clear it
when maximized, fullscreen, or the setting is disabled.
Hide the setting from about:settings on other platforms for now since Qt
does not expose a native antialiased window corner API there.
Refine vertical tab sidebar styling. Let inactive tabs sit on chrome.
Keep close buttons quiet. Align the New Tab row with tab rows.
Persist the expanded sidebar width and keep resizing bounded. Put the
resize hit area over the divider. Keep the collapsed rail fixed-width.
Avoid explicit mouse grabs while dragging.
Paint subtle sidebar dividers. Move the expand/collapse control into
the navigation toolbar. Tune the fallback globe and vertical-tabs icons.
Move custom window controls into the navigation toolbar while vertical
tabs are enabled. Remove the separate top row that only held them.
Mark empty toolbar space as draggable so the unified row acts like the
titlebar. Keep horizontal tab mode on the existing tab strip controls.
Commit 16e218b7f6 removed the fallback
favicon in order to stop showing the Ladybird logo on these tabs. But
with collapsed vertical tabs, we would then render nothing. We now use
the already-existing globe chrome icon, which is tweaked here to look a
bit better when painted larger than it was previously.
This implements an initial version of vertical tabs for the Qt UI. Users
may enable vertical tabs in about:settings. When enabled, a button is
added to the toolbar to expand/collapse the vertical tabs.
There is plenty more to do here. We will want to support expanding on
hover, drawing the close tab / audio button indicators on collapsed
vertical tabs, positioning the vetical tab bar, etc.
This adds a setting to enable vertical tabs and to expand/collapse them
if enabled. This setting is hidden for UIs that do not support them
(which is every UI as of this commit).
Compositor recovery recreated contexts and replayed viewport sizes,
but it left the restarted helper with the default display metadata. A
recovered context could fall back to a 60 Hz timer instead of the
window's current display id and refresh rate until the frontend later
observed a screen change.
Store the display id in the shared view state, alongside the existing
maximum frame rate, and replay both values while restoring view state
after reconnect.
Show active page zoom as a muted pill inside the right side of the
location edit whenever zoom differs from 100%. Keep it next to the
bookmark action and reserve text margin space so the URL cannot render
under the controls.
Clicking the pill activates the existing reset zoom action, returning
the page to 100% and hiding the indicator again.
Show web URLs without their scheme, a leading host www., or the
root slash while the location field is not being edited. Restore the
fully serialized URL while editing so the original scheme is preserved,
but keep mouse focus stable until release so clicks target the visible
text.
Keep the domain emphasis for shortened Qt location text by deriving the
highlight ranges from the serialized URL and remapping them to the
visible display form. Special URLs like about, data, and file remain
unchanged. Add LibWebView coverage for the shared display helper used by
both UI frontends.
Stop mirroring the tab favicon and loading spinner inside the omnibox.
The tab already owns that state, so the leading omnibox control now
reflects the action or warning relevant to the field.
HTTP pages show a compact Not secure pill, edited URL-like input shows
the globe, and edited search input shows the search icon. Current
non-HTTP pages leave the leading edge quiet so ordinary navigation does
not look like a security status surface.
Normalize generated chrome icon weights and alignment, tune dark and
light chrome surface layering, and move the compact palette toward a
quieter neutral treatment. Keep the compact tab strip and toolbar
heights while making the toolbar, tab surfaces, omnibox, and navigation
cluster feel more balanced.
Use tonal dark-mode seams instead of bright separator lines, quiet the
resting omnibox border, and keep background tabs full-strength while
using shape and layering to distinguish the selected tab.
Draw the remaining Qt chrome icons through the existing generated icon
path instead of loading TinyVG resources. Add generated folder and
chevron icons for menus, the bookmarks bar, and find-in-page controls,
and use the generated globe icon for bookmarks without favicons.
Remove the Qt TinyVG icon engine and drop the unused TVG resources from
the Qt resource file.
Reduce the Qt tab strip, navigation toolbar, and location edit heights
so the combined chrome takes less vertical space while the toolbar
buttons keep comfortable 36 px targets. Keep glyph sizes unchanged and
use inset toolbar button styling so the painted hover surfaces feel
lighter than the clickable area.
Let Ctrl+L bypass WebContent in the shortcut override path so the
browser chrome can focus the location editor immediately. This matches
Qt's other browser-owned tab and window shortcuts.
Let AppKit menu dispatch handle browser-owned key equivalents before the
web view forwards them to WebContent. This keeps commands like opening a
new tab, closing the current tab, quitting, and focusing the location
field in the browser chrome even when WebContent is the first responder.
Picker input starts in each platform UI, while protocol state lives in
LibWebView. Route movement, primary clicks, Escape, and leave events
through the WebView picker API so each frontend drives the same hit-test
and picker-event path.
The selected click is consumed because picker mode uses it to choose an
element for DevTools rather than activate page content.
AppKit routes normal mouse input through WebViewBridge, which converts
view points to device pixels before forwarding to LibWeb. Picker input
bypasses that enqueue path, so apply the same scaling before requesting
a picker hit test.
WebContentView scales its painter into device-pixel coordinates before
painting page bitmaps. When no page bitmap was available, the fallback
fill still used the widget rect in CSS pixels. On high-DPI displays, it
only filled the top-left portion of the viewport.
Use the stored device-pixel viewport size for the no-bitmap fill and for
the fallback area around a smaller bitmap.
This abstraction is needed for an upcoming commit to make the UI layer
react to changes in menu visibility. It turned out to be a nice cleanup
in any case.
We attempted to paint a preview of the dragged tab, but this only worked
for the first tab. For other tabs, we were passing a negative offset to
QWidget::render, causing the preview to render transparently.
We were trying to handle this in TabWidget::event, but this mouse event
is now delivered to the child TabBar, where the event is accepted and
thus not propagated further.
Use the page palette for default selection backgrounds instead of a
hardcoded blue color. Tint opaque selection backgrounds before painting.
Selected images stay visible through the overlay.
Use SelectionText for default selected text and HighlightText. Authored
::selection colors still take precedence. On macOS, use a light
translucent color matching other engines.
Update selection and system-color baselines for the themed colors. Add
macOS-specific screenshot baselines for tests whose expectations depend
on system colors.
The browser process forwards the maximum frame rate to WebContent, but
the compositor process had no copy of that display timing metadata or
the display identity needed by compositor-local vsync.
Add one compositor control IPC message carrying the optional display ID
and refresh rate together, and store both values on each compositor
context.
BrowserWindow only connected QWindow::screenChanged while setting up
the non-Wayland DPI path. On Qt Wayland this path is skipped
because device-pixel-ratio changes are delivered to the window instead
of QScreen. A window could stay associated with the screen reported
during construction until the native QWindow exists, so refresh-rate
updates were missed when the window moved between outputs.
Separate refresh-rate and screen tracking from the DPI-specific signal
handling. Connect QScreen::refreshRateChanged for the current screen on
all platforms, connect QWindow::screenChanged when the native window
becomes available, and use ScreenChangeInternal as an additional update
hook. The DPI signal remains disabled for Qt Wayland, where
DevicePixelRatioChange is still handled through BrowserWindow::event().
Custom-paint bookmark bar buttons so favicons and titles are spaced and
vertically centered consistently. Qt’s default QToolButton label layout
does not expose enough control over the icon/text gap or pixel-level
vertical alignment needed for the tightened bookmarks bar.
These both require the same helper processes, and maintaining the same
list in multiple places is error-prone. For example, I spent too long
debugging why test-web was crashing, when the issue was that Compositor
hadn't been rebuilt as test-web didn't depend on it. That problem can
no longer happen if we define the same dependency list for both.
Before the recent Qt overhaul, we had no control over the position of
the tab close button (without drawing/maintaining it ourselves, which we
now do). Qt automatically placed the tab close button on the left side
of the tab bar on macOS, and as such, we had to go out of our way to
place the audio state button on the right side. See:
dd54780d5e
We now consistently place the audio state button on the left and the tab
close button on the right. Qt still attempts to draw its own tab close
button on the left, so we also explicitly set that to null initially.
Generate DPR-tagged pixmap variants for generated icons (this includes
chrome icons, TVG icons, and bookmark favicons). Previously, these icons
would render very blurry on macOS.
Use matching top and right padding around the tab strip window controls
when the menu bar is hidden, while keeping the existing left spacing for
tabs and the resize edge.
Add a menu bar corner widget for the frameless window controls when
the Qt menu bar is visible. Hide the tab strip controls in that mode
so the buttons stay at the top-right edge of the window, and remove
the separator below the menu bar so the chrome reads as one surface.
Use compact menu bar controls and keep the right padding tight so the
buttons align with the edge without changing the hidden-menu layout.
Start a platform window move when pressing empty space in the Qt menu
bar. Toggle maximized state on double-click, matching the tab strip.
Menu actions keep their normal behavior, and resize edges still take
precedence before starting a move.
Add a shared WindowControlButton for the frameless window controls. It
clears the pressed visual state when the pointer leaves during a press,
and restores it on re-entry.
Also reset transient button state around double-clicks so pressed
styling cannot get stuck after the window state changes.
Drop the PNG resources that were only used for menu action icons.
Keep the app icons and directory listing icons that still have direct
resource references, and stop copying the now-empty browser icon set.
Let application actions opt out of icon creation when created for
menus. Use that path for browser menus, tab context menus, and
WebView-backed popup menus.
Keep icon creation for toolbar and bookmarks bar actions, where the
same helper still provides visible button icons. Remove the icon setup
that is now only used by menu actions, and shrink popup menu item
padding now that there is no icon column.
Add application-level QMenu styling using the existing chrome palette so
menu bar dropdowns, hamburger menus, context menus, and other popups use
the same colors and item states. Refresh the application stylesheet when
the Qt palette or theme changes.
Add a QMenuBar stylesheet that uses the same chrome palette as the tab
strip and navigation toolbar. Apply it from BrowserWindow and refresh it
when the palette changes so the optional menu bar matches the frameless
window chrome.
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.
Commit 218d82cb65 added support for
pasting text with the middle mouse button. But primary pasting is
actually meant to interact with the "selection" clipboard, not the
text clipboard.
The bookmarks bar sticks out a bit too much in its own container with
its own background color. This adds a common container for both bars
so that we can apply the background color gradient to that common
container. This naturally handles showing/hiding the bookmarks bar
gracefully.
This is never referenced outside the constructor; we don't need to store
a pointer to it. (I had accidentally removed it in a subsequent commit,
then realized we didn't need it anyways).
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.
Trackpad pinches applied directly as visual viewport zoom, so pages
could not observe the gesture or cancel the browser default. Canvas
apps such as maps expect the ctrl-wheel path instead.
NSMagnificationGestureRecognizer exposes magnification as an accumulated
value for the current gesture. Ladybird kept its own previous value in
WebViewBridge and emitted the residual delta when the gesture ended, so
the Web input pipeline did not receive the same per-update shape as the
native changed events.
Reset the recognizer's magnification accumulator after changed updates
and skip begin/end states. This keeps the AppKit side responsible for
delivering concrete scale changes and removes the bridge pinch state.
Let browser chrome shortcuts for tab and window management bypass the
WebContent shortcut override path. This makes new tab, new window,
reopen closed tab, tab switching, close tab, and quit dispatch through
Qt immediately instead of waiting for page event handling.
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.
When a child process terminates on Windows, the QWinEventNotifier
emits the activated signal it its event handler. Before this
happens, we need to call setEnabled(false) to unregister the
wait object from the Windows thread pool. setEnabled(false)
also causes us to wait for any outstanding callbacks to complete
and then prevents QWinEventNotifier from trying to use the closed
handle.
This seems to follow the pattern from Qt where the destructor also
calls setEnabled(false) before destruction.
Keep late about:newtab load notifications from clearing hidden location
text after the user has started typing. Let LocationEdit own cursor
placement for normal URL display so load-start updates do not disturb
inline autocomplete selection.
Draw back and forward icons as filled stroked paths so the arrow tips do
not accumulate alpha where segments meet. Increase their apparent size,
shift the menu glyph down slightly, and raise the new tab icon to align
with the tab strip.
Keep active and hovered tabs on the same geometry while tightening the
surrounding chrome rhythm. Reduce tab and toolbar padding, shorten the
navigation toolbar and omnibox, center the new tab button, and enlarge
tab close buttons to match the refined tab sizing.
Define neutral chrome material anchors in one place and derive shared
surface, hover, pressed, and border colors from them. This removes
system accent color from chrome hover fills and gives the tab strip,
toolbar, and omnibox separate material roles in both color schemes.
The `WA_OpaquePaintEvent` attribute on `BrowserWindow` means Qt no
longer fills widget backgrounds automatically. Plain `QWidget`
subclasses do not paint their stylesheet backgrounds by default, so the
find in page bar lost its background color and did not clear its state
correctly between repeats . Setting `WA_StyledBackground` tells Qt to
honor the stylesheet background property in the widget's paint cycle.