Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@ 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 {
init {
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<String, String>) {
webView.loadUrl(url, additionalHttpHeaders)
nativeWebView.loadUrl(url, additionalHttpHeaders)
}

override suspend fun loadHtml(
Expand All @@ -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) {
Expand All @@ -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() {
Expand All @@ -97,7 +98,7 @@ internal class AndroidWebView(
}

override fun initJsBridge(webViewJsBridge: WebViewJsBridge) {
webView.addJavascriptInterface(this, "androidJsBridge")
nativeWebView.addJavascriptInterface(this, "androidJsBridge")
}

@JavascriptInterface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ expect class NativeWebView
* Platform WebView abstraction.
*/
interface IWebView {
val webView: NativeWebView
val nativeWebView: NativeWebView

val scope: CoroutineScope

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -34,10 +28,8 @@ class WebViewState(

val webSettings: WebSettings by mutableStateOf(WebSettings())

internal var webView by mutableStateOf<IWebView?>(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())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,23 @@ 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 {
init {
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<String, String>) {
if (url.startsWith("file://")) {
Expand All @@ -52,7 +42,7 @@ internal class IOSWebView(
}

if (readAccessURL != null) {
webView.loadFileURL(fileURL, readAccessURL)
nativeWebView.loadFileURL(fileURL, readAccessURL)
return
}
}
Expand All @@ -66,7 +56,7 @@ internal class IOSWebView(
forHTTPHeaderField = key,
)
}
webView.loadRequest(request = request)
nativeWebView.loadRequest(request = request)
}

override suspend fun loadHtml(
Expand All @@ -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) },
)
Expand Down Expand Up @@ -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 =
Expand All @@ -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" }
Expand Down Expand Up @@ -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
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ 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 {
init {
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<String, String>,
) {
webView.loadUrl(url, additionalHttpHeaders)
nativeWebView.loadUrl(url, additionalHttpHeaders)
}

override suspend fun loadHtml(
Expand All @@ -33,7 +33,7 @@ internal class DesktopWebView(
historyUrl: String?,
) {
if (html == null) return
webView.loadHtml(html)
nativeWebView.loadHtml(html)
}

override suspend fun loadHtmlFile(
Expand All @@ -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()
Expand All @@ -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) }
}
Expand All @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -439,6 +444,7 @@ class WryWebViewPanel(
true
}
}

createInFlight = true
stopCreateTimer()
thread(name = "wry-webview-create", isDaemon = true) {
Expand Down