Created attachment 468769 [details] Deno Server which streams HTML. Run with `deno run --allow-net deno-server-streaming-html.js`. Safari doesn’t seem to be streaming HTML at all, except for `<iframe>` elements. Right now, it appears to buffer all the HTML before rendering anything. To reproduce you will need a web server that supports streaming. The attached example uses Deno (1.38.2). When run locally, it’ll serve HTTP/1.1 and adds the correct `Transport-Encoding: chunked` header. I have a hosted variation of the example which is being served with Deno Deploy: https://shadowroot-streams.deno.dev. It also uses the Declarative Shadow DOM. That example uses HTTP/2 and correctly doesn’t use the `Transport-Encoding` header since it’s incompatible with HTTP/2. In short, Deno’s streaming the content correctly and Deno Deploy supports HTML streaming. Note: the attached example admittedly doesn’t have a tonne of HTML in the first chunk, but that doesn’t seem to matter at all for streaming into an `<iframe>` element. Other browsers are also able to stream this fine.
Created attachment 468770 [details] Source code of the hosted Deno server which streaming to the Declarative Shadow DOM.
<rdar://problem/118837578>
To add to this, the problem is very specific. Safari seems to need about 512bytes of HTML to start streaming, otherwise it will remain buffered as noted here: https://lamplightdev.com/blog/2024/01/10/streaming-html-out-of-order-without-javascript/ *BUT* the problem is even more speific than "512 bytes", Safari seems to want 512 bytes of displayable HTML. That is, any element in the `<head>` is ignored, any element that is default hidden IE: `<script>`, `<template>`, HTML comments, etc are all ignored. Even `<canvas>` with text in it is ignored. The "easiest" way of triggering HTML streaming seems to be with a height / width of 0 div, with no overflow. Like this: ```html <body> <div style="height: 0; width: 0; overflow: hidden;" hidden> {{ 512 bytes of data }} </div> <!-- stream the rest --> </body> ``` This seems to be Safari / WebKit specific and is a pretty big gotcha.
Sorry, `<div hidden>` actually causes it to still buffer. So you need to use aria-hidden instead. ```html <body> <div style="height: 0; width: 0; overflow: hidden;" aria-hidden="true"> {{ 512 bytes of data }} </div> <!-- stream the rest --> </body> ```
As it turns out, the first element in the body doesn't seem to count. So its even worse than that. The above code will not stop buffering. But it will work if you inject it into the shadowroot of the body. Here's the secret incantation Joel Drapper came up with to skirt Safari's HTML streaming. To note, Safari expects 512 characters, and not 512 bytes, so there's 512 escape sequences for zero width white space. ```html <!DOCTYPE html> <html> <head></head> <body> <template shadowrootmode="open"> <span aria-hidden="true" style="-webkit-user-select: none; user-select: none;"> ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ </span> </template> </body> </html> ```