tauri
tauri copied to clipboard
[bug] Webview Do Not Work Properly
Describe the bug
I am trying to implement a multi tabs layout like VS Code. And I try to use webview to show external url i.e. Tauri, Rust official page for testing. It compiles successfully but it cannot show any content from the webpage.
Reproduction
npm create tauri-app@latest
TypeScript
npm
React
In src-tauri/capabilities/default.json
{
"permissions": [
"core:default",
"opener:default",
"core:webview:allow-create-webview",
"core:webview:allow-webview-close",
"core:webview:allow-set-webview-position",
"core:webview:allow-set-webview-size",
"core:webview:allow-webview-show",
"core:webview:allow-webview-hide",
"core:webview:allow-reparent"
]
}
Create file src/components/NativeTab.tsx
import { useEffect, useRef, useState } from 'react';
import { Webview } from '@tauri-apps/api/webview';
import { getCurrentWindow } from '@tauri-apps/api/window';
interface NativeTabProps {
id: string;
url: string;
isActive: boolean;
}
export const NativeTab = ({ id, url, isActive }: NativeTabProps) => {
const containerRef = useRef<HTMLDivElement>(null);
// We store the webview instance in a ref so we don't lose it on re-renders
const webviewRef = useRef<Webview | null>(null);
const [isReady, setIsReady] = useState(false);
useEffect(() => {
let unmounted = false;
const setupWebview = async () => {
// 1. Get the placeholder's exact position on screen
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const appWindow = getCurrentWindow();
// 2. Create the native webview (Host is the current window)
// Note: Labels must be unique and alphanumeric
const wv = new Webview(appWindow, `tab-${id}`, {
url: url,
x: Math.round(rect.x),
y: Math.round(rect.y),
width: Math.round(rect.width),
height: Math.round(rect.height),
});
webviewRef.current = wv;
// 3. Wait for creation to finish
wv.once('tauri://created', () => {
if (!unmounted) setIsReady(true);
});
wv.once('tauri://error', (e) => {
console.error('Failed to create webview', e);
});
};
setupWebview();
// Cleanup: Close the native webview when this React component is removed
return () => {
unmounted = true;
if (webviewRef.current) {
webviewRef.current.close();
}
};
}, [id, url]);
// Sync Layout: Resize native webview when the HTML container resizes
useEffect(() => {
if (!containerRef.current || !webviewRef.current || !isReady) return;
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { x, y, width, height } = entry.target.getBoundingClientRect();
// Native methods to move the "Floating" window
webviewRef.current?.setPosition({ x: Math.round(x), y: Math.round(y) });
webviewRef.current?.setSize({ width: Math.round(width), height: Math.round(height) });
}
});
observer.observe(containerRef.current);
return () => observer.disconnect();
}, [isReady]);
// Visibility Logic: Hide the native window if the tab is not active
useEffect(() => {
if (!webviewRef.current || !isReady) return;
if (isActive) {
webviewRef.current.show();
// Force a position update in case layout changed while hidden
if (containerRef.current) {
const { x, y } = containerRef.current.getBoundingClientRect();
webviewRef.current.setPosition({ x: Math.round(x), y: Math.round(y) });
}
} else {
webviewRef.current.hide();
}
}, [isActive, isReady]);
// The React render is just an empty placeholder
return <div ref={containerRef} style={{ width: '100%', height: '100%' }} />;
};
In file src/App.tsx
import { useState } from "react";
import { NativeTab } from "./components/NativeTab";
import "./App.css";
function App() {
const [tabs, setTabs] = useState([
{ id: "1", title: "Tauri", url: "https://tauri.app" },
{ id: "2", title: "Rust", url: "https://www.rust-lang.org" },
]);
const [activeTabId, setActiveTabId] = useState("1");
const closeTab = (id: string) => {
setTabs(tabs.filter((t) => t.id !== id));
if (activeTabId === id && tabs.length > 1) {
setActiveTabId(tabs[0].id); // Simple fallback logic
}
};
return (
<div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
{/* 1. Top Bar (HTML UI) */}
<div className="tab-bar" style={{
display: "flex",
height: "40px",
background: "#252526",
alignItems: "center",
paddingLeft: "10px"
}}>
{tabs.map((tab) => (
<div
key={tab.id}
onClick={() => setActiveTabId(tab.id)}
style={{
padding: "8px 15px",
background: tab.id === activeTabId ? "#1e1e1e" : "#2d2d2d",
color: tab.id === activeTabId ? "white" : "#999",
cursor: "pointer",
marginRight: "2px",
borderRadius: "5px 5px 0 0",
userSelect: "none"
}}
>
{tab.title}
<span
onClick={(e) => { e.stopPropagation(); closeTab(tab.id); }}
style={{ marginLeft: "10px", fontWeight: "bold" }}
>
×
</span>
</div>
))}
<button onClick={() => {
const newId = Date.now().toString();
setTabs([...tabs, { id: newId, title: "Google", url: "https://google.com" }]);
setActiveTabId(newId);
}} style={{ marginLeft: "10px" }}>
+
</button>
</div>
{/* 2. Content Area (Native Webviews Overlay Here) */}
<div className="content-area" style={{ flex: 1, position: "relative", background: "#1e1e1e" }}>
{tabs.map((tab) => (
<div
key={tab.id}
style={{
display: activeTabId === tab.id ? "block" : "none",
width: "100%",
height: "100%"
}}
>
<NativeTab
id={tab.id}
url={tab.url}
isActive={activeTabId === tab.id}
/>
</div>
))}
</div>
</div>
);
}
export default App;
Run
npm run tauri dev
Expected behavior
It should show the webpage content
Full tauri info output
> [email protected] tauri
> tauri info
[✔] Environment
- OS: Windows 10.0.26100 x86_64 (X64)
✔ WebView2: 142.0.3595.94
✔ MSVC: Visual Studio Build Tools 2022
✔ rustc: 1.91.1 (ed61e7d7e 2025-11-07)
✔ cargo: 1.91.1 (ea2d97820 2025-10-10)
✔ rustup: 1.28.2 (e4f3ad6f8 2025-04-28)
✔ Rust toolchain: stable-x86_64-pc-windows-msvc (default)
- node: 25.2.1
- npm: 11.6.2
[-] Packages
- tauri 🦀: 2.9.3, (outdated, latest: 2.9.4)
- tauri-build 🦀: 2.5.2, (outdated, latest: 2.5.3)
- wry 🦀: 0.53.5
- tao 🦀: 0.34.5
- @tauri-apps/api ⱼₛ: 2.9.1
- @tauri-apps/cli ⱼₛ: 2.9.4 (outdated, latest: 2.9.5)
[-] Plugins
- tauri-plugin-opener 🦀: 2.5.2
- @tauri-apps/plugin-opener ⱼₛ: 2.5.2
[-] App
- build-type: bundle
- CSP: unset
- frontendDist: ../dist
- devUrl: http://localhost:1420/
- framework: React
- bundler: Vite
Stack trace
Additional context
No response