You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -13,78 +13,210 @@ For example, React streams content out of order by injecting inline `<script>` t
13
13
14
14
This proposal introduces partial out-of-order HTML streaming as part of the web platform.
15
15
16
-
## Patching
17
-
A "patch" is a stream of HTML content, that can be injected into an existing position in the DOM.
18
-
A patch can be streamed directly into that position using JavaScript, and multiple patches can be interleaved in an HTML document, allowing for out-of-order content as part of an ordinary HTML response.
16
+
## Declarative patching
19
17
20
-
## Anatomy of a patch
18
+
Patches are delivered using a `<template>` element with the `contentmethod` attribute and target an existing elements in the DOM with the `contentname` attributes. These patches require no scripts to apply (are declarative) and can appear in the main response HTML to support out-of-order streaming.
21
19
22
-
A patch is a [stream](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) that targets a [parent node](https://developer.mozilla.org/en-US/docs/Web/API/Node) (usually an element, but potentially a shadow root).
23
-
It can handle strings, bytes, or `TrustedHTMLString`. When it receives bytes, it decodes them using UTF8.
24
-
Anything other than strings or bytes is stringified.
20
+
Patches can be be applied later in the page lifecycle using JavaScript, see [script-initiated patching](#script-initiated-patching).
25
21
26
-
When a patch is active, it is essentially a [WritableStream](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) that feeds a [fragment-mode parser](https://html.spec.whatwg.org/multipage/parsing.html#html-fragment-parsing-algorithm) with strings from that stream.
27
-
Unlike the usual fragment parser, nodes are inserted directly into the target and not buffered into the fragment first. The fragment parser is only used to set up the parser context.
28
-
It is similar to calling `document.write()`, scoped to an node.
22
+
### Proposed markup
29
23
30
-
## One-off patching
24
+
The `contentname` attribute is used to identify an element which can be patched:
31
25
32
-
The most atomic form of patching is opening a container node for writing, creating a `WritableStream` for it.
- Streams do not abort each other. It is the author's responsibility to manage conflicts between multiple streams.
41
-
- Unlike contextual fragments, when `runScripts` is true, classic scripts in the stream can block the parser until they are fetched. This makes the streaming parser behave more similarly to the main parser.
42
-
- Only the unsafe variant can run scripts.
43
-
- This describes `streamHTML`, but also `streamAppendHTML`, `streamPrependHTML`, `streamBeforeHTML`, `streamAfterHTML`, and `streamReplaceWithHTML` variants are proposed.
30
+
The content is then patches using a `<template>` element:
44
31
45
-
To account for HTML sanitation, this API would have an "Unsafe" version and would accept a sanitizer in its option, like [`setHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML):
Since user-space sanitizers like DOMPurify are not well suited for streaming, TrustedTypes only allows streaming with either sanitation or by giving it a "free pass", by blessing parser options:
52
-
```js
53
-
// This would fail if there is a default policy with `createHTML`
The element name (`section`) needs to be repeated so that children are parsed correctly, but only the child nodes are actually replaced in this example.
39
+
40
+
There are two proposed `contentmethod` values:
41
+
42
+
-`append` inserts nodes at the end of the element, similar to `element.append(nodes)`.
43
+
-`replace-children` replaces any existing child nodes, similar to `element.replaceChildren(nodes)`.
44
+
45
+
At a low level, the only difference is that is `replace-children` removes existing nodes and then appends new nodes, while `append` only appends new nodes.
46
+
47
+
A few details about interleaved patching:
48
+
- Templates with a valid `contentmethod` are not attached to the DOM.
49
+
- If the patching element is not a direct child of `<body>`, the outlet has to have a common ancestor with the patching element's parent.
50
+
- The patch template has to be in the same tree (shadow) scope as the outlet.
51
+
52
+
See the https://github.com/whatwg/html/pull/11818 for the full processing model and details.
53
+
54
+
### Interleaved patching
55
+
56
+
An element can be patched multiple times and patches for different elements can be interleaved. This allows for updates to different parts of the document to be interleaved. For example:
55
57
56
-
// This would "bless" the parser options for streaming.
Also see detailed discussion at https://github.com/whatwg/html/issues/11669.
63
+
In this example, the search results populate in three steps while the product carousel populates in one step in between:
61
64
62
-
## Interleaved patching
65
+
```html
66
+
<templatecontentmethod=replace-children>
67
+
<divcontentname=search-results>
68
+
<p>first result</p>
69
+
</div>
70
+
</template>
63
71
64
-
In addition to invoking streaming using script, this proposal includes patching interleaved inside HTML content. A `<template>` would have a special attribute that
65
-
parses its content as raw text, finds the target element using attributes, and reroutes the raw text content to the target element:
A few variations to support interleaved patching have been considered:
92
+
93
+
##### Automatic defaults
94
+
95
+
To remove children the first time an element is targeted, and to append if it is targeted again within the same parser invocation. In this alternative, the opt-in to patching would be a boolean attribute like `contentupdate` on `<template>`, and `contentmethod` is only used to override the default.
96
+
97
+
<details>
98
+
<summary>Example</summary>
99
+
100
+
The patches use `contentupdate` instead of `contentmethod`:
- Templates with a valid `contentmethod` are not attached to the DOM.
76
-
- If the patching element is not a direct child of `<body>`, the outlet has to have a common ancestor with the patching element's parent.
77
-
- The patch template has to be in the same tree (shadow) scope as the outlet.
78
-
- `contentmethod` can be `replace-children`, or `append`. `replace-children` is the basic one that allows replacing a placeholder with its contents,
79
-
while `append` allows for multiple patches that are interleaved in the same HTML stream to accumulate.
80
-
- Interleaved patching works together with one-off patching. When a `<template contentmethod>` appears inside a stream, it is applied, resolving `contentname` from the stream target.
125
+
</details>
126
+
127
+
(For an append-only use case, `contentmethod` would still be needed in addition to `contentupdate`.)
128
+
129
+
##### Range markers
130
+
131
+
Don't support `contentmethod=append` and instead support this use case using [markers](#streaming-to-non-element-ranges). To "append", target two markers with no content between them are used. For multiple appends, each patch would need to insert an additional marker for the next patch to target.
Similar to above, but instead of the `contentmarkerstart` and `contentmarkerend` attributes, a single marker node and the `contentmarkerstartbefore` attribute is used to define a range starting before the node and implicitly ending at the end of the container element. For multiple appends, each patch would need to insert a new marker at the end, but it could have the same name as the replaced marker.
`streamHTMLUnsafe()` is being pursued as a [separate proposal](https://github.com/whatwg/html/issues/2142), but will also work with patching. When `<template contentmethod>` appears in the streamed HTML, those patches can apply to descendants of element on which `streamHTMLUnsafe()` was called.
210
+
211
+
## Potential enhancement
212
+
213
+
### Avoiding overwriting with identical content
83
214
84
215
Some content might need to remain unchanged in certain conditions. For example, displaying a chat widget in all pages but the home, but not reloading it between pages.
85
216
For this, both the outlet and the patch can have a `contentrevision` attribute. If those match, the content is not applied.
86
217
87
-
## Potential enhancement - streaming to non-element ranges
218
+
### Streaming to non-element ranges
219
+
88
220
See discussion in https://github.com/WICG/declarative-partial-updates/issues/6 and https://github.com/WICG/webcomponents/issues/1116.
89
221
90
222
It has been a common request to stream not just by replacing the whole contents of an element or appending to it, but also by replacing an arbitrary range.
@@ -96,31 +228,32 @@ To achieve these use cases, the direction is to use addressable comments as per
96
228
Very initial example:
97
229
98
230
```html
99
-
<table contentname="data">
100
-
<tr><td>static data
101
-
<tr><td>static data
102
-
103
-
<?marker name=dyn-start?>
104
-
<tr><td>dynamic data 1
105
-
<tr><td>dynamic data 2
106
-
<?marker name=dyn-end?>
231
+
<table>
232
+
<tbodycontentname=data>
233
+
<tr><td>static data</td></tr>
234
+
<tr><td>static data</td></tr>
235
+
236
+
<?marker name=dyn-start?>
237
+
<tr><td>dynamic data 1</td></tr>
238
+
<tr><td>dynamic data 2</td></tr>
239
+
<?marker name=dyn-end?>
240
+
</tbody>
107
241
</table>
108
242
109
243
<!-- stuff.... -->
110
244
111
245
<!-- This would replace the children only between the dyn-start and dyn-end markers, leaving the static data alone. -->
0 commit comments