Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ kotlin.mpp.androidSourceSetLayoutVersion=2
android.useAndroidX=true
android.compileSdk=36
android.targetSdk=36
android.minSdk=21
android.minSdk=24
Comment thread
cyborch marked this conversation as resolved.
#Versions
GROUP=io.github.kevinnzou
POM_ARTIFACT_ID=compose-webview-multiplatform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ expect class WebViewFactoryParam
/**
* Platform specific parameters given to the WebView composable function:
* - On Android, this contains an optional `AccompanistWebViewClient` and `AccompanistWebChromeClient`
* - On iOS, this is currently unused
* - On iOS, this contains an optional `WKNavigationDelegateProtocol`
* - On Desktop, this is currently unused
Comment thread
cyborch marked this conversation as resolved.
*/
expect class PlatformWebViewParams
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import platform.darwin.NSObject
/**
* A JS message handler for WKWebView.
*/
class WKJsMessageHandler(
final class WKJsMessageHandler(
private val webViewJsBridge: WebViewJsBridge,
) : NSObject(),
WKScriptMessageHandlerProtocol {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import kotlinx.cinterop.ObjCSignatureOverride
import platform.CoreGraphics.CGPointMake
import platform.Foundation.HTTPMethod
import platform.Foundation.NSError
import platform.Foundation.NSSelectorFromString
import platform.Foundation.allHTTPHeaderFields
import platform.WebKit.WKNavigation
import platform.WebKit.WKNavigationAction
Expand All @@ -26,16 +27,18 @@ import platform.darwin.NSObject
* Navigation delegate for the WKWebView
*/
@Suppress("CONFLICTING_OVERLOADS")
class WKNavigationDelegate(
final class WKNavigationDelegate(
private val state: WebViewState,
private val navigator: WebViewNavigator,
private val params: PlatformWebViewParams?,
) : NSObject(),
WKNavigationDelegateProtocol {
private var isRedirect = false

/**
* Called when the web view begins to receive web content.
*/
@OptIn(ExperimentalForeignApi::class)
@ObjCSignatureOverride
override fun webView(
webView: WKWebView,
Expand All @@ -47,11 +50,44 @@ class WKNavigationDelegate(
KLogger.info {
"didStartProvisionalNavigation"
}
val delegate = params?.delegate
if (delegate != null &&
(delegate as NSObject).respondsToSelector(
NSSelectorFromString("webView:didStartProvisionalNavigation:"),
)
) {
delegate.webView(webView, didStartProvisionalNavigation = didStartProvisionalNavigation)
}
}

/**
* Called when the web view receives a server redirect.
*/
@OptIn(ExperimentalForeignApi::class)
@ObjCSignatureOverride
override fun webView(
webView: WKWebView,
didReceiveServerRedirectForProvisionalNavigation: WKNavigation?,
) {
isRedirect = true
KLogger.info { "didReceiveServerRedirectForProvisionalNavigation" }
val delegate = params?.delegate
if (delegate != null &&
(delegate as NSObject).respondsToSelector(
NSSelectorFromString("webView:didReceiveServerRedirectForProvisionalNavigation:"),
)
) {
delegate.webView(
webView,
didReceiveServerRedirectForProvisionalNavigation = didReceiveServerRedirectForProvisionalNavigation,
)
}
}

/**
* Called when the web view receives a server redirect.
*/
@OptIn(ExperimentalForeignApi::class)
@ObjCSignatureOverride
override fun webView(
webView: WKWebView,
Expand All @@ -64,6 +100,14 @@ class WKNavigationDelegate(
"var meta = document.createElement('meta');meta.setAttribute('name', 'viewport');meta.setAttribute('content', 'width=device-width, initial-scale=${state.webSettings.zoomLevel}, maximum-scale=10.0, minimum-scale=0.1,user-scalable=$supportZoom');document.getElementsByTagName('head')[0].appendChild(meta);"
webView.evaluateJavaScript(script) { _, _ -> }
KLogger.info { "didCommitNavigation" }
val delegate = params?.delegate
if (delegate != null &&
(delegate as NSObject).respondsToSelector(
NSSelectorFromString("webView:didCommitNavigation:"),
)
) {
delegate.webView(webView, didCommitNavigation = didCommitNavigation)
}
}

/**
Expand Down Expand Up @@ -93,15 +137,60 @@ class WKNavigationDelegate(
}
}
KLogger.info { "didFinishNavigation ${state.lastLoadedUrl}" }
val delegate = params?.delegate
if (delegate != null &&
(delegate as NSObject).respondsToSelector(
NSSelectorFromString("webView:didFinishNavigation:"),
)
) {
delegate.webView(webView, didFinishNavigation = didFinishNavigation)
}
}

/**
* Called when the web view fails to load content.
*/
@OptIn(ExperimentalForeignApi::class)
@ObjCSignatureOverride
override fun webView(
webView: WKWebView,
didFailProvisionalNavigation: WKNavigation?,
withError: NSError,
) {
KLogger.e {
"WebView Loading Failed with error: ${withError.localizedDescription}"
}
state.errorsForCurrentRequest.add(
WebViewError(
code = withError.code.toInt(),
description = withError.localizedDescription,
// on iOS all errors are from the main frame
isFromMainFrame = true,
),
)
KLogger.e {
"didFailProvisionalNavigation"
}
val delegate = params?.delegate
if (delegate != null &&
(delegate as NSObject).respondsToSelector(
NSSelectorFromString("webView:didFailProvisionalNavigation:withError:"),
)
) {
delegate.webView(
webView,
didFailProvisionalNavigation = didFailProvisionalNavigation,
withError = withError,
)
}
}

@OptIn(ExperimentalForeignApi::class)
@ObjCSignatureOverride
override fun webView(
webView: WKWebView,
didFailNavigation: WKNavigation?,
withError: NSError,
) {
KLogger.e {
"WebView Loading Failed with error: ${withError.localizedDescription}"
Expand All @@ -117,8 +206,36 @@ class WKNavigationDelegate(
KLogger.e {
"didFailNavigation"
}
val delegate = params?.delegate
if (delegate != null &&
(delegate as NSObject).respondsToSelector(
NSSelectorFromString("webView:didFailNavigation:withError:"),
)
) {
delegate.webView(
webView,
didFailNavigation = didFailNavigation,
withError = withError,
)
}
}

@OptIn(ExperimentalForeignApi::class)
override fun webViewWebContentProcessDidTerminate(webView: WKWebView) {
KLogger.e {
"webViewWebContentProcessDidTerminate"
}
val delegate = params?.delegate
if (delegate != null &&
(delegate as NSObject).respondsToSelector(
NSSelectorFromString("webViewWebContentProcessDidTerminate:"),
)
) {
delegate.webViewWebContentProcessDidTerminate(webView)
}
}

@OptIn(ExperimentalForeignApi::class)
override fun webView(
webView: WKWebView,
decidePolicyForNavigationAction: WKNavigationAction,
Expand All @@ -128,6 +245,25 @@ class WKNavigationDelegate(
KLogger.info {
"Outer decidePolicyForNavigationAction: $url $isRedirect $decidePolicyForNavigationAction"
}

val delegate = params?.delegate

fun callDelegateOrAllow() {
if (delegate != null &&
(delegate as NSObject).respondsToSelector(
NSSelectorFromString("webView:decidePolicyForNavigationAction:decisionHandler:"),
)
) {
delegate.webView(
webView,
decidePolicyForNavigationAction = decidePolicyForNavigationAction,
decisionHandler = decisionHandler,
)
} else {
decisionHandler(WKNavigationActionPolicy.WKNavigationActionPolicyAllow)
}
}

if (
url != null &&
!isRedirect &&
Expand Down Expand Up @@ -158,7 +294,7 @@ class WKNavigationDelegate(
)
when (interceptResult) {
is WebRequestInterceptResult.Allow -> {
decisionHandler(WKNavigationActionPolicy.WKNavigationActionPolicyAllow)
callDelegateOrAllow()
}

is WebRequestInterceptResult.Reject -> {
Expand All @@ -177,7 +313,7 @@ class WKNavigationDelegate(
}
} else {
isRedirect = false
decisionHandler(WKNavigationActionPolicy.WKNavigationActionPolicyAllow)
callDelegateOrAllow()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import platform.darwin.NSObject
* Observer for the WKWebView's loading state
*/
@ExperimentalForeignApi
class WKWebViewObserver(
final class WKWebViewObserver(
private val state: WebViewState,
private val navigator: WebViewNavigator,
) : NSObject(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import platform.Foundation.NSProcessInfo
import platform.Foundation.setValue
import platform.WebKit.WKAudiovisualMediaTypeAll
import platform.WebKit.WKAudiovisualMediaTypeNone
import platform.WebKit.WKNavigationDelegateProtocol
import platform.WebKit.WKWebView
import platform.WebKit.WKWebViewConfiguration
import platform.WebKit.javaScriptEnabled
Expand Down Expand Up @@ -49,6 +50,7 @@ actual fun ActualWebView(
onCreated = onCreated,
onDispose = onDispose,
factory = factory,
platformWebViewParams = platformWebViewParams,
)
}

Expand All @@ -57,7 +59,9 @@ actual data class WebViewFactoryParam(
val config: WKWebViewConfiguration,
)

actual class PlatformWebViewParams
actual data class PlatformWebViewParams(
val delegate: WKNavigationDelegateProtocol? = null,
)

/** Default WebView factory for iOS. */
@OptIn(ExperimentalForeignApi::class)
Expand All @@ -81,6 +85,7 @@ fun IOSWebView(
onCreated: (NativeWebView) -> Unit,
onDispose: (NativeWebView) -> Unit,
factory: (WebViewFactoryParam) -> NativeWebView,
platformWebViewParams: PlatformWebViewParams?,
) {
val observer =
remember {
Expand All @@ -89,7 +94,7 @@ fun IOSWebView(
navigator = navigator,
)
}
val navigationDelegate = remember { WKNavigationDelegate(state, navigator) }
val navigationDelegate = remember { WKNavigationDelegate(state, navigator, platformWebViewParams) }
val scope = rememberCoroutineScope()

UIKitView(
Expand Down
Loading