Skip to content

Commit e847779

Browse files
committed
WasmJs implementation and some code refactoring
1 parent 3cebc43 commit e847779

25 files changed

Lines changed: 1834 additions & 220 deletions

File tree

demo-shared/build.gradle.kts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
@file:OptIn(ExperimentalWasmDsl::class)
2+
3+
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
4+
15
plugins {
26
alias(libs.plugins.androidLibrary)
37
alias(libs.plugins.kotlinMultiplatform)
@@ -10,8 +14,14 @@ kotlin {
1014

1115
androidTarget()
1216
jvm()
17+
wasmJs {
18+
browser()
19+
}
1320

14-
val isMacHost = System.getProperty("os.name")?.contains("Mac", ignoreCase = true) == true
21+
val isMacHost = System.getProperty("os.name")?.contains(
22+
other = "Mac",
23+
ignoreCase = true
24+
) == true
1525
if (isMacHost) {
1626
listOf(
1727
iosX64(),
@@ -46,6 +56,8 @@ kotlin {
4656
implementation(compose.desktop.common)
4757
}
4858

59+
wasmJsMain.dependencies { }
60+
4961
if (isMacHost) {
5062
iosMain.dependencies { }
5163
}

demo-shared/src/commonMain/kotlin/io/github/kdroidfilter/webview/demo/App.kt

Lines changed: 57 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,12 @@
11
package io.github.kdroidfilter.webview.demo
22

3-
import androidx.compose.foundation.layout.BoxWithConstraints
4-
import androidx.compose.foundation.layout.Column
5-
import androidx.compose.foundation.layout.Row
6-
import androidx.compose.foundation.layout.fillMaxHeight
7-
import androidx.compose.foundation.layout.fillMaxSize
8-
import androidx.compose.foundation.layout.fillMaxWidth
9-
import androidx.compose.foundation.layout.heightIn
10-
import androidx.compose.foundation.layout.width
113
import androidx.compose.animation.AnimatedVisibility
12-
import androidx.compose.material3.ExperimentalMaterial3Api
13-
import androidx.compose.material3.MaterialTheme
14-
import androidx.compose.material3.Surface
15-
import androidx.compose.material3.VerticalDivider
16-
import androidx.compose.material3.darkColorScheme
17-
import androidx.compose.runtime.Composable
18-
import androidx.compose.runtime.DisposableEffect
19-
import androidx.compose.runtime.ExperimentalComposeApi
20-
import androidx.compose.runtime.LaunchedEffect
21-
import androidx.compose.runtime.getValue
22-
import androidx.compose.runtime.movableContentOf
23-
import androidx.compose.runtime.mutableStateListOf
24-
import androidx.compose.runtime.mutableStateOf
25-
import androidx.compose.runtime.remember
26-
import androidx.compose.runtime.rememberCoroutineScope
27-
import androidx.compose.runtime.setValue
4+
import androidx.compose.foundation.layout.*
5+
import androidx.compose.material3.*
6+
import androidx.compose.runtime.*
287
import androidx.compose.ui.Modifier
298
import androidx.compose.ui.unit.dp
309
import io.github.kdroidfilter.webview.cookie.Cookie
31-
import io.github.kdroidfilter.webview.jsbridge.IJsMessageHandler
3210
import io.github.kdroidfilter.webview.jsbridge.rememberWebViewJsBridge
3311
import io.github.kdroidfilter.webview.util.KLogSeverity
3412
import io.github.kdroidfilter.webview.web.WebView
@@ -85,50 +63,51 @@ fun App() {
8563
backgroundColor = androidx.compose.ui.graphics.Color.White
8664
}
8765
val jsBridge = rememberWebViewJsBridge(navigator)
88-
val webViewContent =
89-
remember(webViewState, navigator, jsBridge) {
90-
movableContentOf<Modifier> { webViewModifier ->
91-
WebView(
92-
state = webViewState,
93-
navigator = navigator,
94-
webViewJsBridge = jsBridge,
95-
modifier = webViewModifier,
96-
)
97-
}
66+
val webViewContent = remember(webViewState, navigator, jsBridge) {
67+
movableContentOf<Modifier> { webViewModifier ->
68+
WebView(
69+
state = webViewState,
70+
navigator = navigator,
71+
webViewJsBridge = jsBridge,
72+
modifier = webViewModifier,
73+
)
9874
}
75+
}
9976

10077
var urlText by remember { mutableStateOf("https://httpbin.org/html") }
10178

102-
val additionalHeaders =
103-
remember(customHeadersEnabled, headerName, headerValue) {
104-
if (!customHeadersEnabled) return@remember emptyMap()
105-
val key = headerName.trim()
106-
if (key.isEmpty()) return@remember emptyMap()
107-
mapOf(key to headerValue)
79+
val additionalHeaders = remember(customHeadersEnabled, headerName, headerValue) {
80+
if (!customHeadersEnabled) {
81+
return@remember emptyMap()
10882
}
83+
val key = headerName.trim()
84+
if (key.isEmpty()) {
85+
return@remember emptyMap()
86+
}
87+
mapOf(key to headerValue)
88+
}
10989

11090
LaunchedEffect(webViewState.lastLoadedUrl) {
11191
webViewState.lastLoadedUrl?.let { urlText = it }
11292
}
11393

11494
DisposableEffect(jsBridge, webViewState, scope) {
115-
val handlers =
116-
listOf<IJsMessageHandler>(
117-
EchoHandler(onLog = ::log),
118-
AppInfoHandler(onLog = ::log),
119-
NavigateHandler(onLog = ::log),
120-
SetCookieHandler(
121-
scope = scope,
122-
cookieManager = webViewState.cookieManager,
123-
onLog = ::log,
124-
),
125-
GetCookiesHandler(
126-
scope = scope,
127-
cookieManager = webViewState.cookieManager,
128-
onLog = ::log,
129-
),
130-
CustomHandler(onLog = ::log),
131-
)
95+
val handlers = listOf(
96+
EchoHandler(onLog = ::log),
97+
AppInfoHandler(onLog = ::log),
98+
NavigateHandler(onLog = ::log),
99+
SetCookieHandler(
100+
scope = scope,
101+
cookieManager = webViewState.cookieManager,
102+
onLog = ::log,
103+
),
104+
GetCookiesHandler(
105+
scope = scope,
106+
cookieManager = webViewState.cookieManager,
107+
onLog = ::log,
108+
),
109+
CustomHandler(onLog = ::log),
110+
)
132111

133112
handlers.forEach(jsBridge::register)
134113
onDispose { handlers.forEach(jsBridge::unregister) }
@@ -145,6 +124,7 @@ fun App() {
145124

146125
var jsSnippet by remember {
147126
mutableStateOf(
127+
//language=javascript
148128
"""
149129
(function () {
150130
const id = "composewebview-demo-banner";
@@ -209,9 +189,9 @@ fun App() {
209189

210190
AnimatedVisibility(visible = toolsVisible) {
211191
DemoToolsPanel(
212-
modifier =
213-
Modifier.fillMaxWidth()
214-
.heightIn(max = constraintsMaxHeight * 0.65f),
192+
modifier = Modifier
193+
.fillMaxWidth()
194+
.heightIn(max = constraintsMaxHeight * 0.65f),
215195
isCompact = true,
216196
webViewState = webViewState,
217197
navigator = navigator,
@@ -241,24 +221,22 @@ fun App() {
241221
cookies = cookies,
242222
onSetCookie = {
243223
val url = normalizeUrl(cookieUrlText.ifBlank { urlText })
244-
val domain =
245-
cookieDomain
246-
.trim()
247-
.ifBlank { hostFromUrl(url).orEmpty() }
248-
.trim()
249-
.takeIf { it.isNotBlank() }
224+
val domain = cookieDomain
225+
.trim()
226+
.ifBlank { hostFromUrl(url).orEmpty() }
227+
.trim()
228+
.takeIf { it.isNotBlank() }
250229
val path = cookiePath.trim().ifBlank { "/" }
251-
val cookie =
252-
Cookie(
253-
name = cookieName.trim().ifBlank { "demo_cookie" },
254-
value = cookieValue,
255-
domain = domain,
256-
path = path,
257-
isSessionOnly = true,
258-
isSecure = cookieSecure,
259-
isHttpOnly = cookieHttpOnly,
260-
sameSite = Cookie.HTTPCookieSameSitePolicy.LAX,
261-
)
230+
val cookie = Cookie(
231+
name = cookieName.trim().ifBlank { "demo_cookie" },
232+
value = cookieValue,
233+
domain = domain,
234+
path = path,
235+
isSessionOnly = true,
236+
isSecure = cookieSecure,
237+
isHttpOnly = cookieHttpOnly,
238+
sameSite = Cookie.HTTPCookieSameSitePolicy.LAX,
239+
)
262240
scope.launch {
263241
webViewState.cookieManager.setCookie(url, cookie)
264242
log("setCookie url=$url ${cookie.name} domain=${cookie.domain} path=${cookie.path}")
@@ -298,6 +276,7 @@ fun App() {
298276
},
299277
onCallNativeFromJs = {
300278
val script =
279+
//language=javascript
301280
"""
302281
if (window.kmpJsBridge && window.kmpJsBridge.callNative) {
303282
window.kmpJsBridge.callNative("echo", { text: "Hello from Kotlin (evaluateJavaScript)" }, function (data) {

demo-shared/src/commonMain/kotlin/io/github/kdroidfilter/webview/demo/DemoToolsPanel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ private fun KeyValueRow(
425425
}
426426

427427
private fun inlineHtml(): String =
428+
//language=HTML
428429
"""
429430
<!doctype html>
430431
<html lang="en">
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.github.kdroidfilter.webview.demo
2+
3+
@OptIn(ExperimentalWasmJsInterop::class)
4+
internal actual fun nowTimestamp(): String = js(
5+
"new Date().toISOString().slice(11, 19)"
6+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.github.kdroidfilter.webview.demo
2+
3+
import kotlinx.browser.window
4+
import kotlinx.serialization.json.buildJsonObject
5+
import kotlinx.serialization.json.put
6+
7+
internal actual fun platformInfoJson(): String = buildJsonObject {
8+
put("platform", "wasmJs")
9+
put("runtime", "browser")
10+
put("userAgent", window.navigator.userAgent)
11+
put("language", window.navigator.language)
12+
}.toString()

demo-wasmJs/build.gradle.kts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@file:OptIn(ExperimentalWasmDsl::class)
2+
3+
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
4+
5+
plugins {
6+
alias(libs.plugins.kotlinMultiplatform)
7+
alias(libs.plugins.composeMultiplatform)
8+
alias(libs.plugins.composeCompiler)
9+
alias(libs.plugins.composeHotReload)
10+
}
11+
12+
kotlin {
13+
wasmJs {
14+
browser()
15+
binaries.executable()
16+
}
17+
18+
sourceSets {
19+
wasmJsMain.dependencies {
20+
implementation(compose.runtime)
21+
implementation(compose.foundation)
22+
implementation(compose.material3)
23+
implementation(compose.ui)
24+
implementation(project(":demo-shared"))
25+
}
26+
}
27+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@file:OptIn(ExperimentalComposeUiApi::class)
2+
3+
package io.github.kdroidfilter.webview.demo
4+
5+
import androidx.compose.ui.ExperimentalComposeUiApi
6+
import androidx.compose.ui.window.ComposeViewport
7+
import kotlinx.browser.document
8+
import org.w3c.dom.HTMLElement
9+
10+
fun main() {
11+
val body: HTMLElement = document.body ?: return
12+
ComposeViewport(body) {
13+
App()
14+
}
15+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Demo</title>
7+
<style>
8+
html,
9+
body {
10+
width: 100%;
11+
height: 100%;
12+
margin: 0;
13+
padding: 0;
14+
overflow: hidden;
15+
}
16+
</style>
17+
</head>
18+
<body>
19+
<script src="demo-wasmJs.js"></script>
20+
</body>
21+
</html>

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@ plugins {
3535
include(":demo")
3636
include(":demo-shared")
3737
include(":demo-android")
38+
include(":demo-wasmJs")
3839
include(":wrywebview")
3940
include(":webview-compose")

webview-compose/build.gradle.kts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
@file:OptIn(ExperimentalWasmDsl::class)
2+
13
import com.vanniktech.maven.publish.KotlinMultiplatform
4+
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
25

36
plugins {
47
alias(libs.plugins.androidLibrary)
@@ -13,6 +16,9 @@ kotlin {
1316

1417
androidTarget()
1518
jvm()
19+
wasmJs {
20+
browser()
21+
}
1622

1723
listOf(
1824
iosX64(),
@@ -26,6 +32,10 @@ kotlin {
2632
iosTarget.setUpiOSObserver()
2733
}
2834

35+
compilerOptions {
36+
freeCompilerArgs.add("-Xexpect-actual-classes")
37+
}
38+
2939
sourceSets {
3040
commonMain.dependencies {
3141
implementation(compose.runtime)
@@ -45,6 +55,8 @@ kotlin {
4555
}
4656

4757
iosMain.dependencies { }
58+
59+
wasmJsMain.dependencies { }
4860
}
4961
}
5062

0 commit comments

Comments
 (0)