`ui.scene` and `ui.scene_view`: `initialized()` never resolves when `mounted()` returns before emitting `init`
Description
Both scene.js and scene_view.js emit init from a setInterval created as the very last statement of mounted() — scene.js:224 and scene_view.js:97. Every early exit from mounted() before that point leaves _initialized_event unset for good, and initialized() waits on it with no timeout and no error. The element is alive, its client is alive, and the awaiting task simply parks.
Two such exits exist today.
1. scene_view whose source scene was deleted. scene_view.js:11 reads getElement(this.sceneId).scene during mounted(). If the source ui.scene is gone by then, that read throws and the rest of mounted() — including the interval — never runs.
2. Any ui.scene or ui.scene_view without WebGL. The catch around new THREE.WebGLRenderer(...) renders the "Could not create WebGL renderer." fallback and returns: scene_view.js:48-54, and the identical shape in scene.js:101-107. So on a machine or browser without WebGL the element shows its fallback text, looks handled — and await scene.initialized() never comes back. This half is read off the source only: I had no WebGL-less environment at hand, and it should be verified in a real one before anyone acts on it. The reproduction below covers the first case.
Minimal example for the first case. The page prints PAGE-START and then nothing: neither RESOLVED nor CANCELLED.
import asyncio
from nicegui import ui
@ui.page('/')
async def page():
ui.label('PAGE-START')
scene = ui.scene()
view = ui.scene_view(scene)
scene.delete()
try:
await view.initialized()
ui.label('RESOLVED')
except asyncio.CancelledError:
ui.label('CANCELLED')
raise
ui.run()The browser console carries the cause:
TypeError: Cannot read properties of undefined (reading 'scene')
at Proxy.mounted (.../components/.../scene_view.js:11)Why this is not covered by #6271 / #6294
Those close the case where the awaited element itself is deleted: CancelableWaitElement cancels the waiter, and initialized() raises CancelledError. Here the awaited element is never deleted — only its dependency is, or its renderer fails — so the mixin never fires. After #6294 the stranded task is at least reaped when the scene_view is itself deleted, instead of living until shutdown; but a page that just awaits initialized() still hangs.
What needs deciding
- What should
initialized()do when initialization has definitively failed? The natural analogue of the ruling in #6277 is to raise rather than resolve — resolving would report an initialization that never happened. That needs a channel from JS: aninitevent carrying a failure flag, or a separateinit-failedevent. - Should
scene_viewfollow its source scene? Today the dependency exists only in JS (sceneId), with no Python-side lifecycle link, so deleting the scene leaves a view that can never initialize. Deleting the view along with its scene would make case 1 fall back to the existingCancelledErrorpath and need no new channel.
Found while reviewing #6294, where it is out of scope.
Source: zauberzeug/nicegui