diff --git a/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/setting/PlatformWebSettings.kt b/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/setting/PlatformWebSettings.kt index f9b44ec..6718398 100644 --- a/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/setting/PlatformWebSettings.kt +++ b/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/setting/PlatformWebSettings.kt @@ -14,6 +14,14 @@ sealed class PlatformWebSettings { data class DesktopWebSettings( var transparent: Boolean = true, + var dataDirectory: String? = null, + var initScript: String? = null, + var enableClipboard: Boolean = true, + var enableDevtools: Boolean = false, + var enableNavigationGestures: Boolean = true, + var incognito: Boolean = false, + var autoplayWithoutUserInteraction: Boolean = false, + var focused: Boolean = true, ) : PlatformWebSettings() data class IOSWebSettings( diff --git a/webview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/WebViewDesktop.kt b/webview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/WebViewDesktop.kt index 6c6ad79..011a943 100644 --- a/webview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/WebViewDesktop.kt +++ b/webview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/WebViewDesktop.kt @@ -1,34 +1,56 @@ package io.github.kdroidfilter.webview.web -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.awt.SwingPanel +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb import io.github.kdroidfilter.webview.cookie.WryCookieManager import io.github.kdroidfilter.webview.jsbridge.WebViewJsBridge import io.github.kdroidfilter.webview.jsbridge.parseJsMessage import io.github.kdroidfilter.webview.request.WebRequest import io.github.kdroidfilter.webview.request.WebRequestInterceptResult +import io.github.kdroidfilter.webview.wry.Rgba import kotlinx.coroutines.delay actual class WebViewFactoryParam( val state: WebViewState, val fileContent: String = "", - val userAgent: String? = null, ) actual fun defaultWebViewFactory(param: WebViewFactoryParam): NativeWebView = when (val content = param.state.content) { - is WebContent.Url -> NativeWebView(content.url, param.userAgent ?: param.state.webSettings.customUserAgentString) - else -> NativeWebView("about:blank", param.userAgent ?: param.state.webSettings.customUserAgentString) + is WebContent.Url -> NativeWebView( + initialUrl = content.url, + customUserAgent = param.state.webSettings.customUserAgentString, + dataDirectory = param.state.webSettings.desktopWebSettings.dataDirectory, + supportZoom = param.state.webSettings.supportZoom, + backgroundColor = param.state.webSettings.backgroundColor.toRgba(), + transparent = param.state.webSettings.desktopWebSettings.transparent, + initScript = param.state.webSettings.desktopWebSettings.initScript, + enableClipboard = param.state.webSettings.desktopWebSettings.enableClipboard, + enableDevtools = param.state.webSettings.desktopWebSettings.enableDevtools, + enableNavigationGestures = param.state.webSettings.desktopWebSettings.enableNavigationGestures, + incognito = param.state.webSettings.desktopWebSettings.incognito, + autoplayWithoutUserInteraction = param.state.webSettings.desktopWebSettings.autoplayWithoutUserInteraction, + focused = param.state.webSettings.desktopWebSettings.focused + ) + + else -> NativeWebView( + initialUrl = "about:blank", + customUserAgent = param.state.webSettings.customUserAgentString, + dataDirectory = param.state.webSettings.desktopWebSettings.dataDirectory, + supportZoom = param.state.webSettings.supportZoom, + backgroundColor = param.state.webSettings.backgroundColor.toRgba(), + transparent = param.state.webSettings.desktopWebSettings.transparent, + initScript = param.state.webSettings.desktopWebSettings.initScript, + enableClipboard = param.state.webSettings.desktopWebSettings.enableClipboard, + enableDevtools = param.state.webSettings.desktopWebSettings.enableDevtools, + enableNavigationGestures = param.state.webSettings.desktopWebSettings.enableNavigationGestures, + incognito = param.state.webSettings.desktopWebSettings.incognito, + autoplayWithoutUserInteraction = param.state.webSettings.desktopWebSettings.autoplayWithoutUserInteraction, + focused = param.state.webSettings.desktopWebSettings.focused + ) } @Composable @@ -44,18 +66,25 @@ actual fun ActualWebView( val currentOnDispose by rememberUpdatedState(onDispose) val scope = rememberCoroutineScope() - val desiredUserAgent = state.webSettings.customUserAgentString?.trim()?.takeIf { it.isNotEmpty() } - var effectiveUserAgent by remember { mutableStateOf(desiredUserAgent) } + val desiredSettingsKey = state.webSettings.let { + listOf( + it.customUserAgentString?.trim()?.takeIf(String::isNotEmpty), + it.supportZoom, + it.backgroundColor, + ) + } + + var effectiveSettingsKey by remember { mutableStateOf(desiredSettingsKey) } - LaunchedEffect(desiredUserAgent) { - if (desiredUserAgent == effectiveUserAgent) return@LaunchedEffect - // Wry applies user-agent at creation time, so recreate the webview after a small debounce. - delay(400) - effectiveUserAgent = desiredUserAgent + LaunchedEffect(desiredSettingsKey) { + if (desiredSettingsKey != effectiveSettingsKey) { + delay(400) + effectiveSettingsKey = desiredSettingsKey + } } - key(effectiveUserAgent) { - val nativeWebView = remember(state, factory) { factory(WebViewFactoryParam(state, userAgent = effectiveUserAgent)) } + key(effectiveSettingsKey) { + val nativeWebView = remember(state, factory) { factory(WebViewFactoryParam(state)) } val desktopWebView = remember(nativeWebView, scope, webViewJsBridge) { @@ -73,52 +102,58 @@ actual fun ActualWebView( } // Poll native state (URL/loading/title/nav) and drain IPC messages for JS bridge. - LaunchedEffect(nativeWebView, state, navigator, webViewJsBridge) { - while (true) { - if (!nativeWebView.isReady()) { - if (state.loadingState !is LoadingState.Initializing) { - state.loadingState = LoadingState.Initializing + listOf(nativeWebView, state, navigator, webViewJsBridge).let { + LaunchedEffect(it) { + while (true) { + if (!nativeWebView.isReady()) { + if (state.loadingState !is LoadingState.Initializing) { + state.loadingState = LoadingState.Initializing + } + delay(50) + continue } - delay(50) - continue - } - val isLoading = nativeWebView.isLoading() - state.loadingState = - if (isLoading) { - val current = state.loadingState - val next = - when (current) { - is LoadingState.Loading -> (current.progress + 0.02f).coerceAtMost(0.9f) - else -> 0.1f - } - LoadingState.Loading(next) - } else { - LoadingState.Finished + val isLoading = nativeWebView.isLoading() + state.loadingState = + if (isLoading) { + val next = + when (val current = state.loadingState) { + is LoadingState.Loading -> (current.progress + 0.02f).coerceAtMost(0.9f) + else -> 0.1f + } + LoadingState.Loading(next) + } else { + LoadingState.Finished + } + + val url = nativeWebView.getCurrentUrl() + if (!url.isNullOrBlank()) { + if (!isLoading || state.lastLoadedUrl.isNullOrBlank()) { + state.lastLoadedUrl = url + } } - val url = nativeWebView.getCurrentUrl() - if (!url.isNullOrBlank()) { - if (!isLoading || state.lastLoadedUrl.isNullOrBlank()) { - state.lastLoadedUrl = url + val title = nativeWebView.getTitle() + if (!title.isNullOrBlank()) { + state.pageTitle = title } - } - val title = nativeWebView.getTitle() - if (!title.isNullOrBlank()) { - state.pageTitle = title - } + navigator.canGoBack = nativeWebView.canGoBack() + navigator.canGoForward = nativeWebView.canGoForward() - navigator.canGoBack = nativeWebView.canGoBack() - navigator.canGoForward = nativeWebView.canGoForward() + delay(250) + } + } - if (webViewJsBridge != null) { - for (raw in nativeWebView.drainIpcMessages()) { - parseJsMessage(raw)?.let { webViewJsBridge.dispatch(it) } + LaunchedEffect(it) { + while (true) { + if (webViewJsBridge != null) { + for (raw in nativeWebView.drainIpcMessages()) { + parseJsMessage(raw)?.let { webViewJsBridge.dispatch(it) } + } } + delay(50) } - - delay(250) } } @@ -137,7 +172,8 @@ actual fun ActualWebView( method = "GET", ) - return@a when (val interceptResult = navigator.requestInterceptor.onInterceptUrlRequest(webRequest, navigator)) { + return@a when (val interceptResult = + navigator.requestInterceptor.onInterceptUrlRequest(webRequest, navigator)) { WebRequestInterceptResult.Allow -> true WebRequestInterceptResult.Reject -> false @@ -174,3 +210,12 @@ actual fun ActualWebView( } } } + +private fun Color.toRgba(): Rgba { + val argb: Int = this.toArgb() // 0xAARRGGBB (sRGB) + val a: UByte = ((argb ushr 24) and 0xFF).toUByte() + val r: UByte = ((argb ushr 16) and 0xFF).toUByte() + val g: UByte = ((argb ushr 8) and 0xFF).toUByte() + val b: UByte = (argb and 0xFF).toUByte() + return Rgba(r = r, g = g, b = b, a = a) +} diff --git a/wrywebview/Cargo.lock b/wrywebview/Cargo.lock index 71cabbb..d3416c7 100644 --- a/wrywebview/Cargo.lock +++ b/wrywebview/Cargo.lock @@ -2493,9 +2493,9 @@ dependencies = [ [[package]] name = "webkit2gtk" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -2517,9 +2517,9 @@ dependencies = [ [[package]] name = "webkit2gtk-sys" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" dependencies = [ "bitflags 1.3.2", "cairo-sys-rs", @@ -2972,9 +2972,9 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "wry" -version = "0.53.5" +version = "0.54.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2" +checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a" dependencies = [ "base64", "block2", diff --git a/wrywebview/Cargo.toml b/wrywebview/Cargo.toml index 736708b..4bf65f6 100644 --- a/wrywebview/Cargo.toml +++ b/wrywebview/Cargo.toml @@ -12,7 +12,7 @@ path = "src/main/rust/lib.rs" [dependencies] thiserror = "2.0.11" uniffi = "0.29.4" -wry = "0.53.5" +wry = { version = "0.54.2", features = ["devtools"] } [profile.release] opt-level = "z" diff --git a/wrywebview/src/main/kotlin/io/github/kdroidfilter/webview/wry/WryWebViewPanel.kt b/wrywebview/src/main/kotlin/io/github/kdroidfilter/webview/wry/WryWebViewPanel.kt index 07a7e58..972e533 100644 --- a/wrywebview/src/main/kotlin/io/github/kdroidfilter/webview/wry/WryWebViewPanel.kt +++ b/wrywebview/src/main/kotlin/io/github/kdroidfilter/webview/wry/WryWebViewPanel.kt @@ -9,12 +9,22 @@ import javax.swing.JPanel import javax.swing.SwingUtilities import javax.swing.Timer import kotlin.concurrent.thread -import kotlin.properties.Delegates class WryWebViewPanel( initialUrl: String, customUserAgent: String? = null, + dataDirectory: String? = null, + initScript: String? = null, + private val supportZoom: Boolean = true, + private val backgroundColor: Rgba, + private val transparent: Boolean = true, + private val enableClipboard: Boolean = true, + private val enableDevtools: Boolean = false, + private val enableNavigationGestures: Boolean = true, + private val incognito: Boolean = false, + private val autoplayWithoutUserInteraction: Boolean = false, + private val focused: Boolean = true, private val bridgeLogger: (String) -> Unit = { System.err.println(it) } ) : JPanel() { private val host = SkikoInterop.createHost() @@ -22,7 +32,9 @@ class WryWebViewPanel( private var parentHandle: ULong = 0UL private var parentIsWindow: Boolean = false private var pendingUrl: String = initialUrl + private val dataDirectory: String? = dataDirectory?.trim()?.takeIf { it.isNotEmpty() } private val customUserAgent: String? = customUserAgent?.trim()?.takeIf { it.isNotEmpty() } + private val initScript: String? = initScript?.trim()?.takeIf { it.isNotEmpty() } private var pendingUrlWithHeaders: String? = null private var pendingHeaders: Map = emptyMap() private var pendingHtml: String? = null @@ -312,6 +324,26 @@ class WryWebViewPanel( log("requestWebViewFocus webviewId=$webviewId") } + fun openDevTools() { + val action = { webviewId?.let { NativeBindings.openDevTools(it) } } + if (SwingUtilities.isEventDispatchThread()) { + action() + } else { + SwingUtilities.invokeLater { action() } + } + log("openDevTools webviewId=$webviewId") + } + + fun closeDevTools() { + val action = { webviewId?.let { NativeBindings.closeDevTools(it) } } + if (SwingUtilities.isEventDispatchThread()) { + action() + } else { + SwingUtilities.invokeLater { action() } + } + log("closeDevTools webviewId=$webviewId") + } + private fun createIfNeeded(): Boolean { if (webviewId != null) return true if (createInFlight) return false @@ -342,23 +374,30 @@ class WryWebViewPanel( val width = host.width.coerceAtLeast(1) val height = host.height.coerceAtLeast(1) val userAgent = customUserAgent + val dataDir = dataDirectory val initialUrl = pendingUrl val handleSnapshot = parentHandle if (!IS_MAC) { return try { - webviewId = - if (userAgent == null) { - NativeBindings.createWebview(handleSnapshot, width, height, initialUrl, handler) - } else { - NativeBindings.createWebviewWithUserAgent( - handleSnapshot, - width, - height, - initialUrl, - userAgent, - handler - ) - } + webviewId = NativeBindings.createWebview( + parentHandle = handleSnapshot, + width = width, + height = height, + url = initialUrl, + userAgent = userAgent, + dataDirectory = dataDir, + zoom = supportZoom, + transparent = transparent, + backgroundColor = backgroundColor, + initScript = initScript, + clipboard = enableClipboard, + devTools = enableDevtools, + navigationGestures = enableNavigationGestures, + incognito = incognito, + autoplay = autoplayWithoutUserInteraction, + focused = focused, + navHandler = handler + ) updateBounds() startGtkPumpIfNeeded() startWindowsPumpIfNeeded() @@ -393,18 +432,25 @@ class WryWebViewPanel( stopCreateTimer() thread(name = "wry-webview-create", isDaemon = true) { val createdId = try { - if (userAgent == null) { - NativeBindings.createWebview(handleSnapshot, width, height, initialUrl, handler) - } else { - NativeBindings.createWebviewWithUserAgent( - handleSnapshot, - width, - height, - initialUrl, - userAgent, - handler - ) - } + NativeBindings.createWebview( + parentHandle = handleSnapshot, + width = width, + height = height, + url = initialUrl, + userAgent = userAgent, + dataDirectory = dataDir, + zoom = supportZoom, + transparent = transparent, + backgroundColor = backgroundColor, + initScript = initScript, + clipboard = enableClipboard, + devTools = enableDevtools, + navigationGestures = enableNavigationGestures, + incognito = incognito, + autoplay = autoplayWithoutUserInteraction, + focused = focused, + navHandler = handler + ) } catch (e: RuntimeException) { System.err.println("Failed to create Wry webview: ${e.message}") e.printStackTrace() @@ -702,25 +748,43 @@ class WryWebViewPanel( private object NativeBindings { - fun createWebview(parentHandle: ULong, width: Int, height: Int, url: String, handler: NavigationHandler): ULong { - return io.github.kdroidfilter.webview.wry.createWebview(parentHandle, width, height, url, handler) - } - - fun createWebviewWithUserAgent( + fun createWebview( parentHandle: ULong, width: Int, height: Int, url: String, - userAgent: String, - handler: NavigationHandler, + userAgent: String?, + dataDirectory: String?, + zoom: Boolean, + transparent: Boolean, + backgroundColor: Rgba, + initScript: String?, + clipboard: Boolean, + devTools: Boolean, + navigationGestures: Boolean, + incognito: Boolean, + autoplay: Boolean, + focused: Boolean, + navHandler: NavigationHandler? ): ULong { - return io.github.kdroidfilter.webview.wry.createWebviewWithUserAgent( - parentHandle, - width, - height, - url, - userAgent, - handler, + return io.github.kdroidfilter.webview.wry.createWebview( + parentHandle = parentHandle, + width = width, + height = height, + url = url, + userAgent = userAgent, + dataDirectory = dataDirectory, + zoom = zoom, + transparent = transparent, + backgroundColor = backgroundColor, + initScript = initScript, + clipboard = clipboard, + devTools = devTools, + navigationGestures = navigationGestures, + incognito = incognito, + autoplay = autoplay, + focused = focused, + navHandler = navHandler ) } @@ -733,10 +797,10 @@ private object NativeBindings { } fun loadUrlWithHeaders(id: ULong, url: String, additionalHttpHeaders: Map) { - io.github.kdroidfilter.webview.wry.loadUrlWithHeaders( - id, - url, - additionalHttpHeaders.map { (name, value) -> HttpHeader(name, value) }, + loadUrlWithHeaders( + id = id, + url = url, + headers = additionalHttpHeaders.map { (name, value) -> HttpHeader(name, value) }, ) } @@ -761,7 +825,7 @@ private object NativeBindings { } fun evaluateJavaScript(id: ULong, script: String, callback: JavaScriptCallback) { - io.github.kdroidfilter.webview.wry.evaluateJavascript(id, script, callback) + evaluateJavascript(id, script, callback) } fun getUrl(id: ULong): String { @@ -819,4 +883,12 @@ private object NativeBindings { fun focus(id: ULong) { io.github.kdroidfilter.webview.wry.focus(id) } + + fun openDevTools(id: ULong) { + io.github.kdroidfilter.webview.wry.openDevTools(id) + } + + fun closeDevTools(id: ULong) { + io.github.kdroidfilter.webview.wry.closeDevTools(id) + } } diff --git a/wrywebview/src/main/rust/lib.rs b/wrywebview/src/main/rust/lib.rs index 6653810..9369b72 100644 --- a/wrywebview/src/main/rust/lib.rs +++ b/wrywebview/src/main/rust/lib.rs @@ -8,6 +8,7 @@ mod handle; mod platform; mod state; +use std::path::PathBuf; use std::str::FromStr; use std::sync::atomic::Ordering; use std::sync::{Arc, OnceLock, RwLock}; @@ -16,7 +17,7 @@ use wry::cookie::time::OffsetDateTime; use wry::cookie::{Cookie, Expiration, SameSite}; use wry::http::header::HeaderName; use wry::http::{HeaderMap, HeaderValue}; -use wry::WebViewBuilder; +use wry::{WebContext, WebViewBuilder, RGBA}; pub use error::WebViewError; @@ -68,6 +69,27 @@ pub struct WebViewCookie { pub is_http_only: Option, } +#[derive(Debug, Clone, Copy, uniffi::Record)] +pub struct Rgba { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl From for RGBA { + fn from(v: Rgba) -> Self { + (v.r, v.g, v.b, v.a) + } +} + +impl From for Rgba { + fn from(v: RGBA) -> Self { + let (r, g, b, a) = v; + Rgba { r, g, b, a } + } +} + fn header_map_from(headers: Vec) -> Result { let mut map = HeaderMap::new(); for header in headers { @@ -215,6 +237,17 @@ fn create_webview_inner( height: i32, url: String, user_agent: Option, + data_directory: Option, + zoom: bool, + transparent: bool, + background_color: Rgba, + init_script: Option, + clipboard: bool, + dev_tools: bool, + navigation_gestures: bool, + incognito: bool, + autoplay: bool, + focused: bool, nav_handler: Option>, ) -> Result { let user_agent = @@ -224,12 +257,13 @@ fn create_webview_inner( }); wry_log!( - "[wrywebview] create_webview handle=0x{:x} size={}x{} url={} user_agent={}", + "[wrywebview] create_webview handle=0x{:x} size={}x{} url={} user_agent={} data_directory={}", parent_handle, width, height, url, - user_agent.as_deref().unwrap_or("") + user_agent.as_deref().unwrap_or(""), + data_directory.as_deref().unwrap_or("") ); let raw = raw_window_handle_from(parent_handle)?; @@ -244,10 +278,31 @@ fn create_webview_inner( let state_for_title = Arc::clone(&state); let state_for_ipc = Arc::clone(&state); - let mut builder = WebViewBuilder::new() + let mut web_context = data_directory.map(|path| WebContext::new(Some(PathBuf::from(path)))); + + let mut builder = if let Some(ref mut context) = web_context { + WebViewBuilder::new_with_web_context(context) + } else { + WebViewBuilder::new() + }; + + builder = builder + .with_hotkeys_zoom(zoom) + .with_transparent(transparent) + .with_background_color(background_color.into()) + .with_clipboard(clipboard) + .with_devtools(dev_tools) + .with_back_forward_navigation_gestures(navigation_gestures) + .with_incognito(incognito) + .with_autoplay(autoplay) + .with_focused(focused) .with_url(&url) .with_bounds(make_bounds(0, 0, width, height)); + if let Some(is) = init_script { + builder = builder.with_initialization_script(is); + } + if let Some(ua) = user_agent { builder = builder.with_user_agent(ua); } @@ -347,48 +402,77 @@ fn create_webview_inner( wry_log!("[wrywebview] gtk focus handling configured with X11 support"); } - let id = register(webview, state)?; + let id = register(webview, state, web_context)?; wry_log!("[wrywebview] create_webview success id={}", id); Ok(id) } #[uniffi::export] pub fn create_webview( - parent_handle: u64, - width: i32, - height: i32, - url: String, - nav_handler: Option> -) -> Result { - #[cfg(target_os = "linux")] - { - return run_on_gtk_thread(move || { - create_webview_inner(parent_handle, width, height, url, None, nav_handler) - }); - } - - #[cfg(not(target_os = "linux"))] - run_on_main_thread(move || create_webview_inner(parent_handle, width, height, url, None, nav_handler)) -} - -#[uniffi::export] -pub fn create_webview_with_user_agent( parent_handle: u64, width: i32, height: i32, url: String, user_agent: Option, + data_directory: Option, + zoom: bool, + transparent: bool, + background_color: Rgba, + init_script: Option, + clipboard: bool, + dev_tools: bool, + navigation_gestures: bool, + incognito: bool, + autoplay: bool, + focused: bool, nav_handler: Option> ) -> Result { #[cfg(target_os = "linux")] { return run_on_gtk_thread(move || { - create_webview_inner(parent_handle, width, height, url, user_agent, nav_handler) + create_webview_inner( + parent_handle, + width, + height, + url, + user_agent, + data_directory, + zoom, + transparent, + background_color, + init_script, + clipboard, + dev_tools, + navigation_gestures, + incognito, + autoplay, + focused, + nav_handler + ) }); } #[cfg(not(target_os = "linux"))] - run_on_main_thread(move || create_webview_inner(parent_handle, width, height, url, user_agent,nav_handler)) + run_on_main_thread( + move || create_webview_inner( + parent_handle, + width, height, + url, + user_agent, + data_directory, + zoom, + transparent, + background_color, + init_script, + clipboard, + dev_tools, + navigation_gestures, + incognito, + autoplay, + focused, + nav_handler + ) + ) } // ============================================================================ @@ -847,6 +931,48 @@ pub fn set_cookie(id: u64, cookie: WebViewCookie) -> Result<(), WebViewError> { run_on_main_thread(move || set_cookie_inner(id, cookie)) } +// ============================================================================ +// DevTools +// ============================================================================ + +fn open_dev_tools_inner(id: u64) -> Result<(), WebViewError> { + wry_log!("[wrywebview] open_dev_tools id={}", id); + with_webview(id, |webview| { + webview.open_devtools(); + Ok(()) + }) +} + +#[uniffi::export] +pub fn open_dev_tools(id: u64) -> Result<(), WebViewError> { + #[cfg(target_os = "linux")] + { + return run_on_gtk_thread(move || open_dev_tools_inner(id)); + } + + #[cfg(not(target_os = "linux"))] + run_on_main_thread(move || open_dev_tools_inner(id)) +} + +fn close_dev_tools_inner(id: u64) -> Result<(), WebViewError> { + wry_log!("[wrywebview] close_dev_tools id={}", id); + with_webview(id, |webview| { + webview.close_devtools(); + Ok(()) + }) +} + +#[uniffi::export] +pub fn close_dev_tools(id: u64) -> Result<(), WebViewError> { + #[cfg(target_os = "linux")] + { + return run_on_gtk_thread(move || close_dev_tools_inner(id)); + } + + #[cfg(not(target_os = "linux"))] + run_on_main_thread(move || close_dev_tools_inner(id)) +} + // ============================================================================ // Destruction // ============================================================================ diff --git a/wrywebview/src/main/rust/state.rs b/wrywebview/src/main/rust/state.rs index 241a061..3ae1f5a 100644 --- a/wrywebview/src/main/rust/state.rs +++ b/wrywebview/src/main/rust/state.rs @@ -142,16 +142,8 @@ pub struct WebViewEntry { pub ptr: *mut WebView, pub thread_id: ThreadId, pub state: Arc, -} - -impl Clone for WebViewEntry { - fn clone(&self) -> Self { - WebViewEntry { - ptr: self.ptr, - thread_id: self.thread_id, - state: Arc::clone(&self.state), - } - } + #[allow(dead_code)] + pub context: Option, } // The raw pointer is only dereferenced on the creating thread (checked at runtime). @@ -202,12 +194,17 @@ pub fn get_state(id: u64) -> Result, WebViewError> { } /// Registers a new WebView in the global registry. -pub fn register(webview: WebView, state: Arc) -> Result { +pub fn register( + webview: WebView, + state: Arc, + context: Option, +) -> Result { let id = next_id(); let entry = WebViewEntry { ptr: Box::into_raw(Box::new(webview)), thread_id: std::thread::current().id(), state, + context, }; let mut map = webviews()