Skip to content

Commit c680fe7

Browse files
authored
Merge pull request #29 from zaroxh/main
Prevent unnecessary desktop WebView recreation during recomposition
2 parents ebfdfe4 + b3d9854 commit c680fe7

File tree

7 files changed

+68
-73
lines changed

7 files changed

+68
-73
lines changed

webview-compose/src/androidMain/kotlin/io/github/kdroidfilter/webview/web/AndroidWebView.kt

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@ import io.github.kdroidfilter.webview.util.KLogger
88
import kotlinx.coroutines.CoroutineScope
99

1010
internal class AndroidWebView(
11-
override val webView: WebView,
11+
override val nativeWebView: WebView,
1212
override val scope: CoroutineScope,
1313
override val webViewJsBridge: WebViewJsBridge?,
1414
) : IWebView {
1515
init {
1616
initWebView()
1717
}
1818

19-
override fun canGoBack(): Boolean = webView.canGoBack()
19+
override fun canGoBack(): Boolean = nativeWebView.canGoBack()
2020

21-
override fun canGoForward(): Boolean = webView.canGoForward()
21+
override fun canGoForward(): Boolean = nativeWebView.canGoForward()
2222

2323
override fun loadUrl(url: String, additionalHttpHeaders: Map<String, String>) {
24-
webView.loadUrl(url, additionalHttpHeaders)
24+
nativeWebView.loadUrl(url, additionalHttpHeaders)
2525
}
2626

2727
override suspend fun loadHtml(
@@ -32,7 +32,7 @@ internal class AndroidWebView(
3232
historyUrl: String?,
3333
) {
3434
if (html == null) return
35-
webView.loadDataWithBaseURL(baseUrl, html, mimeType, encoding, historyUrl)
35+
nativeWebView.loadDataWithBaseURL(baseUrl, html, mimeType, encoding, historyUrl)
3636
}
3737

3838
override suspend fun loadHtmlFile(fileName: String, readType: WebViewFileReadType) {
@@ -52,34 +52,35 @@ internal class AndroidWebView(
5252
val selected =
5353
candidates.firstOrNull { path ->
5454
try {
55-
webView.context.assets.open(path).close()
55+
nativeWebView.context.assets.open(path).close()
5656
true
5757
} catch (_: Exception) {
5858
false
5959
}
6060
} ?: candidates.first()
6161
val url = "file:///android_asset/$selected"
62-
webView.loadUrl(url)
62+
nativeWebView.loadUrl(url)
6363
KLogger.d(tag = "AndroidWebView") { "loadUrl $url (candidates: ${candidates.joinToString()})" }
6464
}
65-
WebViewFileReadType.COMPOSE_RESOURCE_FILES -> webView.loadUrl(fileName)
65+
66+
WebViewFileReadType.COMPOSE_RESOURCE_FILES -> nativeWebView.loadUrl(fileName)
6667
}
6768
}
6869

69-
override fun goBack() = webView.goBack()
70+
override fun goBack() = nativeWebView.goBack()
7071

71-
override fun goForward() = webView.goForward()
72+
override fun goForward() = nativeWebView.goForward()
7273

73-
override fun reload() = webView.reload()
74+
override fun reload() = nativeWebView.reload()
7475

75-
override fun stopLoading() = webView.stopLoading()
76+
override fun stopLoading() = nativeWebView.stopLoading()
7677

7778
override fun evaluateJavaScript(script: String, callback: ((String) -> Unit)?) {
7879
val androidScript = "javascript:$script"
7980
KLogger.d {
8081
"evaluateJavaScript: $androidScript"
8182
}
82-
webView.evaluateJavascript(androidScript, callback)
83+
nativeWebView.evaluateJavascript(androidScript, callback)
8384
}
8485

8586
override fun injectJsBridge() {
@@ -97,7 +98,7 @@ internal class AndroidWebView(
9798
}
9899

99100
override fun initJsBridge(webViewJsBridge: WebViewJsBridge) {
100-
webView.addJavascriptInterface(this, "androidJsBridge")
101+
nativeWebView.addJavascriptInterface(this, "androidJsBridge")
101102
}
102103

103104
@JavascriptInterface

webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/IWebView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ expect class NativeWebView
99
* Platform WebView abstraction.
1010
*/
1111
interface IWebView {
12-
val webView: NativeWebView
12+
val nativeWebView: NativeWebView
1313

1414
val scope: CoroutineScope
1515

webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/WebViewState.kt

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
package io.github.kdroidfilter.webview.web
22

3-
import androidx.compose.runtime.Composable
4-
import androidx.compose.runtime.Stable
5-
import androidx.compose.runtime.getValue
6-
import androidx.compose.runtime.mutableStateListOf
7-
import androidx.compose.runtime.mutableStateOf
8-
import androidx.compose.runtime.remember
9-
import androidx.compose.runtime.setValue
3+
import androidx.compose.runtime.*
104
import androidx.compose.runtime.snapshots.SnapshotStateList
115
import io.github.kdroidfilter.webview.cookie.CookieManager
126
import io.github.kdroidfilter.webview.cookie.WebViewCookieManager
@@ -34,10 +28,8 @@ class WebViewState(
3428

3529
val webSettings: WebSettings by mutableStateOf(WebSettings())
3630

37-
internal var webView by mutableStateOf<IWebView?>(null)
38-
39-
val nativeWebView: NativeWebView
40-
get() = webView?.webView ?: error("WebView is not initialized")
31+
var webView: IWebView? by mutableStateOf(null)
32+
internal set
4133

4234
val cookieManager: CookieManager by mutableStateOf(WebViewCookieManager())
4335
}

webview-compose/src/iosMain/kotlin/io/github/kdroidfilter/webview/web/IOSWebView.kt

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,23 @@ import io.github.kdroidfilter.webview.jsbridge.WebViewJsBridge
55
import io.github.kdroidfilter.webview.util.KLogger
66
import kotlinx.cinterop.ExperimentalForeignApi
77
import kotlinx.coroutines.CoroutineScope
8-
import platform.Foundation.NSArray
9-
import platform.Foundation.NSBundle
10-
import platform.Foundation.NSDocumentDirectory
11-
import platform.Foundation.NSFileManager
12-
import platform.Foundation.NSMutableURLRequest
13-
import platform.Foundation.NSSearchPathForDirectoriesInDomains
14-
import platform.Foundation.NSString
15-
import platform.Foundation.NSURL
16-
import platform.Foundation.NSUserDomainMask
17-
import platform.Foundation.setValue
18-
import platform.Foundation.stringByDeletingLastPathComponent
8+
import platform.Foundation.*
199
import platform.WebKit.WKWebView
2010

2111
internal const val IOS_JS_BRIDGE_HANDLER_NAME: String = "iosJsBridge"
2212

2313
internal class IOSWebView(
24-
override val webView: WKWebView,
14+
override val nativeWebView: WKWebView,
2515
override val scope: CoroutineScope,
2616
override val webViewJsBridge: WebViewJsBridge?,
2717
) : IWebView {
2818
init {
2919
initWebView()
3020
}
3121

32-
override fun canGoBack(): Boolean = webView.canGoBack
22+
override fun canGoBack(): Boolean = nativeWebView.canGoBack
3323

34-
override fun canGoForward(): Boolean = webView.canGoForward
24+
override fun canGoForward(): Boolean = nativeWebView.canGoForward
3525

3626
override fun loadUrl(url: String, additionalHttpHeaders: Map<String, String>) {
3727
if (url.startsWith("file://")) {
@@ -52,7 +42,7 @@ internal class IOSWebView(
5242
}
5343

5444
if (readAccessURL != null) {
55-
webView.loadFileURL(fileURL, readAccessURL)
45+
nativeWebView.loadFileURL(fileURL, readAccessURL)
5646
return
5747
}
5848
}
@@ -66,7 +56,7 @@ internal class IOSWebView(
6656
forHTTPHeaderField = key,
6757
)
6858
}
69-
webView.loadRequest(request = request)
59+
nativeWebView.loadRequest(request = request)
7060
}
7161

7262
override suspend fun loadHtml(
@@ -77,7 +67,7 @@ internal class IOSWebView(
7767
historyUrl: String?,
7868
) {
7969
if (html == null) return
80-
webView.loadHTMLString(
70+
nativeWebView.loadHTMLString(
8171
string = html,
8272
baseURL = baseUrl?.let { NSURL.URLWithString(it) },
8373
)
@@ -154,7 +144,7 @@ internal class IOSWebView(
154144
return
155145
}
156146

157-
webView.loadFileURL(fileURL, readAccessURL!!)
147+
nativeWebView.loadFileURL(fileURL, readAccessURL!!)
158148
} catch (e: Exception) {
159149
KLogger.e(e, tag = "IOSWebView") { "Error loading HTML file: $fileName (readType: $readType)" }
160150
val errorHtml =
@@ -172,24 +162,24 @@ internal class IOSWebView(
172162
}
173163

174164
override fun goBack() {
175-
webView.goBack()
165+
nativeWebView.goBack()
176166
}
177167

178168
override fun goForward() {
179-
webView.goForward()
169+
nativeWebView.goForward()
180170
}
181171

182172
override fun reload() {
183-
webView.reload()
173+
nativeWebView.reload()
184174
}
185175

186176
override fun stopLoading() {
187-
webView.stopLoading()
177+
nativeWebView.stopLoading()
188178
}
189179

190180
@OptIn(ExperimentalForeignApi::class)
191181
override fun evaluateJavaScript(script: String, callback: ((String) -> Unit)?) {
192-
webView.evaluateJavaScript(script) { result, error ->
182+
nativeWebView.evaluateJavaScript(script) { result, error ->
193183
if (callback == null) return@evaluateJavaScript
194184
if (error != null) {
195185
KLogger.e { "evaluateJavaScript error: $error" }
@@ -217,6 +207,9 @@ internal class IOSWebView(
217207

218208
override fun initJsBridge(webViewJsBridge: WebViewJsBridge) {
219209
val jsMessageHandler = WKJsMessageHandler(webViewJsBridge)
220-
webView.configuration.userContentController.addScriptMessageHandler(jsMessageHandler, IOS_JS_BRIDGE_HANDLER_NAME)
210+
nativeWebView.configuration.userContentController.addScriptMessageHandler(
211+
scriptMessageHandler = jsMessageHandler,
212+
name = IOS_JS_BRIDGE_HANDLER_NAME
213+
)
221214
}
222215
}

webview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/DesktopWebView.kt

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@ import kotlinx.coroutines.CoroutineScope
66
import java.net.URL
77

88
internal class DesktopWebView(
9-
override val webView: NativeWebView,
9+
override val nativeWebView: NativeWebView,
1010
override val scope: CoroutineScope,
1111
override val webViewJsBridge: WebViewJsBridge?,
1212
) : IWebView {
1313
init {
1414
initWebView()
1515
}
1616

17-
override fun canGoBack(): Boolean = webView.canGoBack()
17+
override fun canGoBack(): Boolean = nativeWebView.canGoBack()
1818

19-
override fun canGoForward(): Boolean = webView.canGoForward()
19+
override fun canGoForward(): Boolean = nativeWebView.canGoForward()
2020

2121
override fun loadUrl(
2222
url: String,
2323
additionalHttpHeaders: Map<String, String>,
2424
) {
25-
webView.loadUrl(url, additionalHttpHeaders)
25+
nativeWebView.loadUrl(url, additionalHttpHeaders)
2626
}
2727

2828
override suspend fun loadHtml(
@@ -33,7 +33,7 @@ internal class DesktopWebView(
3333
historyUrl: String?,
3434
) {
3535
if (html == null) return
36-
webView.loadHtml(html)
36+
nativeWebView.loadHtml(html)
3737
}
3838

3939
override suspend fun loadHtmlFile(
@@ -58,7 +58,8 @@ internal class DesktopWebView(
5858
candidates.add("compose-resources/assets/$normalized")
5959
candidates.add("composeResources/files/$normalized")
6060
candidates.add("composeResources/assets/$normalized")
61-
val loaders = listOfNotNull(Thread.currentThread().contextClassLoader, this::class.java.classLoader)
61+
val loaders =
62+
listOfNotNull(Thread.currentThread().contextClassLoader, this::class.java.classLoader)
6263
candidates.asSequence()
6364
.mapNotNull { path ->
6465
loaders.asSequence()
@@ -69,6 +70,7 @@ internal class DesktopWebView(
6970
.firstOrNull()
7071
?: error("Resource not found: ${candidates.joinToString()}")
7172
}
73+
7274
WebViewFileReadType.COMPOSE_RESOURCE_FILES ->
7375
URL(fileName).openStream().use { it.readBytes().toString(Charsets.UTF_8) }
7476
}
@@ -87,23 +89,23 @@ internal class DesktopWebView(
8789
""".trimIndent()
8890
KLogger.e(e, tag = "DesktopWebView") { "loadHtmlFile failed" }
8991
errorHtml
90-
}
91-
webView.loadHtml(html)
92+
}
93+
nativeWebView.loadHtml(html)
9294
}
9395

94-
override fun goBack() = webView.goBack()
96+
override fun goBack() = nativeWebView.goBack()
9597

96-
override fun goForward() = webView.goForward()
98+
override fun goForward() = nativeWebView.goForward()
9799

98-
override fun reload() = webView.reload()
100+
override fun reload() = nativeWebView.reload()
99101

100-
override fun stopLoading() = webView.stopLoading()
102+
override fun stopLoading() = nativeWebView.stopLoading()
101103

102104
override fun evaluateJavaScript(script: String, callback: ((String) -> Unit)?) {
103105
KLogger.d {
104106
"evaluateJavaScript: $script"
105107
}
106-
webView.evaluateJavaScript(script) { result ->
108+
nativeWebView.evaluateJavaScript(script) { result ->
107109
callback?.invoke(result)
108110
}
109111
}

webview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/WebViewDesktop.kt

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,17 @@ actual fun ActualWebView(
8484
}
8585

8686
key(effectiveSettingsKey) {
87-
val nativeWebView = remember(state, factory) { factory(WebViewFactoryParam(state)) }
88-
89-
val desktopWebView =
90-
remember(nativeWebView, scope, webViewJsBridge) {
91-
DesktopWebView(
92-
webView = nativeWebView,
93-
scope = scope,
94-
webViewJsBridge = webViewJsBridge,
95-
)
96-
}
87+
val nativeWebView = remember(state, factory) {
88+
state.webView?.nativeWebView ?: factory(WebViewFactoryParam(state))
89+
}
90+
91+
val desktopWebView = remember(nativeWebView, scope, webViewJsBridge) {
92+
DesktopWebView(
93+
nativeWebView = nativeWebView,
94+
scope = scope,
95+
webViewJsBridge = webViewJsBridge,
96+
)
97+
}
9798

9899
LaunchedEffect(desktopWebView) {
99100
state.webView = desktopWebView

wrywebview/src/main/kotlin/io/github/kdroidfilter/webview/wry/WryWebViewPanel.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,11 @@ class WryWebViewPanel(
388388
val dataDir = dataDirectory
389389
val initialUrl = pendingUrl
390390
val handleSnapshot = parentHandle
391+
392+
if (!host.isDisplayable) {
393+
return false
394+
}
395+
391396
if (!IS_MAC) {
392397
return try {
393398
webviewId = NativeBindings.createWebview(
@@ -439,6 +444,7 @@ class WryWebViewPanel(
439444
true
440445
}
441446
}
447+
442448
createInFlight = true
443449
stopCreateTimer()
444450
thread(name = "wry-webview-create", isDaemon = true) {

0 commit comments

Comments
 (0)