Aug 26, 2025
4 min read
Rust,
Tauri,

Exploring Tauri Plugin Development: Webview, iframe, and Shadow DOM Practices

This article explores several approaches for implementing plugin-based development in Tauri desktop applications, including technologies such as Webview, iframe, and Shadow DOM. It analyzes the pros and cons and applicable scenarios of each approach, and delves into how to build a secure sandbox environment for loading third-party untrusted resources.

Plugin-based development has several approaches: Webview, iframe, and shadow DOM. Essentially, what’s needed is a sandbox to load third-party untrusted resources without affecting the main application.

Webview

Whether it’s Tauri or Electron, the software window structure is roughly like this:

+-------------------------------------+
|              Window                 |
|  +-------------------------------+  |
|  |          WindowApp            |  |
|  |  +-------------------------+  |  |
|  |  |        Webview          |  |  |
|  |  +-------------------------+  |  |
|  +-------------------------------+  |
+-------------------------------------+

Electron can use the <webview> tag on a page to load third-party resources. However, Tauri cannot.

What’s the reason?

Because Electron is essentially a browser forked from the chromium project, Electron has absolute control over webview and can modify it however it wants. But Tauri is just a “kernel wrapper” - Tauri uses Microsoft Edge WebView2 on Windows, WKWebView on macOS, and webkitgtk on Linux. This determines that it cannot modify webview.

Therefore, simply using it like Electron is not feasible.

Electron also has something called BrowserView, whose window structure looks like this:

+-------------------------------------+
|              Window                 |
|  +-------------------------------+  |
|  |          WindowApp            |  |
|  +-------------------------------+  |
|  +-------------------------------+  |
|  |          BrowserView          |  |
|  +-------------------------------+  |
+-------------------------------------+

Since Tauri’s goal is just to be a wrapper, it doesn’t have something similar to BrowserView.

iframe

iframe is actually an embedded tag in the browser DOM standard, so using iframe to implement a plugin container is definitely feasible. But iframe has significant limitations. For example, if you want to embed some websites, many websites use the X-Frame-Options header to prohibit third parties from embedding <iframe> tags. Another limitation is that iframe, being purely frontend embedded, cannot be converted into independent windows. I’m not deeply familiar with other limitations of iframe, so I don’t fully understand its pitfalls.

shadow DOM

shadow DOM allows browsers to encapsulate templates, style sheets, attributes, JavaScript code, etc., into an independent DOM element, thereby creating an isolated environment. In this regard, it can勉强 serve as a kind of “sandbox”.

However, shadow DOM is not completely isolated. There are ways to break the encapsulation, such as JavaScript still being able to access and manipulate it.

The Best Choice

From the above, webview seems like a good choice, but Tauri cannot directly nest one webview inside another webview.

Fortunately, Tauri supports adding multiple webviews to a single Window.

let window = tauri::window::WindowBuilder::new(app, "main")
        .inner_size(width, height)
        .build()?;

      let _webview1 = window.add_child(
        tauri::webview::WebviewBuilder::new("main1", WebviewUrl::App(Default::default()))
          .auto_resize(),
        LogicalPosition::new(0., 0.),
        LogicalSize::new(width / 2., height / 2.),
      )?;

      let _webview2 = window.add_child(
        tauri::webview::WebviewBuilder::new(
          "main2",
          WebviewUrl::External("https://github.com/tauri-apps/tauri".parse().unwrap()),
        )
        .auto_resize(),
        LogicalPosition::new(width / 2., 0.),
        LogicalSize::new(width / 2., height / 2.),
      )?;

The above code will look like this:

+-------------------------------------+
|            Window                   |
|  +-------------------------------+  |
|  |        WindowApp              |  |
|  |  +-------------------------+  |  |
|  |  |  <webview> |  | <webview>  |  |
|  |  +-------------------------+  |  |
|  +-------------------------------+  |
+-------------------------------------+

At the same time, we also need to add the unstable feature to Tauri:

tauri = { version = "2.8.4", features = ["unstable"] }