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 3302174..6c6ad79 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 @@ -15,6 +15,8 @@ import androidx.compose.ui.awt.SwingPanel 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 kotlinx.coroutines.delay actual class WebViewFactoryParam( @@ -120,6 +122,41 @@ actual fun ActualWebView( } } + DisposableEffect(nativeWebView) { + val listener: (String) -> Boolean = a@{ + if (navigator.requestInterceptor == null) { + return@a true + } + + val webRequest = + WebRequest( + url = it, + headers = mutableMapOf(), + isForMainFrame = true, + isRedirect = true, + method = "GET", + ) + + return@a when (val interceptResult = navigator.requestInterceptor.onInterceptUrlRequest(webRequest, navigator)) { + WebRequestInterceptResult.Allow -> true + + WebRequestInterceptResult.Reject -> false + + is WebRequestInterceptResult.Modify -> { + interceptResult.request.let { modified -> + navigator.stopLoading() + navigator.loadUrl(modified.url, modified.headers) + } + false //no jump? + } + } + } + nativeWebView.addNavigateListener(listener) + onDispose { + nativeWebView.removeNavigateListener(listener) + } + } + SwingPanel( modifier = modifier, factory = { 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 7bfc6f4..8038f34 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 @@ -34,6 +34,12 @@ class WryWebViewPanel( private var pendingBounds: Bounds? = null private var boundsTimer: Timer? = null + private val handlers = mutableListOf<(String) -> Boolean>() + + private val handler = object : NavigationHandler { + override fun handleNavigation(url: String): Boolean = handlers.any { it(url) } + } + init { layout = BorderLayout() add(host, BorderLayout.CENTER) @@ -71,6 +77,14 @@ class WryWebViewPanel( scheduleCreateIfNeeded() } + fun addNavigateListener(data: (String) -> Boolean) { + handlers.add(data) + } + + fun removeNavigateListener(data: (String) -> Boolean) { + handlers.remove(data) + } + fun loadUrl(url: String) { loadUrl(url, emptyMap()) } @@ -332,9 +346,16 @@ class WryWebViewPanel( return try { webviewId = if (userAgent == null) { - NativeBindings.createWebview(handleSnapshot, width, height, initialUrl) + NativeBindings.createWebview(handleSnapshot, width, height, initialUrl, handler) } else { - NativeBindings.createWebviewWithUserAgent(handleSnapshot, width, height, initialUrl, userAgent) + NativeBindings.createWebviewWithUserAgent( + handleSnapshot, + width, + height, + initialUrl, + userAgent, + handler + ) } updateBounds() startGtkPumpIfNeeded() @@ -350,6 +371,7 @@ class WryWebViewPanel( pendingHtml = null NativeBindings.loadHtml(id, html) } + urlWithHeaders != null && headers.isNotEmpty() -> { pendingUrlWithHeaders = null pendingHeaders = emptyMap() @@ -370,9 +392,16 @@ class WryWebViewPanel( thread(name = "wry-webview-create", isDaemon = true) { val createdId = try { if (userAgent == null) { - NativeBindings.createWebview(handleSnapshot, width, height, initialUrl) + NativeBindings.createWebview(handleSnapshot, width, height, initialUrl, handler) } else { - NativeBindings.createWebviewWithUserAgent(handleSnapshot, width, height, initialUrl, userAgent) + NativeBindings.createWebviewWithUserAgent( + handleSnapshot, + width, + height, + initialUrl, + userAgent, + handler + ) } } catch (e: RuntimeException) { System.err.println("Failed to create Wry webview: ${e.message}") @@ -406,11 +435,13 @@ class WryWebViewPanel( pendingHtml = null NativeBindings.loadHtml(createdId, html) } + urlWithHeaders != null && headers.isNotEmpty() -> { pendingUrlWithHeaders = null pendingHeaders = emptyMap() NativeBindings.loadUrlWithHeaders(createdId, urlWithHeaders, headers) } + pendingUrl != initialUrl -> { NativeBindings.loadUrl(createdId, pendingUrl) } @@ -557,7 +588,13 @@ class WryWebViewPanel( } } else if (IS_MAC) { if (contentHandle != 0L && contentHandle != windowHandle) { - log("resolveParentHandle skiko content=0x${contentHandle.toString(16)} window=0x${windowHandle.toString(16)} (macOS content)") + log( + "resolveParentHandle skiko content=0x${contentHandle.toString(16)} window=0x${ + windowHandle.toString( + 16 + ) + } (macOS content)" + ) return ParentHandle(contentHandle.toULong(), false) } if (windowHandle != 0L) { @@ -570,7 +607,13 @@ class WryWebViewPanel( } } else { if (contentHandle != 0L) { - log("resolveParentHandle skiko content=0x${contentHandle.toString(16)} window=0x${windowHandle.toString(16)}") + log( + "resolveParentHandle skiko content=0x${contentHandle.toString(16)} window=0x${ + windowHandle.toString( + 16 + ) + }" + ) return ParentHandle(contentHandle.toULong(), false) } if (windowHandle != 0L) { @@ -642,8 +685,8 @@ class WryWebViewPanel( } private object NativeBindings { - fun createWebview(parentHandle: ULong, width: Int, height: Int, url: String): ULong { - return io.github.kdroidfilter.webview.wry.createWebview(parentHandle, width, height, url) + 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( @@ -652,6 +695,7 @@ private object NativeBindings { height: Int, url: String, userAgent: String, + handler: NavigationHandler, ): ULong { return io.github.kdroidfilter.webview.wry.createWebviewWithUserAgent( parentHandle, @@ -659,6 +703,7 @@ private object NativeBindings { height, url, userAgent, + handler, ) } diff --git a/wrywebview/src/main/rust/lib.rs b/wrywebview/src/main/rust/lib.rs index ac2aacc..3e2933e 100644 --- a/wrywebview/src/main/rust/lib.rs +++ b/wrywebview/src/main/rust/lib.rs @@ -11,7 +11,6 @@ mod state; use std::str::FromStr; use std::sync::atomic::Ordering; use std::sync::Arc; -use std::sync::OnceLock; use wry::cookie::time::OffsetDateTime; use wry::cookie::{Cookie, Expiration, SameSite}; @@ -172,12 +171,19 @@ macro_rules! wry_log { // WebView Creation // ============================================================================ +#[uniffi::export(callback_interface)] +pub trait NavigationHandler: Send + Sync { + /// Return true to allow navigation, false to cancel. + fn handle_navigation(&self, url: String) -> bool; +} + fn create_webview_inner( parent_handle: u64, width: i32, height: i32, url: String, user_agent: Option, + nav_handler: Option>, ) -> Result { let user_agent = user_agent.and_then(|ua| { @@ -216,11 +222,16 @@ fn create_webview_inner( let webview = builder .with_navigation_handler(move |new_url| { + if let Some(handler) = &nav_handler { + return handler.handle_navigation(new_url.to_string()); + } + wry_log!("[wrywebview] navigation_handler url={}", new_url); state_for_nav.is_loading.store(true, Ordering::SeqCst); if let Err(e) = state_for_nav.update_current_url(new_url.clone()) { wry_log!("[wrywebview] navigation_handler state update failed: {}", e); } + true }) .with_on_page_load_handler(move |event, url| { @@ -315,16 +326,17 @@ pub fn create_webview( 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) + 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)) + run_on_main_thread(move || create_webview_inner(parent_handle, width, height, url, None, nav_handler)) } #[uniffi::export] @@ -334,16 +346,17 @@ pub fn create_webview_with_user_agent( height: i32, url: String, user_agent: Option, + nav_handler: Option> ) -> Result { #[cfg(target_os = "linux")] { return run_on_gtk_thread(move || { - create_webview_inner(parent_handle, width, height, url, user_agent) + create_webview_inner(parent_handle, width, height, url, user_agent, nav_handler) }); } #[cfg(not(target_os = "linux"))] - run_on_main_thread(move || create_webview_inner(parent_handle, width, height, url, user_agent)) + run_on_main_thread(move || create_webview_inner(parent_handle, width, height, url, user_agent,nav_handler)) } // ============================================================================