v0.1.8
Menu
Download

WebView2 Shell

On Windows, WebView2 (Edge Chromium) hosts the React UI and a separate embedded surface for Explorer cookie flows.

The main window webview loads the Vite bundle. Local playback uses Tauri asset protocol scopes so convertFileSrc URLs resolve under your download and library paths. CSP is null in config; sandboxing is mostly Tauri capability boundaries plus path allowlists.

Explorer is a child webview (explorer-view label on Windows) positioned over the content column. It paints above normal DOM, which is why back/forward/reload live in ExplorerTitlebarNav fixed at the sidebar seam, not inside the Explorer tab body. explorerBoundsSync.ts sends screen-space rects to ensure_embedded_explorer_bounds during sidebar resize.

WebView2 quirks shaped a few frontend choices: native confirm() is dead in practice, so deletes use React ConfirmDialog. Audio visualizers tap captureStream() because analyser nodes on <video> alone read silence in WebView2. applyMediaOutputState.ts syncs volume/mute on every load because autoplay can leave elements muted after mini handoff.

Rust patches Windows volume mixer labels via Core Audio so sessions show RuForge instead of a generic WebView2 name (windows_audio_brand.rs).

In the repo

#[tauri::command]
pub fn ensure_embedded_explorer_bounds(
    app: AppHandle,
    x: f64,
    y: f64,
    width: f64,
    height: f64,
) -> Result<(), String> {
    #[cfg(target_os = "linux")]
    {
        return linux_ensure_surface_bounds(&app, x, y, width, height);
    }

    #[cfg(not(target_os = "linux"))]
    {
        sync_in_window_child_bounds(&app, x, y, width, height)
    }
}
fn sync_in_window_child_bounds(
    app: &AppHandle,
    x: f64,
    y: f64,
    width: f64,
    height: f64,
) -> Result<(), String> {
    let lx = x.round() as i32;
    let ly = y.round() as i32;
    let lw = width.round().max(1.0) as u32;
    let lh = height.round().max(1.0) as u32;

    let mut cache = LAST_IN_WINDOW_LOGICAL_BOUNDS
        .lock()
        .map_err(|_| "explorer bounds cache poisoned".to_string())?;
    if *cache == Some((lx, ly, lw, lh)) {
        return Ok(());
    }
    *cache = Some((lx, ly, lw, lh));
    drop(cache);

    let Some(webview) = app.get_webview("explorer-view") else {
        return Ok(());
    };
    webview
        .set_position(LogicalPosition::new(lx as f64, ly as f64))
        .map_err(|e| e.to_string())?;
    webview
        .set_size(LogicalSize::new(lw as f64, lh as f64))
        .map_err(|e| e.to_string())?;
    Ok(())
}
/** Apply store volume/mute to a local `<video>` / `<audio>` (WebView autoplay may leave `muted` true). */
export function applyMediaOutputState(
  el: HTMLMediaElement,
  volume: number,
  muted: boolean,
): void {
  const v = Number.isFinite(volume) ? Math.max(0, Math.min(1, volume)) : 1;
  el.volume = v;
  el.muted = muted;
}

Where it shows up

  • explorer_embed.rs child bounds, visibility, Linux surface variant
  • ExplorerTitlebarNav + Explorer host div in App.tsx
  • applyMediaOutputState.ts on <video> / <audio> in player and mini
  • tauri.conf.json assetProtocol.scope drive and home paths