mapbox / mapbox/mapbox-gl-js

TypeError on 'style.imageManager' / 'style.listImages' from worker tile callback after map unmount or setStyle (Safari)

Open
#13,660 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

auto-triaged bug :lady_beetle:
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.