Bug 265386 - Streaming HTML only works in iframe elements. Safari waits to buffer all HTML before rendering.
Summary: Streaming HTML only works in iframe elements. Safari waits to buffer all HTML...
Status: NEW
Alias: None
Product: WebKit
Classification: Unclassified
Component: Layout and Rendering (show other bugs)
Version: Safari 17
Hardware: Mac (Apple Silicon) macOS 13
: P2 Normal
Assignee: Nobody
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2023-11-27 09:03 PST by Nathan Knowler
Modified: 2024-09-25 14:57 PDT (History)
7 users (show)

See Also:


Attachments
Deno Server which streams HTML. Run with `deno run --allow-net deno-server-streaming-html.js`. (743 bytes, text/javascript)
2023-11-27 09:03 PST, Nathan Knowler
no flags Details
Source code of the hosted Deno server which streaming to the Declarative Shadow DOM. (1.85 KB, text/javascript)
2023-11-27 09:05 PST, Nathan Knowler
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Nathan Knowler 2023-11-27 09:03:32 PST
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.
Comment 1 Nathan Knowler 2023-11-27 09:05:10 PST
Created attachment 468770 [details]
Source code of the hosted Deno server which streaming to the Declarative Shadow DOM.
Comment 2 Radar WebKit Bug Importer 2023-11-27 09:39:50 PST
<rdar://problem/118837578>
Comment 3 Konnor Rogers 2024-09-25 14:05:37 PDT
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.
Comment 4 Konnor Rogers 2024-09-25 14:11:44 PDT
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>
```
Comment 5 Konnor Rogers 2024-09-25 14:57:01 PDT
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;">
&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203&#8203
</span>
    </template>
  </body>
</html>
```