Skip to content

Commit

Permalink
[wasm][js] Dispose all listened events alongside with the application…
Browse files Browse the repository at this point in the history
… being disposed (#1239)
  • Loading branch information
Schahen authored Apr 5, 2024
1 parent 3c5febe commit ae0fd0d
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ package androidx.compose.ui.test
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import org.jetbrains.skiko.SkikoInputModifiers
import org.jetbrains.skiko.SkikoKey
import org.jetbrains.skiko.SkikoKeyboardEvent
import org.jetbrains.skiko.SkikoKeyboardEventKind
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.KeyboardEventInit
import androidx.compose.ui.input.key.NativeKeyEvent

/**
* The [KeyEvent] is usually created by the system. This function creates an instance of
Expand All @@ -35,26 +30,13 @@ internal actual fun keyEvent(
): KeyEvent {
val nativeCode = key.keyCode.toInt()

val kind = when (keyEventType) {
KeyEventType.KeyUp -> SkikoKeyboardEventKind.UP
KeyEventType.KeyDown -> SkikoKeyboardEventKind.DOWN
else -> SkikoKeyboardEventKind.UNKNOWN
}

return KeyEvent(
SkikoKeyboardEvent(
kind = kind,
key = SkikoKey.valueOf(nativeCode),
modifiers = SkikoInputModifiers(modifiers),
platform = KeyboardEvent(
when (kind) {
SkikoKeyboardEventKind.DOWN -> "keydown"
SkikoKeyboardEventKind.UP -> "keyup"
else -> "keypress"
}, KeyboardEventInit(
key = if (key.isPrintable()) nativeCode.toChar().toString() else "Unknown"
)
)
NativeKeyEvent(
key = key,
kind = keyEventType,
value = if (key.isPrintable()) nativeCode.toChar().toString() else "Unknown",
modifiers = modifiers,
timestamp = 0
)
)
}
Expand All @@ -79,18 +61,4 @@ private fun Key.isPrintable(): Boolean {
return !NonPrintableKeys.contains(this)
}

internal actual fun Int.updatedKeyboardModifiers(key: Key, down: Boolean): Int {
val mask = when (key) {
Key.ShiftLeft, Key.ShiftRight -> SkikoInputModifiers.SHIFT.value
Key.CtrlLeft, Key.CtrlRight -> SkikoInputModifiers.CONTROL.value
Key.MetaLeft, Key.MetaRight -> SkikoInputModifiers.META.value
Key.AltLeft, Key.AltRight -> SkikoInputModifiers.ALT.value
else -> null
}

return if (mask != null) {
if (down) this or mask else this xor mask
} else {
this
}
}
internal actual fun Int.updatedKeyboardModifiers(key: Key, down: Boolean): Int = this
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.ui.events

import org.w3c.dom.events.Event
import org.w3c.dom.events.EventTarget

private fun interface DisposableEventListener {
fun dispose()
}

private fun EventTarget.addDisposableEvent(eventName: String, handler: (Event) -> Unit): DisposableEventListener {
addEventListener(eventName, handler)
return DisposableEventListener { removeEventListener(eventName, handler) }
}

internal class EventTargetListener(private val eventTarget: EventTarget): DisposableEventListener {
private val registeredEvents = mutableListOf<DisposableEventListener>()

fun addDisposableEvent(eventName: String, handler: (Event) -> Unit) {
registeredEvents.add(eventTarget.addDisposableEvent(eventName, handler))
}

override fun dispose() {
registeredEvents.forEach { evt -> evt.dispose() }
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.LocalSystemTheme
import androidx.compose.ui.events.EventTargetListener
import androidx.compose.ui.events.toNativeDragEvent
import androidx.compose.ui.events.toNativePointerEvent
import androidx.compose.ui.events.toNativeScrollEvent
Expand Down Expand Up @@ -72,9 +73,16 @@ private interface ComposeWindowState {
fun init() {}
fun sizeFlow(): Flow<IntSize>

val globalEvents: EventTargetListener

fun dispose() {
globalEvents.dispose()
}

companion object {
fun createFromLambda(lambda: suspend () -> IntSize): ComposeWindowState {
return object : ComposeWindowState {
override val globalEvents = EventTargetListener(window)
override fun sizeFlow(): Flow<IntSize> = flow {
while (coroutineContext.isActive) {
emit(lambda())
Expand All @@ -88,10 +96,13 @@ private interface ComposeWindowState {
private class DefaultWindowState(private val viewportContainer: Element) : ComposeWindowState {
private val channel = Channel<IntSize>(CONFLATED)

override val globalEvents = EventTargetListener(window)

override fun init() {
window.addEventListener("resize", {

globalEvents.addDisposableEvent("resize") {
channel.trySend(getParentContainerBox())
})
}

initMediaEventListener {
channel.trySend(getParentContainerBox())
Expand Down Expand Up @@ -133,6 +144,9 @@ private class ComposeWindow(
private val _windowInfo = WindowInfoImpl().apply {
isWindowFocused = true
}

private val canvasEvents = EventTargetListener(canvas)

private val jsTextInputService = JSTextInputService()
private val platformContext: PlatformContext =
object : PlatformContext by PlatformContext.Empty {
Expand Down Expand Up @@ -163,7 +177,7 @@ private class ComposeWindow(
type: String,
handler: (event: T) -> Unit
) {
canvas.addEventListener(type, { event -> handler(event as T) })
canvasEvents.addDisposableEvent(type) { event -> handler(event as T) }
}

private fun initEvents(canvas: HTMLCanvasElement) {
Expand Down Expand Up @@ -234,13 +248,13 @@ private class ComposeWindow(
if (processed) event.preventDefault()
}

window.addEventListener("focus", {
state.globalEvents.addDisposableEvent("focus") {
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
})
}

window.addEventListener("blur", {
state.globalEvents.addDisposableEvent("blur") {
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
})
}

lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
Expand Down Expand Up @@ -296,6 +310,10 @@ private class ComposeWindow(
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
layer.dispose()
systemThemeObserver.dispose()
state.dispose()
// modern browsers supposed to garbage collect all events on the element disposed
// but actually we never can be sure dom element was collected in first place
canvasEvents.dispose()
}
}

Expand Down Expand Up @@ -385,4 +403,4 @@ fun ComposeViewport(
content = content,
state = DefaultWindowState(viewportContainer)
)
}
}

0 comments on commit ae0fd0d

Please sign in to comment.