Skip to content

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

@thomastvedt

Description

@thomastvedt

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
  2. Throws inside the hasSymbolBuckets / hasRTLText branch when touching this.style.imageManager.
  3. refreshFeatureState path:
    TypeError: undefined is not an object (evaluating 'f.style.listImages')
    at refreshFeatureState
    at initializeTileState
    at _tileLoaded
    at processTask
    at receive
  4. Same shape as TypeError: undefined is not an object (evaluating 'i.style.listImages') #13361 (reported against v3.8.0, attributed to Fixed attempt of buckets update before map first render #13377, landed in v3.9.2). We are seeing it more than 12 minor releases later in v3.21.0, so either Fixed attempt of buckets update before map first render #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

Relevant log output

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<<Math.min(thi {snip}
{snip} t.terrain)}}_c.getSourceType=function(S){return Bc[S]},_c.setSourceType=function(S,d){Bc[S]=d},_c.registerForPluginStateChange=o.dg;var kh=`
#define EPSILON 0.0000001
#define PI 3.141592653589793
#ifdef RENDER_CUTOFF
{snip} utoffEnd=cutoff_params.w;float linearDepth=(depth-near)/(far-near);return clamp((linearDepth-cutoffStart)/(cutoffEnd-cutoffStart),0.0,1.0);}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions