From 814f9b14eb3b21a1f0ecdd88da5cb283fa8b4022 Mon Sep 17 00:00:00 2001 From: Jeffrey Bush Date: Fri, 21 Jun 2024 22:23:44 -0400 Subject: [PATCH 1/7] Expose several internal fields. This adds a common webView field to the IWebView interface allowing multiplatform code to access the native web view. This native web view is exposed in the WebViewState. --- .../webview/web/AndroidWebView.kt | 4 ++- .../com/multiplatform/webview/web/IWebView.kt | 8 +++++ .../multiplatform/webview/web/WebViewState.kt | 6 ++++ .../webview/web/DesktopWebView.kt | 4 ++- .../multiplatform/webview/web/IOSWebView.kt | 32 ++++++++++--------- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/webview/src/androidMain/kotlin/com/multiplatform/webview/web/AndroidWebView.kt b/webview/src/androidMain/kotlin/com/multiplatform/webview/web/AndroidWebView.kt index 34881394..f95e5d13 100644 --- a/webview/src/androidMain/kotlin/com/multiplatform/webview/web/AndroidWebView.kt +++ b/webview/src/androidMain/kotlin/com/multiplatform/webview/web/AndroidWebView.kt @@ -12,11 +12,13 @@ import kotlinx.serialization.json.Json * Created By Kevin Zou On 2023/9/5 */ +actual typealias NativeWebView = WebView + /** * Android implementation of [IWebView] */ class AndroidWebView( - private val webView: WebView, + override val webView: WebView, override val scope: CoroutineScope, override val webViewJsBridge: WebViewJsBridge?, ) : IWebView { diff --git a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/IWebView.kt b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/IWebView.kt index e7e681ca..840e8ce9 100644 --- a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/IWebView.kt +++ b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/IWebView.kt @@ -10,10 +10,18 @@ import org.jetbrains.compose.resources.ExperimentalResourceApi * Created By Kevin Zou On 2023/9/5 */ +expect class NativeWebView + /** * Interface for WebView */ interface IWebView { + /** + * The native web view instance. On Android, this is an instance of [android.webkit.WebView]. + * On iOS, this is an instance of [WKWebView]. On desktop, this is an instance of [KCEFBrowser]. + */ + val webView: NativeWebView + val scope: CoroutineScope val webViewJsBridge: WebViewJsBridge? diff --git a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebViewState.kt b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebViewState.kt index 6add7cb7..e8360daf 100644 --- a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebViewState.kt +++ b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebViewState.kt @@ -75,6 +75,12 @@ class WebViewState(webContent: WebContent) { */ internal var webView by mutableStateOf(null) + /** + * The native web view instance. On Android, this is an instance of [android.webkit.WebView]. + * On iOS, this is an instance of [WKWebView]. On desktop, this is an instance of [KCEFBrowser]. + */ + val nativeWebView get() = webView?.webView ?: error("WebView is not initialized") + /** * The saved view state from when the view was destroyed last. To restore state, * use the navigator and only call loadUrl if the bundle is null. diff --git a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/DesktopWebView.kt b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/DesktopWebView.kt index 973c0c3a..c5d39748 100644 --- a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/DesktopWebView.kt +++ b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/DesktopWebView.kt @@ -15,11 +15,13 @@ import org.cef.network.CefPostData import org.cef.network.CefPostDataElement import org.cef.network.CefRequest +actual typealias NativeWebView = KCEFBrowser + /** * Created By Kevin Zou On 2023/9/12 */ class DesktopWebView( - private val webView: KCEFBrowser, + override val webView: KCEFBrowser, override val scope: CoroutineScope, override val webViewJsBridge: WebViewJsBridge?, ) : IWebView { diff --git a/webview/src/iosMain/kotlin/com/multiplatform/webview/web/IOSWebView.kt b/webview/src/iosMain/kotlin/com/multiplatform/webview/web/IOSWebView.kt index 597e876a..c2be7477 100644 --- a/webview/src/iosMain/kotlin/com/multiplatform/webview/web/IOSWebView.kt +++ b/webview/src/iosMain/kotlin/com/multiplatform/webview/web/IOSWebView.kt @@ -26,11 +26,13 @@ import platform.darwin.NSObjectMeta * Created By Kevin Zou On 2023/9/5 */ +actual typealias NativeWebView = WKWebView + /** * iOS implementation of [IWebView] */ class IOSWebView( - private val wkWebView: WKWebView, + override val webView: WKWebView, override val scope: CoroutineScope, override val webViewJsBridge: WebViewJsBridge?, ) : IWebView { @@ -38,9 +40,9 @@ class IOSWebView( initWebView() } - override fun canGoBack() = wkWebView.canGoBack + override fun canGoBack() = webView.canGoBack - override fun canGoForward() = wkWebView.canGoForward + override fun canGoForward() = webView.canGoForward override fun loadUrl( url: String, @@ -58,7 +60,7 @@ class IOSWebView( ) true } - wkWebView.loadRequest( + webView.loadRequest( request = request, ) } @@ -76,7 +78,7 @@ class IOSWebView( } return } - wkWebView.loadHTMLString( + webView.loadHTMLString( string = html, baseURL = baseUrl?.let { NSURL.URLWithString(it) }, ) @@ -85,7 +87,7 @@ class IOSWebView( override suspend fun loadHtmlFile(fileName: String) { val res = NSBundle.mainBundle.resourcePath + "/compose-resources/assets/" + fileName val url = NSURL.fileURLWithPath(res) - wkWebView.loadFileURL(url, url) + webView.loadFileURL(url, url) } @OptIn(ExperimentalForeignApi::class, BetaInteropApi::class) @@ -104,30 +106,30 @@ class IOSWebView( NSData.create(bytes = allocArrayOf(postData), length = postData.size.toULong()) } } - wkWebView.loadRequest(request = request) + webView.loadRequest(request = request) } override fun goBack() { - wkWebView.goBack() + webView.goBack() } override fun goForward() { - wkWebView.goForward() + webView.goForward() } override fun reload() { - wkWebView.reload() + webView.reload() } override fun stopLoading() { - wkWebView.stopLoading() + webView.stopLoading() } override fun evaluateJavaScript( script: String, callback: ((String) -> Unit)?, ) { - wkWebView.evaluateJavaScript(script) { result, error -> + webView.evaluateJavaScript(script) { result, error -> if (callback == null) return@evaluateJavaScript if (error != null) { KLogger.e { "evaluateJavaScript error: $error" } @@ -157,7 +159,7 @@ class IOSWebView( override fun initJsBridge(webViewJsBridge: WebViewJsBridge) { KLogger.info { "injectBridge" } val jsMessageHandler = WKJsMessageHandler(webViewJsBridge) - wkWebView.configuration.userContentController.apply { + webView.configuration.userContentController.apply { addScriptMessageHandler(jsMessageHandler, "iosJsBridge") } } @@ -167,13 +169,13 @@ class IOSWebView( if (getPlatformVersionDouble() < 15.0) { return null } - val data = wkWebView.interactionState as NSData? + val data = webView.interactionState as NSData? return data } @OptIn(ExperimentalForeignApi::class) override fun scrollOffset(): Pair { - val offset = wkWebView.scrollView.contentOffset + val offset = webView.scrollView.contentOffset offset.useContents { return Pair(x.toInt(), y.toInt()) } From 3279e13d28846069e7456b125fb7e63f14aed477 Mon Sep 17 00:00:00 2001 From: Jeffrey Bush Date: Fri, 21 Jun 2024 23:19:12 -0400 Subject: [PATCH 2/7] Adding NativeWebView arguments to onCreated and onDispose callbacks --- .../multiplatform/webview/web/WebView.android.kt | 8 ++++---- .../com/multiplatform/webview/web/WebView.kt | 10 ++++------ .../multiplatform/webview/web/WebView.desktop.kt | 12 ++++++------ .../com/multiplatform/webview/web/WebView.ios.kt | 14 +++++++------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/webview/src/androidMain/kotlin/com/multiplatform/webview/web/WebView.android.kt b/webview/src/androidMain/kotlin/com/multiplatform/webview/web/WebView.android.kt index 00ffbde4..72818033 100644 --- a/webview/src/androidMain/kotlin/com/multiplatform/webview/web/WebView.android.kt +++ b/webview/src/androidMain/kotlin/com/multiplatform/webview/web/WebView.android.kt @@ -14,8 +14,8 @@ actual fun ActualWebView( captureBackPresses: Boolean, navigator: WebViewNavigator, webViewJsBridge: WebViewJsBridge?, - onCreated: () -> Unit, - onDispose: () -> Unit, + onCreated: (NativeWebView) -> Unit, + onDispose: (NativeWebView) -> Unit, ) { AccompanistWebView( state, @@ -23,7 +23,7 @@ actual fun ActualWebView( captureBackPresses, navigator, webViewJsBridge, - onCreated = { _ -> onCreated() }, - onDispose = { _ -> onDispose() }, + onCreated = onCreated, + onDispose = onDispose, ) } diff --git a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt index f460bc25..0f3f0702 100644 --- a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt +++ b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt @@ -10,7 +10,6 @@ import com.multiplatform.webview.util.KLogger import com.multiplatform.webview.util.getPlatform import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.merge -import org.jetbrains.compose.resources.ExperimentalResourceApi /** * Created By Kevin Zou On 2023/8/31 @@ -30,7 +29,6 @@ import org.jetbrains.compose.resources.ExperimentalResourceApi * @param onDispose Called when the WebView is destroyed. * @sample sample.BasicWebViewSample */ -@OptIn(ExperimentalResourceApi::class) @Composable fun WebView( state: WebViewState, @@ -38,8 +36,8 @@ fun WebView( captureBackPresses: Boolean = true, navigator: WebViewNavigator = rememberWebViewNavigator(), webViewJsBridge: WebViewJsBridge? = null, - onCreated: () -> Unit = {}, - onDispose: () -> Unit = {}, + onCreated: (NativeWebView) -> Unit = {}, + onDispose: (NativeWebView) -> Unit = {}, ) { val webView = state.webView @@ -141,6 +139,6 @@ expect fun ActualWebView( captureBackPresses: Boolean = true, navigator: WebViewNavigator = rememberWebViewNavigator(), webViewJsBridge: WebViewJsBridge? = null, - onCreated: () -> Unit = {}, - onDispose: () -> Unit = {}, + onCreated: (NativeWebView) -> Unit = {}, + onDispose: (NativeWebView) -> Unit = {}, ) diff --git a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt index 4f2d5a5e..acdadd7f 100644 --- a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt +++ b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt @@ -20,8 +20,8 @@ actual fun ActualWebView( captureBackPresses: Boolean, navigator: WebViewNavigator, webViewJsBridge: WebViewJsBridge?, - onCreated: () -> Unit, - onDispose: () -> Unit, + onCreated: (NativeWebView) -> Unit, + onDispose: (NativeWebView) -> Unit, ) { DesktopWebView( state, @@ -43,8 +43,8 @@ fun DesktopWebView( modifier: Modifier, navigator: WebViewNavigator, webViewJsBridge: WebViewJsBridge?, - onCreated: () -> Unit, - onDispose: () -> Unit, + onCreated: (NativeWebView) -> Unit, + onDispose: (NativeWebView) -> Unit, ) { val currentOnDispose by rememberUpdatedState(onDispose) val client = @@ -132,7 +132,7 @@ fun DesktopWebView( browser?.let { SwingPanel( factory = { - onCreated() + onCreated(it) state.webView = desktopWebView webViewJsBridge?.webView = desktopWebView browser.apply { @@ -149,7 +149,7 @@ fun DesktopWebView( DisposableEffect(Unit) { onDispose { client?.dispose() - currentOnDispose() + browser?.let { currentOnDispose(it) } } } } diff --git a/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt b/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt index 643f12fd..cf4719b6 100644 --- a/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt +++ b/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt @@ -25,8 +25,8 @@ actual fun ActualWebView( captureBackPresses: Boolean, navigator: WebViewNavigator, webViewJsBridge: WebViewJsBridge?, - onCreated: () -> Unit, - onDispose: () -> Unit, + onCreated: (NativeWebView) -> Unit, + onDispose: (NativeWebView) -> Unit, ) { IOSWebView( state = state, @@ -50,8 +50,8 @@ fun IOSWebView( captureBackPresses: Boolean, navigator: WebViewNavigator, webViewJsBridge: WebViewJsBridge?, - onCreated: () -> Unit, - onDispose: () -> Unit, + onCreated: (NativeWebView) -> Unit, + onDispose: (NativeWebView) -> Unit, ) { val observer = remember { @@ -85,8 +85,8 @@ fun IOSWebView( WKWebView( frame = CGRectZero.readValue(), configuration = config, - ).apply { - onCreated() + )).apply { + onCreated(this) state.viewState?.let { this.interactionState = it } @@ -133,7 +133,7 @@ fun IOSWebView( observer = observer, ) it.navigationDelegate = null - onDispose() + onDispose(it) }, ) } From 8db0e3ac5de9d2ddcacfb461a01656be14a902a6 Mon Sep 17 00:00:00 2001 From: Jeffrey Bush Date: Mon, 24 Jun 2024 11:39:46 -0400 Subject: [PATCH 3/7] Adding a factory function for creating platform-specific WebViews. --- .../webview/web/WebView.android.kt | 9 ++ .../com/multiplatform/webview/web/WebView.kt | 22 ++++ .../webview/web/WebView.desktop.kt | 103 ++++++++---------- .../multiplatform/webview/web/WebView.ios.kt | 17 ++- 4 files changed, 89 insertions(+), 62 deletions(-) diff --git a/webview/src/androidMain/kotlin/com/multiplatform/webview/web/WebView.android.kt b/webview/src/androidMain/kotlin/com/multiplatform/webview/web/WebView.android.kt index 72818033..f20b4c68 100644 --- a/webview/src/androidMain/kotlin/com/multiplatform/webview/web/WebView.android.kt +++ b/webview/src/androidMain/kotlin/com/multiplatform/webview/web/WebView.android.kt @@ -1,5 +1,6 @@ package com.multiplatform.webview.web +import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.multiplatform.webview.jsbridge.WebViewJsBridge @@ -16,6 +17,7 @@ actual fun ActualWebView( webViewJsBridge: WebViewJsBridge?, onCreated: (NativeWebView) -> Unit, onDispose: (NativeWebView) -> Unit, + factory: (WebViewFactoryParam) -> NativeWebView, ) { AccompanistWebView( state, @@ -25,5 +27,12 @@ actual fun ActualWebView( webViewJsBridge, onCreated = onCreated, onDispose = onDispose, + factory = { factory(WebViewFactoryParam(it)) }, ) } + +/** Android WebView factory parameters: a context. */ +actual data class WebViewFactoryParam(val context: Context) + +/** Default WebView factory for Android. */ +actual fun defaultWebViewFactory(param: WebViewFactoryParam) = android.webkit.WebView(param.context) diff --git a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt index 0f3f0702..0c8eefed 100644 --- a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt +++ b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.merge * navigation from outside the composable. * @param onCreated Called when the WebView is first created. * @param onDispose Called when the WebView is destroyed. + * @param factory A function that creates a platform-specific WebView object. * @sample sample.BasicWebViewSample */ @Composable @@ -38,6 +39,7 @@ fun WebView( webViewJsBridge: WebViewJsBridge? = null, onCreated: (NativeWebView) -> Unit = {}, onDispose: (NativeWebView) -> Unit = {}, + factory: ((WebViewFactoryParam) -> NativeWebView)? = null, ) { val webView = state.webView @@ -117,6 +119,7 @@ fun WebView( webViewJsBridge = webViewJsBridge, onCreated = onCreated, onDispose = onDispose, + factory = factory ?: ::defaultWebViewFactory, ) DisposableEffect(Unit) { @@ -129,6 +132,24 @@ fun WebView( } } +/** + * Platform specific parameters given to the WebView factory function. This is a + * data class containing one or more platform-specific values necessary to + * create a platform-specific WebView: + * - On Android, this contains a `Context` object + * - On iOS, this contains a `WKWebViewConfiguration` object created from the + * provided WebSettings + * - On Desktop, this contains the WebViewState, the KCEFClient, and the + * loaded file content (if a file, otherwise, an empty string) + */ +expect class WebViewFactoryParam + +/** + * Platform specific default WebView factory function. This can be called from + * a custom factory function for any platforms that don't need to be customized. + */ +expect fun defaultWebViewFactory(param: WebViewFactoryParam): NativeWebView + /** * Expect API of [WebView] that is implemented in the platform-specific modules. */ @@ -141,4 +162,5 @@ expect fun ActualWebView( webViewJsBridge: WebViewJsBridge? = null, onCreated: (NativeWebView) -> Unit = {}, onDispose: (NativeWebView) -> Unit = {}, + factory: (WebViewFactoryParam) -> NativeWebView = ::defaultWebViewFactory, ) diff --git a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt index acdadd7f..4038504d 100644 --- a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt +++ b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt @@ -7,7 +7,9 @@ import com.multiplatform.webview.jsbridge.WebViewJsBridge import compose_webview_multiplatform.webview.generated.resources.Res import dev.datlag.kcef.KCEF import dev.datlag.kcef.KCEFBrowser +import dev.datlag.kcef.KCEFClient import org.cef.browser.CefRendering +import org.cef.browser.CefRequestContext import org.jetbrains.compose.resources.ExperimentalResourceApi /** @@ -22,6 +24,7 @@ actual fun ActualWebView( webViewJsBridge: WebViewJsBridge?, onCreated: (NativeWebView) -> Unit, onDispose: (NativeWebView) -> Unit, + factory: (WebViewFactoryParam) -> NativeWebView, ) { DesktopWebView( state, @@ -30,9 +33,44 @@ actual fun ActualWebView( webViewJsBridge, onCreated = onCreated, onDispose = onDispose, + factory = factory, ) } +/** Desktop WebView factory parameters: web view state, client, and possible file content. */ +actual class WebViewFactoryParam( + val state: WebViewState, + val client: KCEFClient, + val fileContent: String, +) { + inline val webSettings get() = state.webSettings + inline val rendering: CefRendering get() = + if (webSettings.desktopWebSettings.offScreenRendering) { + CefRendering.OFFSCREEN + } else { + CefRendering.DEFAULT + } + inline val transparent: Boolean get() = webSettings.desktopWebSettings.transparent + val requestContext: CefRequestContext get() = createModifiedRequestContext(webSettings) +} + +/** Default WebView factory for Desktop. */ +actual fun defaultWebViewFactory(param: WebViewFactoryParam): NativeWebView = + when (val content = param.state.content) { + is WebContent.Url -> + param.client.createBrowser(content.url, + param.rendering, param.transparent, param.requestContext) + is WebContent.Data -> + param.client.createBrowserWithHtml(content.data, + content.baseUrl ?: KCEFBrowser.BLANK_URI, param.rendering, param.transparent) + is WebContent.File -> + param.client.createBrowserWithHtml(param.fileContent, + KCEFBrowser.BLANK_URI, param.rendering, param.transparent) + else -> + param.client.createBrowser(KCEFBrowser.BLANK_URI, + param.rendering, param.transparent, param.requestContext) + } + /** * Desktop WebView implementation. */ @@ -45,6 +83,7 @@ fun DesktopWebView( webViewJsBridge: WebViewJsBridge?, onCreated: (NativeWebView) -> Unit, onDispose: (NativeWebView) -> Unit, + factory: (WebViewFactoryParam) -> NativeWebView, ) { val currentOnDispose by rememberUpdatedState(onDispose) val client = @@ -70,64 +109,12 @@ fun DesktopWebView( } } - val browser: KCEFBrowser? = - remember( - client, - state.webSettings.desktopWebSettings.offScreenRendering, - state.webSettings.desktopWebSettings.transparent, - state.webSettings, - fileContent, - ) { - val rendering = - if (state.webSettings.desktopWebSettings.offScreenRendering) { - CefRendering.OFFSCREEN - } else { - CefRendering.DEFAULT - } - - when (val current = state.content) { - is WebContent.Url -> - client?.createBrowser( - current.url, - rendering, - state.webSettings.desktopWebSettings.transparent, - createModifiedRequestContext(state.webSettings), - ) - - is WebContent.Data -> - client?.createBrowserWithHtml( - current.data, - current.baseUrl ?: KCEFBrowser.BLANK_URI, - rendering, - state.webSettings.desktopWebSettings.transparent, - ) - - is WebContent.File -> - client?.createBrowserWithHtml( - fileContent, - KCEFBrowser.BLANK_URI, - rendering, - state.webSettings.desktopWebSettings.transparent, - ) - - else -> { - client?.createBrowser( - KCEFBrowser.BLANK_URI, - rendering, - state.webSettings.desktopWebSettings.transparent, - createModifiedRequestContext(state.webSettings), - ) - } - } - } - val desktopWebView = - remember(browser) { - if (browser != null) { - DesktopWebView(browser, scope, webViewJsBridge) - } else { - null - } - } + val browser: KCEFBrowser? = remember(client, state.webSettings, fileContent) { + client?.let { factory(WebViewFactoryParam(state, client, fileContent)) } + } + val desktopWebView: DesktopWebView? = remember(browser) { + browser?.let { DesktopWebView(browser, scope, webViewJsBridge) } + } browser?.let { SwingPanel( diff --git a/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt b/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt index cf4719b6..4e9a05ce 100644 --- a/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt +++ b/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt @@ -27,6 +27,7 @@ actual fun ActualWebView( webViewJsBridge: WebViewJsBridge?, onCreated: (NativeWebView) -> Unit, onDispose: (NativeWebView) -> Unit, + factory: (WebViewFactoryParam) -> NativeWebView, ) { IOSWebView( state = state, @@ -36,9 +37,19 @@ actual fun ActualWebView( webViewJsBridge = webViewJsBridge, onCreated = onCreated, onDispose = onDispose, + factory = factory, ) } +/** iOS WebView factory parameters: configuration created from WebSettings. */ +actual data class WebViewFactoryParam(val config: WKWebViewConfiguration) + +/** Default WebView factory for iOS. */ +@OptIn(ExperimentalForeignApi::class) +actual fun defaultWebViewFactory(param: WebViewFactoryParam) = WKWebView( + frame = CGRectZero.readValue(), configuration = param.config, +) + /** * iOS WebView implementation. */ @@ -52,6 +63,7 @@ fun IOSWebView( webViewJsBridge: WebViewJsBridge?, onCreated: (NativeWebView) -> Unit, onDispose: (NativeWebView) -> Unit, + factory: (WebViewFactoryParam) -> NativeWebView, ) { val observer = remember { @@ -82,10 +94,7 @@ fun IOSWebView( forKey = "allowUniversalAccessFromFileURLs", ) } - WKWebView( - frame = CGRectZero.readValue(), - configuration = config, - )).apply { + factory(WebViewFactoryParam(config)).apply { onCreated(this) state.viewState?.let { this.interactionState = it From e2105055936258f1c9ad6d4f391faaf71e26c24d Mon Sep 17 00:00:00 2001 From: Jeffrey Bush Date: Mon, 24 Jun 2024 11:44:48 -0400 Subject: [PATCH 4/7] Updating readme. --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7cef2462..f5b79ad7 100644 --- a/README.md +++ b/README.md @@ -555,6 +555,7 @@ The complete API of this library is as follows: * navigation from outside the composable. * @param onCreated Called when the WebView is first created. * @param onDispose Called when the WebView is destroyed. + * @param factory A function that creates a platform-specific WebView object. * @sample sample.BasicWebViewSample */ @Composable @@ -563,8 +564,10 @@ fun WebView( modifier: Modifier = Modifier, captureBackPresses: Boolean = true, navigator: WebViewNavigator = rememberWebViewNavigator(), - onCreated: () -> Unit = {}, - onDispose: () -> Unit = {}, + webViewJsBridge: WebViewJsBridge? = null, + onCreated: (NativeWebView) -> Unit = {}, + onDispose: (NativeWebView) -> Unit = {}, + factory: ((WebViewFactoryParam) -> NativeWebView)? = null, ) ``` From d0408c2ce718dd82e68459ebb21c34b5b3b27330 Mon Sep 17 00:00:00 2001 From: Jeffrey Bush Date: Mon, 24 Jun 2024 23:19:44 -0400 Subject: [PATCH 5/7] Fixing style issues --- .../webview/web/WebView.desktop.kt | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt index 4038504d..dfc982f4 100644 --- a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt +++ b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt @@ -58,17 +58,33 @@ actual class WebViewFactoryParam( actual fun defaultWebViewFactory(param: WebViewFactoryParam): NativeWebView = when (val content = param.state.content) { is WebContent.Url -> - param.client.createBrowser(content.url, - param.rendering, param.transparent, param.requestContext) + param.client.createBrowser( + content.url, + param.rendering, + param.transparent, + param.requestContext, + ) is WebContent.Data -> - param.client.createBrowserWithHtml(content.data, - content.baseUrl ?: KCEFBrowser.BLANK_URI, param.rendering, param.transparent) + param.client.createBrowserWithHtml( + content.data, + content.baseUrl ?: KCEFBrowser.BLANK_URI, + param.rendering, + param.transparent, + ) is WebContent.File -> - param.client.createBrowserWithHtml(param.fileContent, - KCEFBrowser.BLANK_URI, param.rendering, param.transparent) + param.client.createBrowserWithHtml( + param.fileContent, + KCEFBrowser.BLANK_URI, + param.rendering, + param.transparent, + ) else -> - param.client.createBrowser(KCEFBrowser.BLANK_URI, - param.rendering, param.transparent, param.requestContext) + param.client.createBrowser( + KCEFBrowser.BLANK_URI, + param.rendering, + param.transparent, + param.requestContext, + ) } /** @@ -109,12 +125,14 @@ fun DesktopWebView( } } - val browser: KCEFBrowser? = remember(client, state.webSettings, fileContent) { - client?.let { factory(WebViewFactoryParam(state, client, fileContent)) } - } - val desktopWebView: DesktopWebView? = remember(browser) { - browser?.let { DesktopWebView(browser, scope, webViewJsBridge) } - } + val browser: KCEFBrowser? = + remember(client, state.webSettings, fileContent) { + client?.let { factory(WebViewFactoryParam(state, client, fileContent)) } + } + val desktopWebView: DesktopWebView? = + remember(browser) { + browser?.let { DesktopWebView(browser, scope, webViewJsBridge) } + } browser?.let { SwingPanel( From e686b546d2dbf0c7e58052caf6ea97f92e40af24 Mon Sep 17 00:00:00 2001 From: Jeffrey Bush Date: Wed, 26 Jun 2024 21:50:11 -0400 Subject: [PATCH 6/7] Fixing style issues --- .../kotlin/com/multiplatform/webview/web/WebView.ios.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt b/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt index 4e9a05ce..f47ceb65 100644 --- a/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt +++ b/webview/src/iosMain/kotlin/com/multiplatform/webview/web/WebView.ios.kt @@ -46,9 +46,7 @@ actual data class WebViewFactoryParam(val config: WKWebViewConfiguration) /** Default WebView factory for iOS. */ @OptIn(ExperimentalForeignApi::class) -actual fun defaultWebViewFactory(param: WebViewFactoryParam) = WKWebView( - frame = CGRectZero.readValue(), configuration = param.config, -) +actual fun defaultWebViewFactory(param: WebViewFactoryParam) = WKWebView(frame = CGRectZero.readValue(), configuration = param.config) /** * iOS WebView implementation. From 07502bebff16098ba004c9330d3ef9e320fad2b4 Mon Sep 17 00:00:00 2001 From: Jeffrey Bush Date: Fri, 28 Jun 2024 13:38:09 -0400 Subject: [PATCH 7/7] Adding backwards compatible wrapper for WebView. --- .../com/multiplatform/webview/web/WebView.kt | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt index 0c8eefed..aa7cfade 100644 --- a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt +++ b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt @@ -16,8 +16,43 @@ import kotlinx.coroutines.flow.merge */ /** + * Provides a basic WebView composable. + * This version of the function is provided for backwards compatibility by using the older + * onCreated and onDispose callbacks and is missing the factory parameter. * - * A wrapper around the Android View WebView to provide a basic WebView composable. + * @param state The webview state holder where the Uri to load is defined. + * @param modifier A compose modifier + * @param captureBackPresses Set to true to have this Composable capture back presses and navigate + * the WebView back. + * @param navigator An optional navigator object that can be used to control the WebView's + * navigation from outside the composable. + * @param onCreated Called when the WebView is first created. + * @param onDispose Called when the WebView is destroyed. + * @sample sample.BasicWebViewSample + */ +@Composable +fun WebView( + state: WebViewState, + modifier: Modifier = Modifier, + captureBackPresses: Boolean = true, + navigator: WebViewNavigator = rememberWebViewNavigator(), + webViewJsBridge: WebViewJsBridge? = null, + onCreated: () -> Unit = {}, + onDispose: () -> Unit = {}, +) { + WebView( + state = state, + modifier = modifier, + captureBackPresses = captureBackPresses, + navigator = navigator, + webViewJsBridge = webViewJsBridge, + onCreated = { _ -> onCreated() }, + onDispose = { _ -> onDispose() }, + ) +} + +/** + * Provides a basic WebView composable. * * @param state The webview state holder where the Uri to load is defined. * @param modifier A compose modifier