Repository: tauri-apps/wry (language: rust)

Issue:
Launch Webviews as incognito
**Is your feature request related to a problem? Please describe.**
For browser based OAuth where you cannot guarantee the existence of a web browser but can guarantee the existence of a WebView, having an option to launch a webview as incognito would be useful.

**Would you want to assign yourself to implement this feature?**
- [x] Yes
- [ ] No

Other (I did a PoC for WebView2)

**Additional context**
On Windows, it could be done by modifying ```InnerWebView::create_controller``` to something like this:
```
  fn create_controller(
    hwnd: HWND,
    env: &ICoreWebView2Environment,
    incognito: bool
  ) -> webview2_com::Result<ICoreWebView2Controller> {
    let (tx, rx) = mpsc::channel();
    let env = env.clone().cast::<ICoreWebView2Environment10>()?;
    let controller_opts = unsafe {env.CreateCoreWebView2ControllerOptions()?};

    unsafe {controller_opts.SetIsInPrivateModeEnabled(incognito)?}

    CreateCoreWebView2ControllerCompletedHandler::wait_for_async_operation(
      Box::new(move |handler| unsafe {
        env
          .CreateCoreWebView2ControllerWithOptions(hwnd, &controller_opts, &handler)
          .map_err(webview2_com::Error::WindowsError)
      }),
      Box::new(move |error_code, controller| {
        error_code?;
        tx.send(controller.ok_or_else(|| windows::core::Error::from(E_POINTER)))
          .expect("send over mpsc channel");
        Ok(())
      }),
    )?;

    rx.recv()
      .map_err(|_| webview2_com::Error::SendError)?
      .map_err(webview2_com::Error::WindowsError)
  }
```
It would be needed to cast ```env``` to ICoreWebView2Environment10 first as ICoreWebView2Environment wouldn't have CreateCoreWebView2ControllerWithOptions.

Launch Webviews as incognito
**Is your feature request related to a problem? Please describe.**
For browser based OAuth where you cannot guarantee the existence of a web browser but can guarantee the existence of a WebView, having an option to launch a webview as incognito would be useful.

**Would you want to assign yourself to implement this feature?**
- [x] Yes
- [ ] No

Other (I did a PoC for WebView2)

**Additional context**
On Windows, it could be done by modifying ```InnerWebView::create_controller``` to something like this:
```
  fn create_controller(
    hwnd: HWND,
    env: &ICoreWebView2Environment,
    incognito: bool
  ) -> webview2_com::Result<ICoreWebView2Controller> {
    let (tx, rx) = mpsc::channel();
    let env = env.clone().cast::<ICoreWebView2Environment10>()?;
    let controller_opts = unsafe {env.CreateCoreWebView2ControllerOptions()?};

    unsafe {controller_opts.SetIsInPrivateModeEnabled(incognito)?}

    CreateCoreWebView2ControllerCompletedHandler::wait_for_async_operation(
      Box::new(move |handler| unsafe {
        env
          .CreateCoreWebView2ControllerWithOptions(hwnd, &controller_opts, &handler)
          .map_err(webview2_com::Error::WindowsError)
      }),
      Box::new(move |error_code, controller| {
        error_code?;
        tx.send(controller.ok_or_else(|| windows::core::Error::from(E_POINTER)))
          .expect("send over mpsc channel");
        Ok(())
      }),
    )?;

    rx.recv()
      .map_err(|_| webview2_com::Error::SendError)?
      .map_err(webview2_com::Error::WindowsError)
  }
```
It would be needed to cast ```env``` to ICoreWebView2Environment10 first as ICoreWebView2Environment wouldn't have CreateCoreWebView2ControllerWithOptions.



Discussion / hints:
webkit2gtk can enable it via [SettingsExt](https://docs.rs/webkit2gtk/latest/webkit2gtk/trait.SettingsExt.html#tymethod.set_enable_private_browsing) trait.
webkit2gtk can enable it via [SettingsExt](https://docs.rs/webkit2gtk/latest/webkit2gtk/trait.SettingsExt.html#tymethod.set_enable_private_browsing) trait.

Fix patch:
diff --git a/.changes/as_incognito.md b/.changes/as_incognito.md
new file mode 100644
index 000000000..78d827f7a
--- /dev/null
+++ b/.changes/as_incognito.md
@@ -0,0 +1,5 @@
+---
+"wry": patch
+---
+
+Adds a way to launch a WebView as incognito through a new API at WebViewBuilder named as `with_incognito`.
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 06f77738c..4717df7e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,4 @@ Cargo.lock
 gh-pages
 .DS_Store
 examples/test_video.mp4
-.vscode/
\ No newline at end of file
+.vscode/
diff --git a/examples/hello_world.rs b/examples/hello_world.rs
index fa0f21580..f74d27625 100644
--- a/examples/hello_world.rs
+++ b/examples/hello_world.rs
@@ -17,7 +17,8 @@ fn main() -> wry::Result<()> {
     .with_title("Hello World")
     .build(&event_loop)?;
   let _webview = WebViewBuilder::new(window)?
-    .with_url("https://html5test.com")?
+    .with_url("https://www.netflix.com/browse")?
+    // .with_incognito(true)
     .build()?;
 
   event_loop.run(move |event, _, control_flow| {
diff --git a/src/webview/mod.rs b/src/webview/mod.rs
index 30bf85afb..495e55d2d 100644
--- a/src/webview/mod.rs
+++ b/src/webview/mod.rs
@@ -228,6 +228,14 @@ pub struct WebViewAttributes {
   /// Set a handler closure to process the change of the webview's document title.
   pub document_title_changed_handler: Option<Box<dyn Fn(&Window, String)>>,
 
+  /// Run the WebView with incognito mode. Note that WebContext will be ingored if incognito is
+  /// enabled.
+  ///
+  /// ## Platform-specific:
+  ///
+  /// - **Android:** Unsupported yet.
+  pub incognito: bool,
+
   /// Whether all media can be played without user interaction.
   pub autoplay: bool,
 }
@@ -259,6 +267,7 @@ impl Default for WebViewAttributes {
       accept_first_mouse: false,
       back_forward_navigation_gestures: false,
       document_title_changed_handler: None,
+      incognito: false,
       autoplay: true,
     }
   }
@@ -625,6 +634,17 @@ impl<'a> WebViewBuilder<'a> {
     self
   }
 
+  /// Run the WebView with incognito mode. Note that WebContext will be ingored if incognito is
+  /// enabled.
+  ///
+  /// ## Platform-specific:
+  ///
+  /// - **Android:** Unsupported yet.
+  pub fn with_incognito(mut self, incognito: bool) -> Self {
+    self.webview.incognito = incognito;
+    self
+  }
+
   /// Consume the builder and create the [`WebView`].
   ///
   /// Platform-specific behavior:
diff --git a/src/webview/web_context.rs b/src/webview/web_context.rs
index e7368517f..714e282c7 100644
--- a/src/webview/web_context.rs
+++ b/src/webview/web_context.rs
@@ -45,6 +45,19 @@ impl WebContext {
     Self { data, os }
   }
 
+  #[cfg(any(
+    target_os = "linux",
+    target_os = "dragonfly",
+    target_os = "freebsd",
+    target_os = "netbsd",
+    target_os = "openbsd"
+  ))]
+  pub(crate) fn new_ephemeral() -> Self {
+    let data = WebContextData::default();
+    let os = WebContextImpl::new_ephemeral();
+    Self { data, os }
+  }
+
   /// A reference to the data directory the context was created with.
   pub fn data_directory(&self) -> Option<&Path> {
     self.data.data_directory()
diff --git a/src/webview/webkitgtk/mod.rs b/src/webview/webkitgtk/mod.rs
index 75c22178f..1f07333ed 100644
--- a/src/webview/webkitgtk/mod.rs
+++ b/src/webview/webkitgtk/mod.rs
@@ -16,9 +16,9 @@ use std::{
 };
 use url::Url;
 use webkit2gtk::{
-  traits::*, AutoplayPolicy, LoadEvent, NavigationPolicyDecision, PolicyDecisionType, URIRequest,
-  UserContentInjectedFrames, UserScript, UserScriptInjectionTime, WebView, WebViewBuilder,
-  WebsitePoliciesBuilder,
+  traits::*, AutoplayPolicy, LoadEvent, NavigationPolicyDecision, PolicyDecisionType, SettingsExt,
+  URIRequest, UserContentInjectedFrames, UserScript, UserScriptInjectionTime, WebView,
+  WebViewBuilder, WebsitePoliciesBuilder,
 };
 use webkit2gtk_sys::{
   webkit_get_major_version, webkit_get_micro_version, webkit_get_minor_version,
@@ -58,11 +58,16 @@ impl InnerWebView {
 
     // default_context allows us to create a scoped context on-demand
     let mut default_context;
-    let web_context = match web_context {
-      Some(w) => w,
-      None => {
-        default_context = Default::default();
-        &mut default_context
+    let web_context = if attributes.incognito {
+      default_context = WebContext::new_ephemeral();
+      &mut default_context
+    } else {
+      match web_context {
+        Some(w) => w,
+        None => {
+          default_context = Default::default();
+          &mut default_context
+        }
       }
     };
 
@@ -88,7 +93,6 @@ impl InnerWebView {
     let w = window_rc.clone();
     let ipc_handler = attributes.ipc_handler.take();
     let manager = web_context.manager();
-
     // Use the window hash as the script handler name to prevent from conflict when sharing same
     // web context.
     let window_hash = {
diff --git a/src/webview/webkitgtk/web_context.rs b/src/webview/webkitgtk/web_context.rs
index b0cca7aaf..1d6c0acaa 100644
--- a/src/webview/webkitgtk/web_context.rs
+++ b/src/webview/webkitgtk/web_context.rs
@@ -37,8 +37,6 @@ pub struct WebContextImpl {
 
 impl WebContextImpl {
   pub fn new(data: &WebContextData) -> Self {
-    use webkit2gtk::traits::*;
-
     let mut context_builder = WebContextBuilder::new();
     if let Some(data_directory) = data.data_directory() {
       let data_manager = WebsiteDataManagerBuilder::new()
@@ -58,9 +56,19 @@ impl WebContextImpl {
       }
       context_builder = context_builder.website_data_manager(&data_manager);
     }
-
     let context = context_builder.build();
 
+    Self::create_context(context)
+  }
+
+  pub fn new_ephemeral() -> Self {
+    let context = WebContext::new_ephemeral();
+
+    Self::create_context(context)
+  }
+
+  pub fn create_context(context: WebContext) -> Self {
+    use webkit2gtk::traits::*;
     let automation = false;
     context.set_automation_allowed(automation);
 
diff --git a/src/webview/webview2/mod.rs b/src/webview/webview2/mod.rs
index 773fa7356..0f76ae486 100644
--- a/src/webview/webview2/mod.rs
+++ b/src/webview/webview2/mod.rs
@@ -73,7 +73,7 @@ impl InnerWebView {
     let file_drop_window = window.clone();
 
     let env = Self::create_environment(&web_context, pl_attrs.clone(), attributes.autoplay)?;
-    let controller = Self::create_controller(hwnd, &env)?;
+    let controller = Self::create_controller(hwnd, &env, attributes.incognito)?;
     let webview = Self::init_webview(window, hwnd, attributes, &env, &controller, pl_attrs)?;
 
     if let Some(file_drop_handler) = file_drop_handler {
@@ -173,14 +173,18 @@ impl InnerWebView {
   fn create_controller(
     hwnd: HWND,
     env: &ICoreWebView2Environment,
+    incognito: bool,
   ) -> webview2_com::Result<ICoreWebView2Controller> {
     let (tx, rx) = mpsc::channel();
-    let env = env.clone();
+    let env = env.clone().cast::<ICoreWebView2Environment10>()?;
+    let controller_opts = unsafe { env.CreateCoreWebView2ControllerOptions()? };
+
+    unsafe { controller_opts.SetIsInPrivateModeEnabled(incognito)? }
 
     CreateCoreWebView2ControllerCompletedHandler::wait_for_async_operation(
       Box::new(move |handler| unsafe {
         env
-          .CreateCoreWebView2Controller(hwnd, &handler)
+          .CreateCoreWebView2ControllerWithOptions(hwnd, &controller_opts, &handler)
           .map_err(webview2_com::Error::WindowsError)
       }),
       Box::new(move |error_code, controller| {
diff --git a/src/webview/wkwebview/mod.rs b/src/webview/wkwebview/mod.rs
index 0bbc010f1..98889c43f 100644
--- a/src/webview/wkwebview/mod.rs
+++ b/src/webview/wkwebview/mod.rs
@@ -239,6 +239,14 @@ impl InnerWebView {
       // Config and custom protocol
       let config: id = msg_send![class!(WKWebViewConfiguration), new];
       let mut protocol_ptrs = Vec::new();
+
+      // Incognito mode
+      let data_store: id = if attributes.incognito {
+        msg_send![class!(WKWebsiteDataStore), nonPersistentDataStore]
+      } else {
+        msg_send![class!(WKWebsiteDataStore), defaultDataStore]
+      };
+
       for (name, function) in attributes.custom_protocols {
         let scheme_name = format!("{}URLSchemeHandler", name);
         let cls = ClassDecl::new(&scheme_name, class!(NSObject));
@@ -299,6 +307,7 @@ impl InnerWebView {
         _ => class!(WryWebView),
       };
       let webview: id = msg_send![cls, alloc];
+      let () = msg_send![config, setWebsiteDataStore: data_store];
       let _preference: id = msg_send![config, preferences];
       let _yes: id = msg_send![class!(NSNumber), numberWithBool:1];
 


Test patch: