tauri icon indicating copy to clipboard operation
tauri copied to clipboard

[feat] Expose AddBrowserExtension for Windows

Open Revxrsal opened this issue 1 year ago • 2 comments

Describe the problem

I can see in https://github.com/tauri-apps/tauri/issues/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

Revxrsal avatar Oct 07 '24 20:10 Revxrsal

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.

FabianLars avatar Oct 08 '24 04:10 FabianLars

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)

Revxrsal avatar Oct 08 '24 22:10 Revxrsal

Once https://github.com/tauri-apps/wry/pull/1399 is merged (which contains my with_extension_path implementation), I will be making a PR to tauri to expose that to the Tauri API 👍

SpikeHD avatar Nov 06 '24 18:11 SpikeHD

added in #11628

amrbashir avatar Nov 12 '24 23:11 amrbashir