0
# Browser Integration
1
2
Compose Multiplatform for WASM/JS provides seamless integration with browser APIs and web platform features. This enables access to browser-specific functionality, system preferences, network operations, and JavaScript interoperability while maintaining the declarative Compose programming model.
3
4
## Browser API Access
5
6
### Window and Document
7
8
Direct access to browser window and document objects.
9
10
```kotlin { .api }
11
// Browser window access
12
val window: Window = kotlinx.browser.window
13
val document: Document = kotlinx.browser.document
14
```
15
16
**Window Operations:**
17
```kotlin { .api }
18
@Composable
19
fun WindowIntegration() {
20
LaunchedEffect(Unit) {
21
// Window properties
22
val windowWidth = window.innerWidth
23
val windowHeight = window.innerHeight
24
25
// Browser information
26
val userAgent = window.navigator.userAgent
27
val language = window.navigator.language
28
val platform = window.navigator.platform
29
30
// Location and history
31
val currentUrl = window.location.href
32
window.history.pushState(null, "New Page", "/new-path")
33
}
34
}
35
```
36
37
**Document Manipulation:**
38
```kotlin { .api }
39
@Composable
40
fun DocumentIntegration() {
41
LaunchedEffect(Unit) {
42
// Document properties
43
val title = document.title
44
document.title = "New Title"
45
46
// Element access
47
val element = document.getElementById("myElement")
48
val canvas = document.querySelector("canvas")
49
50
// DOM manipulation
51
val newElement = document.createElement("div")
52
newElement.textContent = "Created from Kotlin"
53
document.body?.appendChild(newElement)
54
}
55
}
56
```
57
58
## System Preferences Detection
59
60
### Dark Mode Detection
61
62
```kotlin { .api }
63
fun isSystemInDarkTheme(): Boolean {
64
return window.matchMedia("(prefers-color-scheme: dark)").matches
65
}
66
67
@Composable
68
fun DarkModeAwareTheme(content: @Composable () -> Unit) {
69
var isDarkMode by remember { mutableStateOf(isSystemInDarkTheme()) }
70
71
LaunchedEffect(Unit) {
72
val mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
73
val listener: (MediaQueryListEvent) -> Unit = { event ->
74
isDarkMode = event.matches
75
}
76
77
mediaQuery.addEventListener("change", listener)
78
// Cleanup handled automatically
79
}
80
81
MaterialTheme(
82
colors = if (isDarkMode) darkColors() else lightColors(),
83
content = content
84
)
85
}
86
```
87
88
### Language and Locale
89
90
```kotlin { .api }
91
fun getCurrentLanguage(): String {
92
return window.navigator.languages.firstOrNull()
93
?: window.navigator.language
94
?: "en"
95
}
96
97
fun getCurrentLocale(): String {
98
val language = getCurrentLanguage()
99
return language.split("-").first() // Extract primary language
100
}
101
102
@Composable
103
fun LocalizedContent() {
104
val currentLanguage = remember { getCurrentLanguage() }
105
val isRightToLeft = remember {
106
listOf("ar", "he", "fa", "ur").contains(currentLanguage.split("-").first())
107
}
108
109
CompositionLocalProvider(
110
LocalLayoutDirection provides if (isRightToLeft) LayoutDirection.Rtl else LayoutDirection.Ltr
111
) {
112
// Content automatically adapts to language direction
113
Text(stringResource(Res.string.welcome_message))
114
}
115
}
116
```
117
118
### Display and Accessibility
119
120
```kotlin { .api }
121
fun getDevicePixelRatio(): Double {
122
return window.devicePixelRatio
123
}
124
125
fun prefersReducedMotion(): Boolean {
126
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
127
}
128
129
fun prefersHighContrast(): Boolean {
130
return window.matchMedia("(prefers-contrast: high)").matches
131
}
132
133
@Composable
134
fun AccessibilityAwareUI() {
135
val reducedMotion = remember { prefersReducedMotion() }
136
val highContrast = remember { prefersHighContrast() }
137
138
AnimatedVisibility(
139
visible = shouldShowContent,
140
enter = if (reducedMotion) EnterTransition.None else fadeIn(),
141
exit = if (reducedMotion) ExitTransition.None else fadeOut()
142
) {
143
Surface(
144
color = if (highContrast) Color.Black else MaterialTheme.colors.surface
145
) {
146
// Content adapted for accessibility
147
}
148
}
149
}
150
```
151
152
## Network Operations
153
154
### Fetch API
155
156
Modern web API for network requests.
157
158
```kotlin { .api }
159
suspend fun fetchData(url: String): String {
160
return window.fetch(url).await().text().await()
161
}
162
163
suspend fun fetchJson(url: String): dynamic {
164
val response = window.fetch(url).await()
165
if (!response.ok) {
166
throw Exception("Network request failed: ${response.status}")
167
}
168
return response.json().await()
169
}
170
171
@Composable
172
fun NetworkDataLoader() {
173
var data by remember { mutableStateOf<String?>(null) }
174
var isLoading by remember { mutableStateOf(false) }
175
var error by remember { mutableStateOf<String?>(null) }
176
177
LaunchedEffect(Unit) {
178
isLoading = true
179
try {
180
data = fetchData("https://api.example.com/data")
181
error = null
182
} catch (e: Exception) {
183
error = e.message
184
data = null
185
} finally {
186
isLoading = false
187
}
188
}
189
190
when {
191
isLoading -> CircularProgressIndicator()
192
error != null -> Text("Error: $error", color = MaterialTheme.colors.error)
193
data != null -> Text("Data: $data")
194
}
195
}
196
```
197
198
### XMLHttpRequest
199
200
Traditional HTTP requests with more control.
201
202
```kotlin { .api }
203
suspend fun xmlHttpRequest(url: String, method: String = "GET"): String {
204
return suspendCoroutine { continuation ->
205
val xhr = XMLHttpRequest()
206
xhr.open(method, url)
207
208
xhr.onload = {
209
if (xhr.status == 200.toShort()) {
210
continuation.resume(xhr.responseText)
211
} else {
212
continuation.resumeWithException(
213
Exception("HTTP ${xhr.status}: ${xhr.statusText}")
214
)
215
}
216
}
217
218
xhr.onerror = {
219
continuation.resumeWithException(Exception("Network error"))
220
}
221
222
xhr.send()
223
}
224
}
225
```
226
227
## Local Storage
228
229
### Browser Storage APIs
230
231
```kotlin { .api }
232
object BrowserStorage {
233
fun setItem(key: String, value: String) {
234
window.localStorage.setItem(key, value)
235
}
236
237
fun getItem(key: String): String? {
238
return window.localStorage.getItem(key)
239
}
240
241
fun removeItem(key: String) {
242
window.localStorage.removeItem(key)
243
}
244
245
fun clear() {
246
window.localStorage.clear()
247
}
248
}
249
250
@Composable
251
fun PersistentSettings() {
252
var theme by remember {
253
val saved = BrowserStorage.getItem("theme") ?: "light"
254
mutableStateOf(saved)
255
}
256
257
LaunchedEffect(theme) {
258
BrowserStorage.setItem("theme", theme)
259
}
260
261
Switch(
262
checked = theme == "dark",
263
onCheckedChange = { isDark ->
264
theme = if (isDark) "dark" else "light"
265
}
266
)
267
}
268
```
269
270
### Session Storage
271
272
```kotlin { .api }
273
object SessionStorage {
274
fun setItem(key: String, value: String) {
275
window.sessionStorage.setItem(key, value)
276
}
277
278
fun getItem(key: String): String? {
279
return window.sessionStorage.getItem(key)
280
}
281
}
282
```
283
284
## Platform Detection
285
286
### Runtime Platform Information
287
288
```kotlin { .api }
289
fun getCurrentPlatform(): String = "Web WASM"
290
291
fun getBrowserInfo(): BrowserInfo {
292
val userAgent = window.navigator.userAgent
293
return BrowserInfo(
294
name = detectBrowserName(userAgent),
295
version = detectBrowserVersion(userAgent),
296
isMobile = isMobileDevice(userAgent),
297
supportsWebAssembly = supportsWasm(),
298
supportsWebGL = supportsWebGL()
299
)
300
}
301
302
data class BrowserInfo(
303
val name: String,
304
val version: String,
305
val isMobile: Boolean,
306
val supportsWebAssembly: Boolean,
307
val supportsWebGL: Boolean
308
)
309
310
fun detectBrowserName(userAgent: String): String {
311
return when {
312
"Chrome" in userAgent -> "Chrome"
313
"Firefox" in userAgent -> "Firefox"
314
"Safari" in userAgent -> "Safari"
315
"Edge" in userAgent -> "Edge"
316
else -> "Unknown"
317
}
318
}
319
320
@Composable
321
fun PlatformAwareUI() {
322
val browserInfo = remember { getBrowserInfo() }
323
324
if (browserInfo.isMobile) {
325
// Mobile-optimized UI
326
Column(
327
modifier = Modifier.padding(8.dp)
328
) {
329
// Larger touch targets
330
Button(
331
onClick = { /* action */ },
332
modifier = Modifier
333
.fillMaxWidth()
334
.height(56.dp)
335
) {
336
Text("Mobile Button")
337
}
338
}
339
} else {
340
// Desktop-optimized UI
341
Row {
342
// More compact layout
343
Button(onClick = { /* action */ }) {
344
Text("Desktop Button")
345
}
346
}
347
}
348
}
349
```
350
351
## JavaScript Interoperability
352
353
### Calling JavaScript Functions
354
355
```kotlin { .api }
356
// Define external JavaScript functions
357
@JsFun("(message) => { console.log(message); }")
358
external fun jsConsoleLog(message: String)
359
360
@JsFun("(callback) => { setTimeout(callback, 1000); }")
361
external fun jsSetTimeout(callback: () -> Unit)
362
363
@JsFun("() => { return Date.now(); }")
364
external fun jsGetTimestamp(): Double
365
366
@Composable
367
fun JavaScriptIntegration() {
368
LaunchedEffect(Unit) {
369
// Call JavaScript functions
370
jsConsoleLog("Hello from Kotlin!")
371
372
val timestamp = jsGetTimestamp()
373
println("Current timestamp: $timestamp")
374
375
jsSetTimeout {
376
println("JavaScript timeout callback executed")
377
}
378
}
379
}
380
```
381
382
### Accessing JavaScript Objects
383
384
```kotlin { .api }
385
@Composable
386
fun JavaScriptObjectAccess() {
387
LaunchedEffect(Unit) {
388
// Access global JavaScript objects
389
val console = window.asDynamic().console
390
console.log("Direct console access")
391
392
// Use browser APIs
393
val performance = window.asDynamic().performance
394
val navigationStart = performance.timing.navigationStart
395
396
// Access custom JavaScript objects
397
val customObject = window.asDynamic().myCustomObject
398
if (customObject != null) {
399
customObject.customMethod("parameter")
400
}
401
}
402
}
403
```
404
405
## URL and Navigation
406
407
### URL Management
408
409
```kotlin { .api }
410
@Composable
411
fun URLManagement() {
412
var currentPath by remember { mutableStateOf(window.location.pathname) }
413
414
LaunchedEffect(Unit) {
415
// Listen for navigation events
416
window.addEventListener("popstate") { event ->
417
currentPath = window.location.pathname
418
}
419
}
420
421
fun navigateTo(path: String) {
422
window.history.pushState(null, "", path)
423
currentPath = path
424
}
425
426
when (currentPath) {
427
"/" -> HomeScreen()
428
"/about" -> AboutScreen()
429
"/settings" -> SettingsScreen()
430
else -> NotFoundScreen()
431
}
432
433
BottomNavigation {
434
BottomNavigationItem(
435
icon = { Icon(Icons.Default.Home, contentDescription = null) },
436
label = { Text("Home") },
437
selected = currentPath == "/",
438
onClick = { navigateTo("/") }
439
)
440
BottomNavigationItem(
441
icon = { Icon(Icons.Default.Info, contentDescription = null) },
442
label = { Text("About") },
443
selected = currentPath == "/about",
444
onClick = { navigateTo("/about") }
445
)
446
}
447
}
448
```
449
450
### URL Parameters
451
452
```kotlin { .api }
453
fun parseUrlParameters(): Map<String, String> {
454
val params = mutableMapOf<String, String>()
455
val search = window.location.search.removePrefix("?")
456
457
if (search.isNotEmpty()) {
458
search.split("&").forEach { param ->
459
val (key, value) = param.split("=", limit = 2)
460
params[decodeURIComponent(key)] = decodeURIComponent(value)
461
}
462
}
463
464
return params
465
}
466
467
@Composable
468
fun URLParameterHandler() {
469
val urlParams = remember { parseUrlParameters() }
470
val userId = urlParams["userId"]
471
val tab = urlParams["tab"] ?: "profile"
472
473
userId?.let { id ->
474
UserProfile(userId = id, initialTab = tab)
475
} ?: run {
476
Text("User ID not provided")
477
}
478
}
479
```
480
481
## Performance Monitoring
482
483
### Performance API
484
485
```kotlin { .api }
486
fun measurePerformance(): PerformanceData {
487
val performance = window.asDynamic().performance
488
val timing = performance.timing
489
490
return PerformanceData(
491
navigationStart = timing.navigationStart as Double,
492
domContentLoaded = timing.domContentLoadedEventEnd as Double,
493
loadComplete = timing.loadEventEnd as Double,
494
firstPaint = getFirstPaintTime(),
495
memory = getMemoryUsage()
496
)
497
}
498
499
data class PerformanceData(
500
val navigationStart: Double,
501
val domContentLoaded: Double,
502
val loadComplete: Double,
503
val firstPaint: Double?,
504
val memory: MemoryInfo?
505
)
506
507
@Composable
508
fun PerformanceMonitor() {
509
var performanceData by remember { mutableStateOf<PerformanceData?>(null) }
510
511
LaunchedEffect(Unit) {
512
delay(1000) // Wait for page to load
513
performanceData = measurePerformance()
514
}
515
516
performanceData?.let { data ->
517
Column {
518
Text("Load Time: ${data.loadComplete - data.navigationStart}ms")
519
Text("DOM Ready: ${data.domContentLoaded - data.navigationStart}ms")
520
data.memory?.let { memory ->
521
Text("Memory Used: ${memory.usedJSHeapSize / 1024 / 1024}MB")
522
}
523
}
524
}
525
}
526
```
527
528
## Error Handling and Debugging
529
530
### Browser Developer Tools
531
532
```kotlin { .api }
533
fun logToBrowserConsole(message: String, level: LogLevel = LogLevel.LOG) {
534
val console = window.asDynamic().console
535
when (level) {
536
LogLevel.LOG -> console.log(message)
537
LogLevel.INFO -> console.info(message)
538
LogLevel.WARN -> console.warn(message)
539
LogLevel.ERROR -> console.error(message)
540
}
541
}
542
543
enum class LogLevel { LOG, INFO, WARN, ERROR }
544
545
@Composable
546
fun DebuggingSupport() {
547
LaunchedEffect(Unit) {
548
// Enable debugging in development
549
if (isDebugMode()) {
550
logToBrowserConsole("App started in debug mode", LogLevel.INFO)
551
552
// Register global error handler
553
window.addEventListener("error") { event ->
554
val errorEvent = event as ErrorEvent
555
logToBrowserConsole(
556
"Global error: ${errorEvent.message} at ${errorEvent.filename}:${errorEvent.lineno}",
557
LogLevel.ERROR
558
)
559
}
560
}
561
}
562
}
563
564
fun isDebugMode(): Boolean {
565
return window.location.hostname == "localhost" ||
566
window.location.search.contains("debug=true")
567
}
568
```