Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,13 @@ jobs:
components: clippy
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
# Lints every member and, as a side effect, type-checks the example
# crates that the `test` job leaves out.
# crates that the `test` job leaves out. `gpui-webview` enables
# `gpui-kit/gpui-fast`, which feature unification would spread to every
# member, so it and its example are linted on their own.
- name: Lint
run: cargo clippy --workspace --exclude gpui-shell --locked -- --deny warnings
run: cargo clippy --workspace --exclude gpui-shell --exclude gpui-webview --exclude webview --locked -- --deny warnings
- name: Lint WebView (GPUI Fast)
run: cargo clippy -p gpui-webview -p webview --locked -- --deny warnings

no-default-features:
name: No default features
Expand Down Expand Up @@ -108,15 +112,17 @@ jobs:
# most of the warm-cache build time; the `lint` job type-checks them
# instead. Leaving them out changes no crate's feature set. The recipes
# crate stays in: its tests are what certify the documentation's code.
# `gpui-webview` enables `gpui-kit/gpui-fast`, which feature unification
# would spread to every member; the GPUI Fast job tests it instead.
# `jq -j` writes the list as one line with no newline at all: on Windows
# `jq` emits CRLF, and a `\r` left on a name breaks `--exclude`.
- name: Leave the example crates to the lint job
run: |
excludes=$(cargo metadata --no-deps --format-version 1 | jq -j '
((.workspace_root | gsub("\\\\"; "/")) + "/examples/") as $examples
| .packages[]
| select((.manifest_path | gsub("\\\\"; "/")) | startswith($examples))
| select(.name != "gpui-kit-recipes")
| select((((.manifest_path | gsub("\\\\"; "/")) | startswith($examples))
and .name != "gpui-kit-recipes") or .name == "gpui-webview")
| "--exclude \(.name) "')
echo "TEST_EXCLUDES=$excludes" >> "$GITHUB_ENV"
# `gpui-component-story/test-support` turns on `gpui-kit/test-support`, so
Expand Down Expand Up @@ -165,6 +171,8 @@ jobs:
run: script/bootstrap
- name: Test GPUI Fast facade and UI integration
run: cargo test -p gpui-kit --features gpui-fast,test-support --locked
- name: Test WebView
run: cargo test -p gpui-webview --locked

gpui-fast-web:
name: GPUI Fast (WebAssembly)
Expand Down
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 9 additions & 7 deletions crates/webview/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@ publish = true
version = "0.7.1"

[features]
gpui-fast = ["dep:gpui-fast", "dep:objc2", "dep:objc2-app-kit", "dep:objc2-foundation", "dep:log"]
# GPUI Fast is always used; this feature is kept so existing manifests that enable it still resolve.
gpui-fast = []
inspector = ["wry/devtools"]

[lib]
doctest = false

[dependencies]
anyhow.workspace = true
gpui.workspace = true
gpui-fast = { workspace = true, optional = true }
log = { version = "0.4", optional = true }
# Enabling `gpui-fast` here selects GPUI Fast for the application's `gpui-kit` too, through
# feature unification, so applications need no extra configuration.
gpui-kit = { path = "../kit", version = "0.7.1", default-features = false, features = ["gpui-fast"] }
log = "0.4"
wry = { version = "0.53.3", package = "lb-wry" }

[target.'cfg(target_os = "macos")'.dependencies]
objc2 = { version = "0.6", optional = true }
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSView"], optional = true }
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "NSGeometry"], optional = true }
objc2 = "0.6"
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSView"] }
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "NSGeometry"] }

[target.'cfg(target_os = "linux")'.dependencies]
gtk = "0.18"
10 changes: 5 additions & 5 deletions crates/webview/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ This still a experimental with limited features, please file issues for any bugs

Supported platforms:

| Platform | Engine | GPUI overlays above the page (`gpui-fast`) |
| Platform | Engine | GPUI overlays above the page |
| --- | --- | --- |
| macOS | WKWebView | Supported |
| Windows | WebView2 | Not yet; the WebView covers overlays |
| Linux (X11 / XWayland) | WebKitGTK | Supported |
| Linux (Wayland) | — | Not supported; run on XWayland |

- With the default backend, native WebViews render above GPUI content.
- On macOS, enabling `gpui-fast` makes `WebView::new()` register the native view with window composition so GPUI overlays can render above it. Composition failures are logged and fall back to the default native attachment.
- On Linux, enabling `gpui-fast` makes `WebView::build()` build the native view in a window composition surface, with the same fallback.
- This crate is built on GPUI Fast. Depending on it enables the `gpui-fast` feature of `gpui-kit`, so the whole application runs on GPUI Fast without further configuration. The gpui-pre backend is not supported. The crate's own `gpui-fast` feature has no effect and is kept only so existing manifests still resolve.
- On macOS, `WebView::new()` registers the native view with window composition so GPUI overlays can render above it. Composition failures are logged and fall back to the plain native attachment.
- On Linux, `WebView::build()` builds the native view in a window composition surface, with the same fallback.
- Use `WebView::set_bounds()` and `WebView::set_visible()` to keep the managed container and Wry child synchronized. Raw Wry handles bypass this coordination.
- Use `WebView::build()` to attach a webview to a GPUI window with the platform setup Wry needs.
- On Linux, the webview uses WebKitGTK and requires the application to run on X11 (or XWayland in a Wayland session). Start it with `gpui_kit::platform::linux(WindowingModes::X11)`; `WebView::build()` returns an error on a Wayland window. Wayland is not supported.

With the default backend or on Windows, use the webview in a separate window or a Popup layer when overlapping GPUI content is required.
On Windows, use the webview in a separate window or a Popup layer when overlapping GPUI content is required.

## Run Example

Expand Down
2 changes: 1 addition & 1 deletion crates/webview/src/composition.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! AppKit adapter for gpui-fast's native window composition.
//! AppKit adapter for GPUI Fast's native window composition.
//! The existing Wry parent stays unchanged for focus_parent(); only WKWebView
//! is reattached. All positioning goes through the surface instead of Wry's
//! original parent coordinates.
Expand Down
34 changes: 16 additions & 18 deletions crates/webview/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
#[cfg(feature = "gpui-fast")]
extern crate gpui_fast as gpui;
extern crate gpui_kit as gpui;

#[cfg(all(feature = "gpui-fast", target_os = "macos"))]
#[cfg(target_os = "macos")]
mod composition;
#[cfg(target_os = "linux")]
mod linux;

#[cfg(all(feature = "gpui-fast", target_os = "macos"))]
#[cfg(target_os = "macos")]
use composition::NativeWebViewSurface;
#[cfg(all(feature = "gpui-fast", target_os = "linux"))]
#[cfg(target_os = "linux")]
use linux::NativeWebViewSurface;

use std::{cell::Cell, ops::Deref, rc::Rc};
Expand Down Expand Up @@ -44,7 +43,7 @@ pub struct WebView {
webview: Rc<wry::WebView>,
visible: Cell<bool>,
bounds: Bounds<Pixels>,
#[cfg(all(feature = "gpui-fast", any(target_os = "macos", target_os = "linux")))]
#[cfg(any(target_os = "macos", target_os = "linux"))]
composition: Option<NativeWebViewSurface>,
/// The GPUI scale factor the page zoom was last matched to.
#[cfg(target_os = "linux")]
Expand All @@ -57,7 +56,7 @@ pub struct WebView {
impl Drop for WebView {
fn drop(&mut self) {
self.hide();
#[cfg(all(feature = "gpui-fast", any(target_os = "macos", target_os = "linux")))]
#[cfg(any(target_os = "macos", target_os = "linux"))]
if let Some(surface) = self.composition.take() {
surface.release(self.webview.clone());
}
Expand All @@ -69,7 +68,7 @@ impl WebView {
pub fn new(webview: wry::WebView, window: &mut Window, cx: &mut App) -> Self {
let _ = webview.set_bounds(Rect::default());

#[cfg(all(feature = "gpui-fast", target_os = "macos"))]
#[cfg(target_os = "macos")]
let composition = match NativeWebViewSurface::new(&webview, window, cx) {
Ok(surface) => Some(surface),
Err(error) => {
Expand All @@ -81,17 +80,17 @@ impl WebView {
let origin = Rc::new(Cell::new(gpui::Point::default()));
#[cfg(target_os = "linux")]
linux::forward_mouse_down(&webview, origin.clone(), window, cx);
#[cfg(not(any(all(feature = "gpui-fast", target_os = "macos"), target_os = "linux")))]
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
let _ = window;

Self {
focus_handle: cx.focus_handle(),
visible: Cell::new(true),
bounds: Bounds::default(),
webview: Rc::new(webview),
#[cfg(all(feature = "gpui-fast", target_os = "macos"))]
#[cfg(target_os = "macos")]
composition,
#[cfg(all(feature = "gpui-fast", target_os = "linux"))]
#[cfg(target_os = "linux")]
composition: None,
#[cfg(target_os = "linux")]
scale_factor: Cell::new(0.),
Expand All @@ -104,8 +103,8 @@ impl WebView {
///
/// On Linux the window must run on X11 or XWayland: start the application with
/// `gpui_kit::platform::linux(WindowingModes::X11)`. A Wayland window returns an error.
/// This also initializes GTK and drives its main loop from GPUI. With GPUI Fast, the webview
/// is built in a window composition surface so GPUI overlays can render above it.
/// This also initializes GTK and drives its main loop from GPUI. The webview is built in a
/// window composition surface so GPUI overlays can render above it.
pub fn build(
builder: wry::WebViewBuilder,
window: &mut Window,
Expand All @@ -116,7 +115,6 @@ impl WebView {
let parent = linux::X11Parent::new(window)?;
linux::ensure_gtk(cx)?;

#[cfg(feature = "gpui-fast")]
match NativeWebViewSurface::new(window, cx) {
Ok(surface) => {
let webview = builder.build_as_child(&surface.parent()?)?;
Expand All @@ -136,9 +134,9 @@ impl WebView {
}

/// Set window-relative bounds, including offscreen loading bounds.
/// With GPUI Fast, position the managed container and its child together.
/// With window composition, position the managed container and its child together.
pub fn set_bounds(&self, bounds: Rect) -> wry::Result<()> {
#[cfg(all(feature = "gpui-fast", any(target_os = "macos", target_os = "linux")))]
#[cfg(any(target_os = "macos", target_os = "linux"))]
if let Some(surface) = &self.composition {
return surface
.set_bounds(&self.webview, bounds)
Expand All @@ -156,7 +154,7 @@ impl WebView {
Ok(())
};
self.webview.set_visible(visible)?;
#[cfg(all(feature = "gpui-fast", any(target_os = "macos", target_os = "linux")))]
#[cfg(any(target_os = "macos", target_os = "linux"))]
if let Some(surface) = &self.composition {
surface
.set_visible(visible)
Expand Down Expand Up @@ -322,7 +320,7 @@ impl Element for WebViewElement {
}

let parent = self.parent.read(cx);
#[cfg(all(feature = "gpui-fast", any(target_os = "macos", target_os = "linux")))]
#[cfg(any(target_os = "macos", target_os = "linux"))]
if let Some(surface) = &parent.composition {
surface.set_scale_factor(window.scale_factor());
}
Expand Down
2 changes: 0 additions & 2 deletions crates/webview/src/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,13 @@ pub(crate) fn match_scale_factor(webview: &wry::WebView, scale_factor: f32) {

/// A GPUI Fast composition surface hosting the webview: an X11 child window that GPUI cuts
/// its overlays out of, so they render above the page.
#[cfg(feature = "gpui-fast")]
pub(crate) struct NativeWebViewSurface {
surface: gpui::WindowCompositionSurface,
scale_factor: std::cell::Cell<f32>,
window: gpui::AnyWindowHandle,
cx: gpui::AsyncApp,
}

#[cfg(feature = "gpui-fast")]
impl NativeWebViewSurface {
pub(crate) fn new(window: &Window, cx: &App) -> anyhow::Result<Self> {
let surface = window
Expand Down
1 change: 0 additions & 1 deletion examples/webview/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ publish = false
edition.workspace = true

[features]
gpui-fast = ["gpui-kit/gpui-fast", "gpui-webview/gpui-fast"]
inspector = []

[dependencies]
Expand Down
7 changes: 4 additions & 3 deletions script/bump-gpui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2092,11 +2092,12 @@ async function verifyKitAgainstStaging(

// The same commands the repository's CI runs. Clippy stays scoped to the
// crates this repository publishes; `--no-deps` keeps it off the staged
// crates, whose warnings are Zed's to fix.
// crates, whose warnings are Zed's to fix. `gpui-webview` enables
// `gpui-kit/gpui-fast`, which would switch every member off gpui-pre.
const commands = [
["cargo", "check", ...patches, "--workspace", "--all-targets"],
["cargo", "check", ...patches, "--workspace", "--exclude", "gpui-webview", "--exclude", "webview", "--all-targets"],
["cargo", "clippy", ...patches, "--no-deps", "-p", "gpui-component", "-p", "gpui-component-story", "-p", "gpui-kit-assets", "-p", "gpui-kit", "--", "--deny", "warnings"],
["cargo", "test", ...patches, "--workspace", "--exclude", "gpui-shell", "--features", "gpui-component-story/test-support"],
["cargo", "test", ...patches, "--workspace", "--exclude", "gpui-shell", "--exclude", "gpui-webview", "--exclude", "webview", "--features", "gpui-component-story/test-support"],
];
for (const cmd of commands) {
const { code } = await runStreaming(cmd, REPO_ROOT);
Expand Down
10 changes: 5 additions & 5 deletions website/docs/webview.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ maturity: [experimental, platform-dependent]

| Platform | Engine | Status |
| --- | --- | --- |
| macOS | WKWebView | Experimental. With `gpui-fast`, deferred GPUI overlays can render above the page. |
| macOS | WKWebView | Experimental. Deferred GPUI overlays render above the page. |
| Windows | WebView2 | Experimental. The example disables GPUI's DirectComposition so the child view renders. |
| Linux (X11) | WebKitGTK | Experimental. The application must start on X11; see [Linux](#linux). With `gpui-fast`, deferred GPUI overlays can render above the page. |
| Linux (X11) | WebKitGTK | Experimental. The application must start on X11; see [Linux](#linux). Deferred GPUI overlays render above the page. |
| Linux (Wayland) | — | Not supported. Run the application on X11 through XWayland instead. |

## Run the example
Expand Down Expand Up @@ -46,7 +46,7 @@ div().flex_1().child(webview.clone())

## GPUI Fast native composition

Enable `gpui-fast` on both `gpui-kit` and `gpui-webview` to select one backend. On macOS, the existing `WebView::new()` automatically registers the WKWebView in the window composition tree. On Linux, `WebView::build()` builds the view in a composition surface, an X11 child window that GPUI cuts the overlays above it out of. Deferred GPUI overlays can then render above the page. No separate composition feature is needed. The default backend and Windows retain the existing native child-view behavior.
`gpui-webview` is built on GPUI Fast. Depending on it enables the `gpui-fast` feature of `gpui-kit`, so the whole application runs on GPUI Fast without further configuration; the gpui-pre backend is not supported. The crate's own `gpui-fast` feature has no effect and remains only so existing manifests still resolve. On macOS, the existing `WebView::new()` automatically registers the WKWebView in the window composition tree. On Linux, `WebView::build()` builds the view in a composition surface, an X11 child window that GPUI cuts the overlays above it out of. Deferred GPUI overlays can then render above the page. Windows retains the native child-view behavior.

On Linux, GPUI Fast draws shadows and translucent content, such as a dialog's backdrop, into a transparent window above the page, which the compositing manager blends over it; XWayland always has one. On an X11 desktop without a compositing manager, overlays are cut out of the page instead, and shadows over the page are not drawn. Hyprland draws a pinned window above all others, so in a pinned window (Omarchy's pop-out) overlays show without the shadows and backdrops over the page.

Expand Down Expand Up @@ -140,10 +140,10 @@ For an application smoke test on each supported OS, exercise initial load, links
| Area | Current behavior |
| --- | --- |
| Platforms | Experimental on macOS, Windows, and Linux on X11. Wayland is not supported; Linux applications must start on X11 or XWayland. |
| Overlay order | The native WebView sits above the GPUI surface and covers GPUI content in the same rectangle, including popovers, dialogs, menus, and tooltips. With the default backend or on Windows, a GPUI overlay cannot reliably appear on top of it. macOS and Linux with `gpui-fast` use native composition for deferred overlays. |
| Overlay order | The native WebView sits above the GPUI surface and covers GPUI content in the same rectangle, including popovers, dialogs, menus, and tooltips. On Windows, a GPUI overlay cannot reliably appear on top of it. macOS and Linux use native composition for deferred overlays. |
| Windows renderer | The repository example sets `GPUI_DISABLE_DIRECT_COMPOSITION=true` before starting GPUI so this child-view approach renders. This is an example-specific requirement, not a general GPUI setting recommendation. |

When an overlay must be visible, place the WebView in a separate window or arrange the screen so the overlay does not cross its bounds. This restriction applies to the default backend and Windows; macOS and Linux with `gpui-fast` support deferred GPUI overlays through native composition.
When an overlay must be visible, place the WebView in a separate window or arrange the screen so the overlay does not cross its bounds. This restriction applies to Windows; macOS and Linux support deferred GPUI overlays through native composition.

## Unmerged composition experiments

Expand Down
Loading
Loading