Hacker News new | past | comments | ask | show | jobs | submit login
Safari Technology Preview Release Notes (developer.apple.com)
87 points by clairity 15 days ago | hide | past | favorite | 48 comments

Looks like there's some good performance gains here as well. Safari has traditionally been faster on the benchmarks, but recently Chrome has overtaken it.

This Preview puts Safari in the lead again.

Speedometer 2.0 (https://browserbench.org/Speedometer2.0/) on my M1 Air:

Safari 15.4: 258 runs/min Chrome 101.0.4951.64: 264 runs/min Safari Tech Preview 15.4 (release 145): 307 runs/min

For anyone curious, there are a ton of selectors here that you may have never heard of before here: https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-clas...

while many of those other pseudo-classes are useful, :has() is a next level of usefulness, as are :is()/:where() and :not(). combining these together gives you far-reaching selector capabilities.

safari also now has lch colors and the color() function, which make theming more flexible.

and the color-contrast() function just landed in firefox beta!

edit: subgrid in firefox is pretty nifty too.

I don't think I've ever been excited for a CSS feature as much as I am for `:has` and `:where`!

I see mention of Media Source Extensions (MSE) in these release notes. Good to see they're improving it, I guess. But my understanding [1] is that while Safari on Desktop and iPad support MSE, Safari on iPhone does not. Does anyone know why this restriction exists and if it's likely to be removed? I'd expect these to be substantially the same codebase so the restriction confuses me.

I saw folks speculating about business reasons for this restriction, which confuses me. It seems like HLS provides broadly similar functionality, so I'm not sure how many determined folks are entirely stopped from doing something by Apple's technology choice, or steered toward a native app, or whatever Apple supposedly wants instead. This just makes things unnecessarily painful for small projects.

[1] backed up by https://caniuse.com/mediasource

I'd put down a lot of money on this being an artificial limitation, and not a technical issue.

If I had to guess, it is that they have hyper-optimized the pipeline for HLS for battery consumption. Streaming video is one of the most power hungry things people do with their phone that laymen often don't think is power hungry. (Unlike say 3d games, or GPS).

If the MSE path is less optimized (and it is, simply by nature of having JavaScript in the loop handling the video segments), which drains the battery faster, but sites like Twitch, Youtube, and Facebook switch to using MSE, then now the iPhone battery life numbers look worse.

Fair point; I can see them making the choice for that reason. But I'm not sure it's actually true that HLS is more optimal:

* On the one hand, the MSE path makes all the data flow through Javascript. At least that means chunks must be garbage-collected. I can see it meaning an extra copy or two also.

* On the other hand, doesn't HLS require polling and extra round trips? You fetch the .m3u8 manifest repeatedly, and then fetch the media segments it indicates. If you want lowest possible latency, well, it's 1.5 trips higher than necessary, as well as requiring extra network requests (costing battery for wake-ups and a bit of bandwidth/radio time). As compared to a WebSocket where the server pushes segments as soon as they're available.

In an application of mine, I've thought about writing web worker code to basically create a HLS API for my service from the real, WebSocket-based API. Then it'd work on iPhone. Of course, this means everything would flow through Javascript, totally defeating what advantage HLS has and requiring (device-internal, fortunately) polling.

> As compared to a WebSocket where the server pushes segments as soon as they're available.

This didn’t become a requirement for LL-HLS according to this blog post, but you could build the same push technique using HLS and HTTP/2 to push data to the client before it needs to ask for it: https://www.theoplayer.com/blog/impact-of-apple-ll-hls-updat... (or as suggested in the post, provide hint URLs for where the next block will be…)

Apple’s official docs on LL-HLS: https://developer.apple.com/documentation/http_live_streamin...

Edit: the full spec is at https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc82... and doesn’t appear to mention push directly but does require HTTP2. https://www.akamai.com/blog/performance/-using-ll-hls-with-b... Mentions more details but I don’t have time to read them now.

I expect folks are looking at this very closely, but the current protocol obviously lets the client be in full control over what gets sent, which would be somewhat lost if relying exclusively on HTTP2 server-initiated push… For example, clients can request separate caches of low and high bitrate media to always ensure they have something to play back in time…

Oh, I missed EXT-X-PRELOAD-HINT, [1] thanks. That seems like a significant improvement, in that the client can do a hanging GET and have the server respond when the media segment exists, avoiding that transit latency. Still has to send extra requests. (I guess the silver lining there is that this is a flow control mechanism, so the server won't keep sending more data if the client isn't keeping up with previous data. My custom WebSocket protocol doesn't really do flow control right now, beyond what TCP does.)

[edit: another problem: multiple simultaneous hanging GETS are really awkward with HTTP/1.1 due to connection limits. You have to shard the domain name (which causes problems with cookie auth). Or you really need to use HTTP/2 or HTTP/3.]

I'm really confused by the HTTP/2 push thing that article mentioned. My general understanding of HTTP/2 push is that the clients implement it by adding the pushed data to their cache and nothing more. (Or don't implement it at all. IIRC Chrome dropped support.) If the client later needs that URL, it loads more quickly. But there's no JS API for doing something as soon as a push is received. Maybe Safari has a special hook in their HLS implementation that bypasses the polling delay if the media segment is pushed, but if so, I don't know where that's documented, and it's a surprising behavior. Or maybe they gave up on the idea? Dunno.

The whole HLS spec seems arcane compared to a protocol in which the server pushes data over a WebSocket. Even MSE is more complicated than I'd like. I'd rather try WebCodecs, but only Chrome supports it AFAIK. [2]

I suppose the cool kids use WebRTC for live video instead, but my use case is an NVR, where I'd like a uniform way of handling playback and live. I don't think WebRTC really does playback.

[1] https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc82...

[2] https://github.com/Fyrd/caniuse/issues/5641

> where I'd like a uniform way of handling playback and live

Seems to me that WebRTC and HLS solve two different problems though. WebRTC largely prefers dropping packets to stay real-time while HLS buffers and preserves every frame by default rather than drop frames to stay real-time. One is designed for calls (real-time) and one is not (streaming).

That’s why HLS seems over-complicated. It’s not designed around real-time signalling, instead it’s designed around making requests for frames/bytes effectively sequentially.

Now, if you’re not distributing requests at scale, HLS is indeed overkill. But if you might have latency and want to stream uninterrupted video footage, it’s a necessary evil. If you pick WebRTC, you’ve no easy way to ask to pick up where you left off, because the default is just a stream of “real-time now” packets and dropped packets are lost forever.

MSE would be a way of capturing packets, but if the protocol doesn’t let you sequentially access bytes starting from a timestamp, you’re stuck when trying to resume a stream, no?

I might have misunderstood something, but they do seem like they serve different purposes. :)

> WebRTC largely prefers dropping packets to stay real-time

In my experience, this doesn't work as well as people say. I haven't played with WebRTC yet, but my understanding is it's based on RTP over an unreliable transport, which I am familiar with. Dropping packet by packet (no mechanism to either retransmit the dropped packet or skip sending the rest of the packets in the frame) isn't great. When you lose one packet, you lose a full frame but waste bandwidth sending the data anyway. Worse, it's usually a reference frame, so all the following frames are suspect until the next IDR frame. RTP over TCP (interleaved RTSP channels) can be better, by retransmitting lost packets belonging to a frame once you've decided to send that frame, and by skipping whole frames at once when things fall behind (observed within the application as insufficient buffer space). TCP has more rapid feedback, too. (ACKs are far more frequent than RTCP RRs.)

> while HLS buffers and preserves every frame by default rather than drop frames to stay real-time. One is designed for calls (real-time) and one is not (streaming). That’s why HLS seems over-complicated. It’s not designed around real-time signalling, instead it’s designed around making requests for frames/bytes effectively sequentially.

Sure, and the WebSocket protocol I mentioned also can preserve every frame, more simply.

The most justifiable complexity IMHO in HLS is multiple encodings (variants) of different quality so you can switch between them. But not everyone needs/wants that, or trusts the user agent to do a good job with the selection.

> MSE would be a way of capturing packets, but if the protocol doesn’t let you sequentially access bytes starting from a timestamp, you’re stuck when trying to resume a stream, no?

MSE lets you specify your own protocol. Mine [1] lets you seek to arbitrary timestamps. It has its own complexity (around clock uncertainty/drift due to cheap/unreliable hardware setups) but will be more complex if I have to deal with HLS's complexity also.

[1] https://github.com/scottlamb/moonfire-nvr/blob/master/design...

They also still don’t support WebM, VP8 or 9, or Opus on iOS, to my knowledge. I’m pretty sure there is hardware support, plus desktop Safari does, but for some unknown reason, they continue to force parent encumbered codecs only in Safari iOS.

Not sure if that could theoretically be related to MSE being missing, but maybe it’s in a similar vein of control.

What specifically is not supported on iPhones?

MSE is entirely unsupported on iPhones. window.MediaSource is undefined. https://github.com/scottlamb/moonfire-nvr/issues/121

> Removed the 1ms minimum for setTimeout

This one is big - it finally means there are no arbitrary delays when using setTimeout to add something to the next iteration of the javascript main event loop.

There is queueMicrotask (https://developer.mozilla.org/en-US/docs/Web/API/queueMicrot...) to queue onto the end of the current iteration and you can use an `await 0;` to cycle the event loop as well.

Never knew this limitation existed

only in Safari evidently, so it's really a bug fix on their part.

Until very recently it was a thing in all browsers, with a variety of lower bounds depending on which browser. It was meant to prevent accidental busy-loops when developers forgot the set the time parameter IIRC.

hmm, I can't recall ever noticing it but probably I didn't because it didn't come up or more likely because if I want immediate I explicitly set 0, but anyway the ticket https://trac.webkit.org/changeset/291998/webkit/ says

>This patch removes the 1ms minimum for setTimeout. The HTML spec makes no mention of such a minimum, and Firefox and Chrome do not enforce this minimum

so I guess it was other browsers with the varying minimums, or these two removed it earlier.

A bit OT, but setTimeout this reminds me of something I've wondered about.

Suppose you want to make some DOM change, and then when that change has actually been rendered so that the user can see it you want to do something else.

How do you actually wait for a DOM change to show up?

I first ran into this many years ago when I wanted clicking on a table column header to change the sorting of the table with the sort happening client side. The table was big enough that it could take a few seconds to sort, and this was before JavaScript had any true asynchronous computing mechanisms so the page would be unresponsive during the sort. I wanted to change some text near the table to "Sorting...please wait", do the sort, then clear the text.

A Stack Overflow answer said to use setTimeout of 0, and do the sort in the callback. In the pseudocode below, indentation means something done in the callback of the last function above with less indentation and the callback parameter will be omitted in the function that sets the callback.

So that Stack Overflow answer said do this.

It said that browsers all special cased 0 timeout and took it to mean run when the browser finishes all current pending processing, which included all DOM changes that JavaScript had made but that have not yet rendered.

I tested it on all the major browsers back then, on Windows, Linux, and Mac, and it indeed worked. Great.

But then a few years later I needed it again...and this time it did not work. Timeout 0 callbacks in Firefox were now always running the sort before the DOM updated. I tried double timeout 0:

That sometimes did the trick but sometimes did not.

Stack Overflow and some Googling suggested that the idea should still be sound, but rather than setTimeout one should use window.requestAnimationFrame (rAF) or window.requestIdleCallback (rIC).

On Firefox, those gave similar results to the double timeout 0. On Chrome, where single timeout 0 still worked, rIC worked the same as timeout 0, but rAF seemed to always run the callback code before the DOM update. On Safari timeout 0 and rAF worked, and Safari at the time did not have rIC. On Edge, both timeout 0 and rAF seemed to work, and edge did not have rIC.

I then tested double rAF and double rIC. Both of those seemed reliable on Firefox and Chrome. And since Safari, and Edge all have rAF, and even single rAF seems to work there, double rAF works on them too.

So, as of about 4 years ago when I last looked into this this seems the way to update the DOM, wait for the update to be visible, and then do something:

      // DOM changes are visible
But is this actually guaranteed? Or just an accident of current implementations?

PS: here's a test page to play with this: https://pastebin.com/n26iVs7d The various buttons in the bottom section increment the count shown at the top then do a slow task. If it takes a second for the count to update then the slow task was not deferred until after the DOM update.

>Suppose you want to make some DOM change, and then when that change has actually been rendered so that the user can see it you want to do something else.

>How do you actually wait for a DOM change to show up?

I believe that use case would be covered by MutationObserver[0].

[0] https://developer.mozilla.org/en-US/docs/Web/API/MutationObs...

I don't think so. MutationObserver makes no guarantee that the changes have rendered to the screen, just that the DOM tree has changed.

Double requestAnimationFrame will still work and is the technique used by the WPT repository (a test suite shared by all major browsers): https://source.chromium.org/chromium/chromium/src/+/main:thi...

It makes sense, in theory the message should paint in the first animation frame but if you got in there before it you may sort before that happens. So you are requesting to wait for the frame after that. I'm sure there's some way it's not "guaranteed" because it seems nothing in front end is guaranteed without some asterisks, but this is the most logically correct solution I've seen.

Invoking `getComputedStyle` should also do the trick: https://developer.mozilla.org/en-US/docs/Web/API/Window/getC...

getComputedStyle causes a layout to occur, but doesn't guarantee that rendering is actually done.

I wish outline could respect border-radius like in other browsers.

This has been a known bug for 14 (FOURTEEN) years (https://bugs.webkit.org/show_bug.cgi?id=20807). I have lost hope at this point.

Don't know if they fixed the buggy border-radius on videos either.

I didn't realize Safari already had support for cascade layers. I'm really excited for the reduction in specificity that will allow once frameworks and themes get on board.

i've been using :where() to reduce specificity in recent projects. it's not quite the same as cascade layers, but provides a lot of benefit now for modern browsers (support goes back about 2 years).

I can only hope Apple actually ships these Preview additions to main Safari; since they seem to have a habit of of not doing so.


Excited to finally have a parent selector, but this feels like a major footgun that will promote brittle DOM specific CSS.

:has() sounds amazing on the surface. I’d be interested in a good advantages / disadvantages overview if anyone :has() a link.

Edit: Useful: https://caniuse.com/?search=has

there are a bunch of good articles out there on :has() now, like this one: https://www.smashingmagazine.com/2021/06/has-native-css-pare...

i've been experimenting with it on safari tech preview, and it really lets you do many things that were previously impossible or unacceptably brittle. i love it.

same with container queries. have been experimenting, it's really nice compared to page grids and breakpoint hell.

Ohhh, I didn't know that container queries are finally being implemented. I'd given up on them as something that would be great but didn't look like they were going to happen.

it's extremely useful for defining ublock origin rules

So far I've only used it in TamperMonkey. Between your comment and culturestate's[0], you may be on to something.

[0] https://news.ycombinator.com/item?id=31369223

> this feels like a major footgun that will promote brittle DOM specific CSS

I can’t say I’ve really considered this in any depth, but my very first thought when I saw :has() was “finally, I can fix those last stupid bugs in my HN userstyle.”

So…you may be on to something.

I wish there was a convention to favor paragraphs over sentences in release notes.

Understanding most of these release notes requires an extremely deep understanding of WebKit and the relevant web standards.

I'd love to see projects invest more effort in expanding these out. Imagine how much more valuable this document would be if each of the bullet points (or just the most important bullet points) had a couple more sentences clarifying what it meant.

(This is one of the reasons I've started trying to publish annotated versions of the release notes for my own projects: https://simonwillison.net/tags/annotatedreleasenotes/ )

I completely agree, but as someone who's owned publishing for changelogs on large-ish, often super technical OSS projects, it can be very difficult to explain some of the concepts needed to grok a bugfix or low-level design change even in the span of a couple paragraphs. And more importantly, that context needs to be written by the engineer involved in the change, which means more context == less code work.

That being said, I think some projects do it extremely well. My gold standard for a while has actually been the OSS Dolphin emulator for Gamecube/Wii. They write incredible progress reports[1] every 1-2 months that give phenomenal context on how they found the bug, why it matters, how it works, and what they did to fix it.

Another great example is VS Code[2]. Their context tends to be around changes that are more UX-oriented, but it's still a great way to understand what new value you're getting on every new release.

[1]: https://dolphin-emu.org/blog/2022/02/08/dolphin-progress-rep...

[2]: https://code.visualstudio.com/updates/v1_67

The VS Code release notes are great. I always read through them, and reading about a new experimental feature I can enable if I want to try it out always gets me a bit excited. Why can't Windows put those release notes up-front-and-centre when it continually nags me to update?

Oh wow, yeah the VS Code release notes really are superb.

Check out the TypeScript release notes if you like extensively documented notes :-)

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact