[feat] Expose AddBrowserExtension for Windows
Revxrsal opened this issue · 2 comments
Describe the problem
I can see in #10909 that it is possible now to enable browser extensions on Windows. Unfortunately, we can hardly take advantage of this feature as you currently don't have any way to actually add extensions :P
Describe the solution you'd like
Add a function that invokes ICoreWebviewProfile7#AddBrowserExtension.
A possible approach is to add an extensions
function in WebviewWindowBuilder
, as shown below:
let window = tauri::WebviewWindowBuilder::new(...)
.title(...)
.browser_extensions_enabled(true)
.extensions(
/* directory, possibly a Path or PathBuf */,
/* list of extension IDs, maybe a Vec<String> */
);
Since this is only possible on Windows, it could either:
- Be put behind a feature flag
- Be callable on all platforms, but only affect Windows
The second approach is likely better, as it is consistent with Tauri's overall design and ensures forward compatibility in case other platforms allow extensions in the future.
Alternatives considered
Modifying the underlying Wry window. Looks like this is not possible.
Additional context
No response
You can use with_webview
for now https://docs.rs/tauri/latest/tauri/webview/struct.WebviewWindow.html#method.with_webview
AreBrowserExtensionsEnabled
got an actual method because it needs to be set when the inner webview is created.
I've spent some decent three hours trying to get this right 😂 Had to learn a bit about the Windows API and how it's used in Rust. I also got sidetracked by the whole COM thing.
Anyway, here's the final code, in case anybody needs it:
[target.'cfg(target_os = "windows")'.dependencies]
windows-core = "0.58.0"
webview2-com-sys = "0.33.0"
use std::path::Path;
use tauri::webview::PlatformWebview;
/// Represents an Error that occurs when adding a browser extension
#[derive(Debug)]
pub enum Error {
Unsupported,
#[cfg(target_os = "windows")]
WindowsError(windows_core::Error),
}
/// Defines extensions for [PlatformWebview] to easily add extensions
/// to web-views
pub trait BrowserExtensionsExt {
fn add_extension<P: AsRef<Path>>(&self, extension_folder: P) -> Result<(), Error>;
}
impl BrowserExtensionsExt for PlatformWebview {
#[cfg(not(target_os = "windows"))]
fn add_extension<P: AsRef<Path>>(&self, extension_folder: P) -> Result<(), Error> {
Err(Error::Unsupported)
}
#[cfg(target_os = "windows")]
fn add_extension<P: AsRef<Path>>(&self, extension_folder: P) -> Result<(), Error> {
windows_webview::add_extension(self, extension_folder.as_ref()).map_err(|e| Error::WindowsError(e))
}
}
#[cfg(target_os = "windows")]
mod windows_webview {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use tauri::webview::PlatformWebview;
use webview2_com_sys::Microsoft::Web::WebView2::Win32::ICoreWebView2BrowserExtension;
use webview2_com_sys::Microsoft::Web::WebView2::Win32::ICoreWebView2ProfileAddBrowserExtensionCompletedHandler;
use webview2_com_sys::Microsoft::Web::WebView2::Win32::ICoreWebView2ProfileAddBrowserExtensionCompletedHandler_Impl;
use webview2_com_sys::Microsoft::Web::WebView2::Win32::{ICoreWebView2Profile7, ICoreWebView2_13};
use windows_core::{Interface, Result, PCWSTR};
use windows_core::{implement, HRESULT};
#[implement(ICoreWebView2ProfileAddBrowserExtensionCompletedHandler)]
pub struct AddBrowserExtensionCallback;
impl ICoreWebView2ProfileAddBrowserExtensionCompletedHandler_Impl for AddBrowserExtensionCallback_Impl {
#[allow(non_snake_case)]
fn Invoke(&self, error_code: HRESULT, _extension: Option<&ICoreWebView2BrowserExtension>) -> windows_core::Result<()> {
error_code.ok()
}
}
impl AddBrowserExtensionCallback {
pub fn new() -> Self {
Self
}
}
pub(crate) fn add_extension(webview: &PlatformWebview, path: &Path) -> Result<()> {
unsafe {
let web_view2 = webview.controller().CoreWebView2()?;
let profile = web_view2.cast::<ICoreWebView2_13>()?.Profile()?;
let profile: ICoreWebView2Profile7 = profile.cast()?;
add_extension_to_profile(profile, path.as_os_str())?;
}
Ok(())
}
fn add_extension_to_profile(profile: ICoreWebView2Profile7, extension_folder: &OsStr) -> Result<()> {
unsafe {
let str: Vec<u16> = extension_folder.encode_wide()
.chain(std::iter::once(0))
.collect();
let path_ptr = PCWSTR(str.as_ptr());
let handler = AddBrowserExtensionCallback::new();
let h = ICoreWebView2ProfileAddBrowserExtensionCompletedHandler::from(handler);
profile.AddBrowserExtension(path_ptr, Some(&h))?;
}
Ok(())
}
}
Which can be used as follows:
let window = tauri::WebviewWindowBuilder::new(...)
.title(website)
.browser_extensions_enabled(true)
.build().unwrap();
window.with_webview(|webview| {
let r = webview.add_extension(r#"<extension path here>"#);
if let Err(why) = r {
println!("Failed to add extension: {why:?}");
}
}).unwrap();
(Excuse my unwrap() use)