diff --git a/webview-compose/src/androidMain/kotlin/io/github/kdroidfilter/webview/web/AndroidWebView.kt b/webview-compose/src/androidMain/kotlin/io/github/kdroidfilter/webview/web/AndroidWebView.kt index e509b08..8e9a87a 100644 --- a/webview-compose/src/androidMain/kotlin/io/github/kdroidfilter/webview/web/AndroidWebView.kt +++ b/webview-compose/src/androidMain/kotlin/io/github/kdroidfilter/webview/web/AndroidWebView.kt @@ -8,7 +8,7 @@ import io.github.kdroidfilter.webview.util.KLogger import kotlinx.coroutines.CoroutineScope internal class AndroidWebView( - override val webView: WebView, + override val nativeWebView: WebView, override val scope: CoroutineScope, override val webViewJsBridge: WebViewJsBridge?, ) : IWebView { @@ -16,12 +16,12 @@ internal class AndroidWebView( initWebView() } - override fun canGoBack(): Boolean = webView.canGoBack() + override fun canGoBack(): Boolean = nativeWebView.canGoBack() - override fun canGoForward(): Boolean = webView.canGoForward() + override fun canGoForward(): Boolean = nativeWebView.canGoForward() override fun loadUrl(url: String, additionalHttpHeaders: Map) { - webView.loadUrl(url, additionalHttpHeaders) + nativeWebView.loadUrl(url, additionalHttpHeaders) } override suspend fun loadHtml( @@ -32,7 +32,7 @@ internal class AndroidWebView( historyUrl: String?, ) { if (html == null) return - webView.loadDataWithBaseURL(baseUrl, html, mimeType, encoding, historyUrl) + nativeWebView.loadDataWithBaseURL(baseUrl, html, mimeType, encoding, historyUrl) } override suspend fun loadHtmlFile(fileName: String, readType: WebViewFileReadType) { @@ -52,34 +52,35 @@ internal class AndroidWebView( val selected = candidates.firstOrNull { path -> try { - webView.context.assets.open(path).close() + nativeWebView.context.assets.open(path).close() true } catch (_: Exception) { false } } ?: candidates.first() val url = "file:///android_asset/$selected" - webView.loadUrl(url) + nativeWebView.loadUrl(url) KLogger.d(tag = "AndroidWebView") { "loadUrl $url (candidates: ${candidates.joinToString()})" } } - WebViewFileReadType.COMPOSE_RESOURCE_FILES -> webView.loadUrl(fileName) + + WebViewFileReadType.COMPOSE_RESOURCE_FILES -> nativeWebView.loadUrl(fileName) } } - override fun goBack() = webView.goBack() + override fun goBack() = nativeWebView.goBack() - override fun goForward() = webView.goForward() + override fun goForward() = nativeWebView.goForward() - override fun reload() = webView.reload() + override fun reload() = nativeWebView.reload() - override fun stopLoading() = webView.stopLoading() + override fun stopLoading() = nativeWebView.stopLoading() override fun evaluateJavaScript(script: String, callback: ((String) -> Unit)?) { val androidScript = "javascript:$script" KLogger.d { "evaluateJavaScript: $androidScript" } - webView.evaluateJavascript(androidScript, callback) + nativeWebView.evaluateJavascript(androidScript, callback) } override fun injectJsBridge() { @@ -97,7 +98,7 @@ internal class AndroidWebView( } override fun initJsBridge(webViewJsBridge: WebViewJsBridge) { - webView.addJavascriptInterface(this, "androidJsBridge") + nativeWebView.addJavascriptInterface(this, "androidJsBridge") } @JavascriptInterface diff --git a/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/IWebView.kt b/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/IWebView.kt index 7366d17..efb4ecb 100644 --- a/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/IWebView.kt +++ b/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/IWebView.kt @@ -9,7 +9,7 @@ expect class NativeWebView * Platform WebView abstraction. */ interface IWebView { - val webView: NativeWebView + val nativeWebView: NativeWebView val scope: CoroutineScope diff --git a/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/WebViewState.kt b/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/WebViewState.kt index b448504..d613e1a 100644 --- a/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/WebViewState.kt +++ b/webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/WebViewState.kt @@ -1,12 +1,6 @@ package io.github.kdroidfilter.webview.web -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.runtime.snapshots.SnapshotStateList import io.github.kdroidfilter.webview.cookie.CookieManager import io.github.kdroidfilter.webview.cookie.WebViewCookieManager @@ -34,10 +28,8 @@ class WebViewState( val webSettings: WebSettings by mutableStateOf(WebSettings()) - internal var webView by mutableStateOf(null) - - val nativeWebView: NativeWebView - get() = webView?.webView ?: error("WebView is not initialized") + var webView: IWebView? by mutableStateOf(null) + internal set val cookieManager: CookieManager by mutableStateOf(WebViewCookieManager()) } diff --git a/webview-compose/src/iosMain/kotlin/io/github/kdroidfilter/webview/web/IOSWebView.kt b/webview-compose/src/iosMain/kotlin/io/github/kdroidfilter/webview/web/IOSWebView.kt index 0329f21..bb61977 100644 --- a/webview-compose/src/iosMain/kotlin/io/github/kdroidfilter/webview/web/IOSWebView.kt +++ b/webview-compose/src/iosMain/kotlin/io/github/kdroidfilter/webview/web/IOSWebView.kt @@ -5,23 +5,13 @@ import io.github.kdroidfilter.webview.jsbridge.WebViewJsBridge import io.github.kdroidfilter.webview.util.KLogger import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.coroutines.CoroutineScope -import platform.Foundation.NSArray -import platform.Foundation.NSBundle -import platform.Foundation.NSDocumentDirectory -import platform.Foundation.NSFileManager -import platform.Foundation.NSMutableURLRequest -import platform.Foundation.NSSearchPathForDirectoriesInDomains -import platform.Foundation.NSString -import platform.Foundation.NSURL -import platform.Foundation.NSUserDomainMask -import platform.Foundation.setValue -import platform.Foundation.stringByDeletingLastPathComponent +import platform.Foundation.* import platform.WebKit.WKWebView internal const val IOS_JS_BRIDGE_HANDLER_NAME: String = "iosJsBridge" internal class IOSWebView( - override val webView: WKWebView, + override val nativeWebView: WKWebView, override val scope: CoroutineScope, override val webViewJsBridge: WebViewJsBridge?, ) : IWebView { @@ -29,9 +19,9 @@ internal class IOSWebView( initWebView() } - override fun canGoBack(): Boolean = webView.canGoBack + override fun canGoBack(): Boolean = nativeWebView.canGoBack - override fun canGoForward(): Boolean = webView.canGoForward + override fun canGoForward(): Boolean = nativeWebView.canGoForward override fun loadUrl(url: String, additionalHttpHeaders: Map) { if (url.startsWith("file://")) { @@ -52,7 +42,7 @@ internal class IOSWebView( } if (readAccessURL != null) { - webView.loadFileURL(fileURL, readAccessURL) + nativeWebView.loadFileURL(fileURL, readAccessURL) return } } @@ -66,7 +56,7 @@ internal class IOSWebView( forHTTPHeaderField = key, ) } - webView.loadRequest(request = request) + nativeWebView.loadRequest(request = request) } override suspend fun loadHtml( @@ -77,7 +67,7 @@ internal class IOSWebView( historyUrl: String?, ) { if (html == null) return - webView.loadHTMLString( + nativeWebView.loadHTMLString( string = html, baseURL = baseUrl?.let { NSURL.URLWithString(it) }, ) @@ -154,7 +144,7 @@ internal class IOSWebView( return } - webView.loadFileURL(fileURL, readAccessURL!!) + nativeWebView.loadFileURL(fileURL, readAccessURL!!) } catch (e: Exception) { KLogger.e(e, tag = "IOSWebView") { "Error loading HTML file: $fileName (readType: $readType)" } val errorHtml = @@ -172,24 +162,24 @@ internal class IOSWebView( } override fun goBack() { - webView.goBack() + nativeWebView.goBack() } override fun goForward() { - webView.goForward() + nativeWebView.goForward() } override fun reload() { - webView.reload() + nativeWebView.reload() } override fun stopLoading() { - webView.stopLoading() + nativeWebView.stopLoading() } @OptIn(ExperimentalForeignApi::class) override fun evaluateJavaScript(script: String, callback: ((String) -> Unit)?) { - webView.evaluateJavaScript(script) { result, error -> + nativeWebView.evaluateJavaScript(script) { result, error -> if (callback == null) return@evaluateJavaScript if (error != null) { KLogger.e { "evaluateJavaScript error: $error" } @@ -217,6 +207,9 @@ internal class IOSWebView( override fun initJsBridge(webViewJsBridge: WebViewJsBridge) { val jsMessageHandler = WKJsMessageHandler(webViewJsBridge) - webView.configuration.userContentController.addScriptMessageHandler(jsMessageHandler, IOS_JS_BRIDGE_HANDLER_NAME) + nativeWebView.configuration.userContentController.addScriptMessageHandler( + scriptMessageHandler = jsMessageHandler, + name = IOS_JS_BRIDGE_HANDLER_NAME + ) } } diff --git a/webview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/DesktopWebView.kt b/webview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/DesktopWebView.kt index 79bfcee..d838800 100644 --- a/webview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/DesktopWebView.kt +++ b/webview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/DesktopWebView.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.CoroutineScope import java.net.URL internal class DesktopWebView( - override val webView: NativeWebView, + override val nativeWebView: NativeWebView, override val scope: CoroutineScope, override val webViewJsBridge: WebViewJsBridge?, ) : IWebView { @@ -14,15 +14,15 @@ internal class DesktopWebView( initWebView() } - override fun canGoBack(): Boolean = webView.canGoBack() + override fun canGoBack(): Boolean = nativeWebView.canGoBack() - override fun canGoForward(): Boolean = webView.canGoForward() + override fun canGoForward(): Boolean = nativeWebView.canGoForward() override fun loadUrl( url: String, additionalHttpHeaders: Map, ) { - webView.loadUrl(url, additionalHttpHeaders) + nativeWebView.loadUrl(url, additionalHttpHeaders) } override suspend fun loadHtml( @@ -33,7 +33,7 @@ internal class DesktopWebView( historyUrl: String?, ) { if (html == null) return - webView.loadHtml(html) + nativeWebView.loadHtml(html) } override suspend fun loadHtmlFile( @@ -58,7 +58,8 @@ internal class DesktopWebView( candidates.add("compose-resources/assets/$normalized") candidates.add("composeResources/files/$normalized") candidates.add("composeResources/assets/$normalized") - val loaders = listOfNotNull(Thread.currentThread().contextClassLoader, this::class.java.classLoader) + val loaders = + listOfNotNull(Thread.currentThread().contextClassLoader, this::class.java.classLoader) candidates.asSequence() .mapNotNull { path -> loaders.asSequence() @@ -69,6 +70,7 @@ internal class DesktopWebView( .firstOrNull() ?: error("Resource not found: ${candidates.joinToString()}") } + WebViewFileReadType.COMPOSE_RESOURCE_FILES -> URL(fileName).openStream().use { it.readBytes().toString(Charsets.UTF_8) } } @@ -87,23 +89,23 @@ internal class DesktopWebView( """.trimIndent() KLogger.e(e, tag = "DesktopWebView") { "loadHtmlFile failed" } errorHtml - } - webView.loadHtml(html) + } + nativeWebView.loadHtml(html) } - override fun goBack() = webView.goBack() + override fun goBack() = nativeWebView.goBack() - override fun goForward() = webView.goForward() + override fun goForward() = nativeWebView.goForward() - override fun reload() = webView.reload() + override fun reload() = nativeWebView.reload() - override fun stopLoading() = webView.stopLoading() + override fun stopLoading() = nativeWebView.stopLoading() override fun evaluateJavaScript(script: String, callback: ((String) -> Unit)?) { KLogger.d { "evaluateJavaScript: $script" } - webView.evaluateJavaScript(script) { result -> + nativeWebView.evaluateJavaScript(script) { result -> callback?.invoke(result) } } 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 011a943..cc741b8 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 @@ -84,16 +84,17 @@ actual fun ActualWebView( } key(effectiveSettingsKey) { - val nativeWebView = remember(state, factory) { factory(WebViewFactoryParam(state)) } - - val desktopWebView = - remember(nativeWebView, scope, webViewJsBridge) { - DesktopWebView( - webView = nativeWebView, - scope = scope, - webViewJsBridge = webViewJsBridge, - ) - } + val nativeWebView = remember(state, factory) { + state.webView?.nativeWebView ?: factory(WebViewFactoryParam(state)) + } + + val desktopWebView = remember(nativeWebView, scope, webViewJsBridge) { + DesktopWebView( + nativeWebView = nativeWebView, + scope = scope, + webViewJsBridge = webViewJsBridge, + ) + } LaunchedEffect(desktopWebView) { state.webView = desktopWebView 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 67f577a..d020588 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 @@ -388,6 +388,11 @@ class WryWebViewPanel( val dataDir = dataDirectory val initialUrl = pendingUrl val handleSnapshot = parentHandle + + if (!host.isDisplayable) { + return false + } + if (!IS_MAC) { return try { webviewId = NativeBindings.createWebview( @@ -439,6 +444,7 @@ class WryWebViewPanel( true } } + createInFlight = true stopCreateTimer() thread(name = "wry-webview-create", isDaemon = true) {