How Safari Ruined My Tuesday

Fly.io runs apps close to users, by transmuting Docker containers into micro-VMs that run on our own hardware around the world. This is a post about a bug in Safari, but if you just want to ship a Phoenix app, the easiest way to learn more is to try it out; you can be up and running in just a couple minutes.

Safari 15 shipped in September 2021 and included an obscure CSS bug that broke most LiveView applications. The LiveView client operates in two modes – connected, when it can talk to the server over websockets, and disconnected when offline. When LiveView disconnects, it's useful to communicate the change to users. CSS includes a handy pointer-events attribute that makes buttons and links flip to non-interactive. Set pointer-events: none, and elements won't get a pointer cursor or visibly respond to clicks.

CSS cascades (it's literally in the name!) so we can make a bunch of elements non-interactive at once by adding this rule, then toggling a .phx-disconnected class on container elements:

.phx-disconnected *{ pointer-events: none; }

This has been part of the default app.css in LiveView apps for three years. So we're some of the first to feel the pain of a Safari 15 bug that makes pointer-events: none stick – forever. It stays none even if you update classes or explicitly overwrite pointer-events. Which means, LiveView stays non-interactive for Safari users even when we reconnect.

In the whole of the internet, there's only one, lonely Stack Overflow thread talking about this issue affecting mobile Safari. And it includes tiny HTML and CSS example reproduction. You can see the bug in action below. The link remains in a none state an clicks aren't registered when we remove the class.

This was an annoyingly difficult issue to debug. Safari's web inspector will tell you the computed property of the elements is pointer-events: auto, but it's lying. Elements won't respond to click events. Bad times. It's like living with IE6 again

There is a workaround for LiveView apps, though. Removing point-events: none; from app.css and carry on. LiveView already ignores on clicks on Phoenix links and buttons when disconnected, so pointer-events: none is mostly redundant. Phoenix LiveView 0.17 works around this issue – by using an entirely different class name for disconnected state.

Sadly, this isn't an elegant breaking change. But it's a fix that allows us to avoid applying the erroneous CSS rules from existing user-land CSS while still supporting custom styling of loading states. It looks like no bug is currently being tracked on the Webkit issue tracker, but the latest Safari Development Preview build might fix the underlying bug. Maybe even accidentally. Safari release cycles are quite slow, though, so a breaking CSS change in LiveView is the best way to reduce the end user impact.

I spent my entire Tuesday tracking this one down. I'll be able to write Elixir again someday, right? 😄 In the meantime, feel free to read a mildly frustrated commit message.