TypeError on 'style.imageManager' / 'style.listImages' from worker tile callback after map unmount or setStyle (Safari)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 12.4k
- Forks
- 2.4k
- PR merge metrics
- No merged PRs in 30d
Description
### mapbox-gl-js version
v3.21.0
### Browser and version
Safari 26.3 on macOS (not reproduced on Chromium — see below)
### Expected behavior
When a map is removed via map.remove() or its style is swapped via map.setStyle(), any in-flight worker tile-load messages should either be cancelled before their main-thread callback runs, or the callback should be guarded against this.style being undefined. No TypeError should reach the window error handler.
### Actual behavior
A worker message event handler invokes a tile-load callback after the map's style has been torn down. Two distinct stacks are observed in production, both originating from Actor.receive →
processTask on the main thread, both ultimately dereferencing a now-undefined style:
1. loadVectorData path:
TypeError: undefined is not an object (evaluating 'l.style.imageManager')
at loadVectorData
at processTask
at receive
1. Throws inside the hasSymbolBuckets / hasRTLText branch when touching this.style.imageManager.
2. refreshFeatureState path:
TypeError: undefined is not an object (evaluating 'f.style.listImages')
at refreshFeatureState
at initializeTileState
at _tileLoaded
at processTask
at receive
2. Same shape as #13361 (reported against v3.8.0, attributed to #13377, landed in v3.9.2). We are seeing it more than 12 minor releases later in v3.21.0, so either #13377 didn't cover all
entry points or a regression has reintroduced the race on another path.
### Link to the demonstration
No reliable minimal repro yet — the race is timing-dependent and we've only seen it in production. If useful, I can share anonymized Sentry events and session replays separately.
### Steps to trigger the unexpected behavior
Not deterministic, but in production the error fires when one of the following happens while vector tiles are in flight:
1. The component owning the map is unmounted (calls map.remove()), or
2. map.setStyle() is invoked to switch between a light/dark/satellite style.
In both cases, a worker message already in the Safari event queue is dispatched after map.style has been nulled. Chromium tends to drain queued worker messages before teardown completes,
which is likely why we don't see this stack from non-Safari users.
Relevant log output
/assets/vendor-CnBHFJ2o.js:538:536512 (loadVectorData)
/assets/vendor-CnBHFJ2o.js:538:530331 (processTask)
/assets/vendor-CnBHFJ2o.js:538:530172 (receive)
/assets/vendor-CnBHFJ2o.js:538:542567 (refreshFeatureState)
/assets/vendor-CnBHFJ2o.js:539:75049 (initializeTileState)
/assets/vendor-CnBHFJ2o.js:539:80134 (_tileLoaded)
/assets/vendor-CnBHFJ2o.js:538:530331 (processTask)
/assets/vendor-CnBHFJ2o.js:538:530172 (receive)
User-agent: Safari 26.3, macOS >=10.15.7
Related
- #13361 (closed, same listImages symptom against v3.8.0)
- #13377 (the fix merged into v3.9.2)
### Relevant log output
```shell
Stacktrace js:
TypeError: undefined is not an object (evaluating 'f.style.listImages')
at refreshFeatureState (/assets/vendor-CnBHFJ2o.js:538:542567)
at initializeTileState (/assets/vendor-CnBHFJ2o.js:539:75049)
at _tileLoaded (/assets/vendor-CnBHFJ2o.js:539:80134)
at processTask (/assets/vendor-CnBHFJ2o.js:538:530331)
at receive (/assets/vendor-CnBHFJ2o.js:538:530172)
at r (/assets/vendor-CnBHFJ2o.js:479:9531)
----
This is most likely a bug, please report this via https://github.com/mapbox/mapbox-gl-js/issues/new?assignees=&labels=&template=Bug_report.md
and paste the contents of this message in the report.
Thank you!
Filter Expression:
${JSON.stringify(b,null,2)}
{snip} .state="expired"):this.expiredRequestCount=0}}getExpiryTimeout(){if(this.expirationTime)return this.expiredRequestCount?1e3*(1<
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing the receive → processTask flow into loadVectorData and refreshFeatureState, then follow initializeTileState and _tileLoaded for the two reported failures. Reproduce the timing around map.remove() and map.setStyle() in Safari if possible, and inspect how style teardown interacts with queued worker messages. Done means neither path reaches a TypeError after teardown, with regression coverage for both entry points.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend, web-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100