fix(google-maps): initialize deck in onDraw when onContextRestored is skipped#10179
Conversation
… skipped When a GoogleMapsOverlay with interleaved=true is removed from a map and a new overlay is subsequently added, Google Maps may not fire the onContextRestored callback for the new WebGLOverlayView because the WebGL context was never actually lost — only the overlay was removed. This leaves _deck as null, causing _onDrawVector to return early without ever rendering. The fix adds a defensive initialization check in _onDrawVector: if the deck instance has not been created yet and we are in interleaved mode, call _onContextRestored with the GL context provided by the onDraw callback. This ensures the deck instance is properly initialized regardless of whether Google Maps fires onContextRestored. The check only triggers when _deck is null, so it has no effect on the normal lifecycle where onContextRestored fires before onDraw. Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
Can you elaborate on this scenario? How does it bypass |
|
@Pessimistress The case I was aiming at is not that onAdd is skipped, it is that the interleaved WebGL overlay can reach onDraw without a fresh onContextRestored after the overlay is detached and re-added. That said, you are right that onDraw is a poor place to create resources. I need to rework this around the lifecycle rather than force initialization there. |
|
@Pessimistress Thanks for the question. The scenario is:
The fix does not create GL resources in the wrong place. It delegates to the existing |
This has nothing to do with |
…nterleaved mode When a WebGLOverlayView is removed and re-added to the same map, onContextRestored may be skipped because the WebGL context was never lost. Previously the fallback was in onDraw, but resource creation belongs in onAdd. The new _onAddInterleaved callback retrieves the existing GL context from the map canvas and delegates to _onContextRestored, keeping initialization in the correct lifecycle stage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@Pessimistress You're right — initializing resources in The new approach adds an
The |
Pessimistress
left a comment
There was a problem hiding this comment.
I want to challenge your report that this._deck is null on re-add.
As you can see, onRemove does not delete this._deck, only hides it.
Only places where this._deck is set to null is in onContextLost and finalize.
So either you are calling finalize in your own app, after which you should not call setMap again; or Google Maps is invoking onContextLost but not onContextRestored. Neither of these scenarios are for this component to handle.
| } | ||
| const mapCanvas = this._map.getDiv().querySelector('canvas'); | ||
| if (mapCanvas) { | ||
| const gl = mapCanvas.getContext('webgl2'); |
There was a problem hiding this comment.
This is very suspicious... If the underlying context has not been created for any reason, this will return a new WebGL context AND break the base map because getContext is not called with any expected options.
There was a problem hiding this comment.
Fair concern. HTMLCanvasElement.getContext('webgl2') is supposed to return the existing context if one was already created on that canvas (which Google Maps does before onAdd runs). But the moment the invariant breaks, we'd spuriously create a fresh context without the options Google Maps needs, which is exactly what you're flagging.
Two options I can take:
- Drop this path entirely and instead trigger
this._overlay.requestRedraw()right after the re-add, letting the normalonContextRestoredlifecycle handle it. - Keep the canvas lookup but guard with a check that the canvas already has an active gl context before calling getContext (e.g. inspect canvas attributes or skip if any unexpected state), and only fire once via the
_overlayInitializedflag I mentioned on the other thread.
(1) feels safer to me if requestRedraw reliably triggers onContextRestored on a re-add.
| // Create WebGL overlay for camera data (and GL context if interleaved) | ||
| const overlay = new google.maps.WebGLOverlayView(); | ||
| overlay.onAdd = noop; | ||
| overlay.onAdd = interleaved ? this._onAddInterleaved.bind(this) : noop; |
There was a problem hiding this comment.
If onContextRestored is also invoked on first add, doesn't this call _onContextRestored twice?
There was a problem hiding this comment.
Good catch. On first add Google Maps does fire onContextRestored separately, so this would indeed double-init. The intent here was to cover the re-add case where the context was never lost and onContextRestored is skipped.
Thinking about the cleanest fix: track an _overlayInitialized flag per overlay instance that _onContextRestored sets (and _onContextLost/_onRemove clears), then defer the fallback via queueMicrotask so Google Maps' own onContextRestored can fire first. If it does, the flag gates out the duplicate. If it doesn't (the re-add case), we fetch the existing context.
Does that approach sound right to you before I push?
There was a problem hiding this comment.
I am still not convinced. As I commented before:
onRemove does not delete this._deck, only hides it.
Only places where this._deck is set to null is in onContextLost and finalize.
So either you are calling finalize in your own app, after which you should not call setMap again; or Google Maps is invoking onContextLost but not onContextRestored. Neither of these scenarios are for this component to handle.
Closes #10059
Change List
_onDrawVectorfor interleaved mode whenonContextRestoredwas not firedWebGLOverlayViewis removed from a map and a new one is added, Google Maps may not fireonContextRestoredbecause the WebGL context was never actually lost — only the overlay was detached_deckasnull, causing_onDrawVectorto return early without rendering, making the second overlay permanently invisible_onContextRestored({gl})from_onDrawVectorif the deck instance hasn't been created yet, using the GL context provided by theonDrawcallback!this._deck && this.props.interleaved) ensures this only triggers when needed and has no effect on the normal lifecycle whereonContextRestoredfires beforeonDraw