-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
[Note: OP edited after some discussion]
What is the issue with the HTML Standard?
With the introduction of setHTML{Unsafe}, we have multiple ways of applying an HTML string into an existing document:
setHTMLsetHTMLUnsafeinnerHTMLandouterHTMLsetterscreateContextualFragmentinsertAdjacentHTML- Detached document streaming (
document.write()into an inactive document and inserting the root node the active one)
With #2142 and #11542, we are about to introduce even more methods that insert HTML asynchronously using a stream.
Some of these techniques support different levels of sanitation and slightly different behaviors.
So this is a good time to have a wider overview of these methods, and come up with a consistent structure for their API.
Specifically, the following variants apply when inserting HTML:
- Does the HTML have to go through the sanitizer ("safe")
- Does the HTML replace the whole contents of the element or just parts
- Is the HTML passed as a string or as a stream (as per A way to stream content into an element #2142)
- Is the HTML parsed as a whole, or containing interleaved patches (as per Out of order HTML streaming ("patching") #11542)
- How does script execution work
- How this works with
TrustedTypes.
API shape
-Treat unsafe/safe stream/set and replaceChildren/replaceWith/append/prepend/before/after as permutations that are best exposed as different methods (226 = 20 methods). We can tweak this of course
dictionary UnsafeHTMLSetterOptions {
(Sanitizer or SanitizerConfig)? sanitizer = null;
boolean? runScripts = false;
};
dictionary SafeHTMLSetterOptions {
// The user of this dictionary must ensure a sanitizer is provided.
(Sanitizer or SanitizerConfig) sanitizer;
};
[Exposed=Window]
interface ParentNode {
void setHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
void setHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
void beforeHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
void beforeHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
void afterHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
void afterHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
void appendHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
void appendHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
void prependHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
void prependHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
void replaceWithHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
void replaceWithHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
WritableStream streamHTML(SafeHTMLSetterOptions options);
WritableStream streamHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
WritableStream streamBeforeHTML(SafeHTMLSetterOptions options);
WritableStream streamBeforeHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
WritableStream streamAfterHTML(SafeHTMLSetterOptions options);
WritableStream streamAfterHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
WritableStream streamAppendHTML(SafeHTMLSetterOptions options);
WritableStream streamAppendHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
WritableStream streamPrependHTML(SafeHTMLSetterOptions options);
WritableStream streamPrependHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
WritableStream streamReplaceWithHTML(SafeHTMLSetterOptions options);
WritableStream streamReplaceWithHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
};Alternative: 12 methods, with insertion mode (taken from #10122)
enum HTMLInsertionPoint { "before", "after", "start", "end" };
// Hypothetical interface where these methods would live,
// for example, on Element or ShadowRoot.
[Exposed=Window]
interface ParentNode {
void setHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
void setHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
void insertHTML((DOMString or TrustedHTML) html, HTMLInsertionPoint insertionPoint, SafeHTMLSetterOptions options);
void insertHTMLUnsafe((DOMString or TrustedHTML) html, HTMLInsertionPoint insertionPoint, optional UnsafeHTMLSetterOptions options = {});
void replaceWithHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
void replaceWithHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
WritableStream streamHTML(SafeHTMLSetterOptions options);
WritableStream streamHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
WritableStream streamInsertHTML(HTMLInsertionPoint insertionPoint, SafeHTMLSetterOptions options);
WritableStream streamInsertHTMLUnsafe(HTMLInsertionPoint insertionPoint, optional UnsafeHTMLSetterOptions options = {});
WritableStream streamReplaceWithHTML(SafeHTMLSetterOptions options);
WritableStream streamReplaceWithHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
}};Alternative: pass ReadableStream instead of returning a WritableStream:
[Exposed=Window]
interface ParentNode {
Promise<void> streamHTML((ReadableStream or TrustedReadableStream), SafeHTMLSetterOptions options);
// ...
};This is simpler from a trusted types perspective, as the stream has to be trusted as a whole rather than the chunks.
OTOH the API feels a bit less "streamy" than returning a Writable.
Other notes
For the interleaved case, as well as for trusted types, suggesting to do that via transform streams:
- A
TransformStreamthat resolves a raw HTML streams to patches as per Out of order HTML streaming ("patching") #11542 - A
TransformStreamthat allows a trusted types to add a control point in the middle of a stream, as per Streaming support w3c/trusted-types#594
The aforementioned issues include more details about these, but they are designed in a way that shouldn't add more complexity to the normal case.