This simple expression constructor will be used for symbolizer properties and the layer will generate symbolizer literals for rendering by evaluating any expressions with a feature as the this argument and feature attributes as the scope. This allows generating labels that concatenate multiple attribute values together or displaying point symbols that are sized according to a population attribute divided by an area attribute, for example.
This implementation will not work in environments where the content security policy disallows the use of the Function constructor. This is the case on browser extensions. A more content-security-policy-friendly implementation would be to come up with a restricted grammar and write a lex/parser. This is the road I started down, but this verison is far less code, and I think the security policy limitations are minor at this point. This version will always be faster/lighter than a parser, so one is written in the future, it should only be pulled in where content security policy mandates it.
Assuming browser garbage collection cannot happen while we have listeners in the global registry, we need to listenOnce to avoid memory leaks with the tile queue.
Instead of going into an unnecessary animation loop, we can simply wait to be notified when tiles load.
The WebGL renderer still sets frameState.animate true, but I think this too should be unnecessary (full page example works without it, side-by-side example shows it cannot yet be removed).
Instead of keeping track of wanted tile ranges, we can instead track wanted tiles individually. This provides enough for the map to know how to prioritize tiles and should be more efficient (no extra calls to extend tile ranges or check for tile containment within a range).