Skip to content

Commit cf3b548

Browse files
authored
Merge pull request #76 from WICG/noamr-patch-5
Create "dynamic markup" explainer
2 parents ab76fca + 041fed8 commit cf3b548

File tree

1 file changed

+264
-0
lines changed

1 file changed

+264
-0
lines changed
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
# Dynamic markup - revamped
2+
3+
## Overview
4+
The web platform has multiple various ways to dynamically inject HTML into an existing document using script:
5+
- `setHTML`
6+
- `setHTMLUnsafe`
7+
- `innerHTML` and `outerHTML` setters
8+
- `createContextualFragment`
9+
- `insertAdjacentHTML`
10+
11+
Additionally, there is emerging work to allow injecting markup via stream, with the `streamHTML` and `streamHTMLUnsafe` methods. See https://github.com/whatwg/html/issues/2142.
12+
13+
These methods all have explicit and implicit knobs and consideration:
14+
- What is the insertion point (replace children? append? etc)
15+
- Synchronous vs. streaming
16+
- [Safe vs. unsafe](https://wicg.github.io/sanitizer-api/#safe-and-unsafe)
17+
- Is there a sanitizer?
18+
- Do scripts run? If so, when?
19+
- Relationship with trusted types.
20+
- Element creation side-effects (e.g. image preloading).
21+
22+
The purpose of this explainer is to set a coherent way forward with dynamic markup insertion, in a way that takes all of these considerations into account while remaining consistent in terms of API.
23+
24+
## API design
25+
26+
Following API decisions in the DOM spec, the direction of the APIs here is to expose separate methods for the following permutations:
27+
- Synchronous vs. streaming, as those have different arguments and return values
28+
- Insertion point (replaceChildren, replaceWith, before, after, append, prepend),
29+
as making a "positional" argument doesn't add much to readability and discoverability.
30+
- Safe vs. unsafe, to have the differences explicit when looking at call sites.
31+
32+
The following are optional (or implicit):
33+
- Sanitizer
34+
- Do scripts run
35+
36+
## Script Execution
37+
38+
### `runScripts`
39+
40+
Currently, only `createContextualFragment` is capable of running scripts in dynamic markup.
41+
The scripts are executed after the markup is inserted. Unlike regular parsing, classic external scripts are not
42+
parser-blocking, as it's awkward to block a synchronous call on an asynchronous fetch.
43+
44+
The new proposal here is to add a `{runScripts: boolean}` option, false by default, to the `SetHTMLUnsafeOptions` dictionary.
45+
This would allow any unsafe HTML setter to run scripts in a similar fashion to `createContextualFragment`.
46+
47+
Note that `runScripts` is not available for safe dynamic markup injection.
48+
49+
### Scripts & streaming
50+
51+
For streaming, the processing model would be different, as the streaming parser is not a synchronous call.
52+
In the streaming case, script execution would behave more like the main parser, where classic scripts block furher parsing,
53+
and scripts with `defer` (including `module` scripts) are executed when the stream is closed and the parser finishes
54+
processing the entire markup.
55+
56+
## Sanitizer integration
57+
58+
Both safe and unafe variants can receive a `sanitizer` option. As per the sanitizer spec,
59+
the safe variants ensure that the sanitizer config has a few baseline features. See https://wicg.github.io/sanitizer-api/.
60+
61+
## Trusted types integration
62+
63+
The current API for trusted types policies rely on transforming HTML strings before they are passed to the parser.
64+
This is incompatible with how the sanitizer works, and also doesn't work well with streaming, as userspace sanitation
65+
libraries such as `DOMPurify` would have to support streaming as well.
66+
67+
The proposal is that trusted types would be able to participate in a flow that involves streaming and/or sanitizer
68+
by transforming or "blessing" a parser options dictionary ([`SetHTMLOptions`](https://wicg.github.io/sanitizer-api/#dictdef-sethtmloptions) or [`SetHTMLUnsafeOptions`](https://wicg.github.io/sanitizer-api/#dictdef-sethtmlunsafeoptions)):
69+
70+
```webidl
71+
interface TrustedTypePolicy {
72+
TrustedHTMLParserOptions createHTMLParserOptions((SetHTMLOptions or SetHTMLUnsafeOptions) options = {});
73+
}
74+
```
75+
76+
By providing a method as such, the policy can:
77+
- Modify a sanitizer, or inject one if it doesn't exist
78+
- Change the `runScripts` option
79+
- Bless the options as-is, e.g. to allow first-party scripts to inject unsafe and non-sanitized markup.
80+
81+
Passing a (non-fungible) `TrustedHTMLParserOptions` to one of the HTML setting/streaming methods would bypass the default policy,
82+
and unlike `createHTML`, would also allow streaming.
83+
If this method is provided in the default policy, it would transform any incoming options, after also going through the `createHTML` call.
84+
(Alternatively, they can be mutually exclusive, using `createHTML` as graceful degradation).
85+
86+
See [discussion](https://github.com/w3c/trusted-types/issues/594).
87+
88+
## Node-creation side effects
89+
90+
Currently, `createContextualFragment` has a somewhat quirky side-effect of preloading images, even before the fragment is connected.
91+
See https://github.com/whatwg/html/issues/12010.
92+
93+
This is intended to stay as a quirk specific to `createContextualFragment`, as no other API separates between the fragment creation and insertion.
94+
95+
## Special template behavior
96+
97+
Some new features such as declarative shadow DOM and out-of-order streaming allows template elements to be "active" and have a side effect when encountered.
98+
This is another difference between APIs, as older APIs might rely on userspace sanitizers that don't know about the existence of these features.
99+
100+
Open issue: define how this should behave going forward.
101+
102+
## Resulting API
103+
104+
This results in the following API, which includes 24 methods:
105+
106+
```webidl
107+
enum SanitizerPresets { "default" };
108+
dictionary SetHTMLOptions {
109+
(Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = "default";
110+
};
111+
dictionary SetHTMLUnsafeOptions {
112+
(Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = {};
113+
boolean runScripts = false;
114+
};
115+
116+
interface TrustedSetHTMLOptions {
117+
(Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer;
118+
}
119+
120+
interface TrustedSetHTMLUnsafeOptions {
121+
(Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer;
122+
boolean runScripts;
123+
}
124+
125+
typedef (SetHTMLUnsafeOptions or TrustedHTMLParserOptions) UnsafeHTMLSetterOptions;
126+
typedef (SetHTMLOptions or TrustedHTMLParserOptions) SafeHTMLSetterOptions;
127+
128+
[Exposed=Window]
129+
mixin interface ElementOrShadowRoot {
130+
void setHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
131+
void setHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
132+
void beforeHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
133+
void beforeHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
134+
void afterHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
135+
void afterHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
136+
void appendHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
137+
void appendHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
138+
void prependHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
139+
void prependHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
140+
void replaceWithHTML((DOMString or TrustedHTML) html, SafeHTMLSetterOptions options);
141+
void replaceWithHTMLUnsafe((DOMString or TrustedHTML) html, optional UnsafeHTMLSetterOptions options = {});
142+
WritableStream streamHTML(SafeHTMLSetterOptions options);
143+
WritableStream streamHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
144+
WritableStream streamBeforeHTML(SafeHTMLSetterOptions options);
145+
WritableStream streamBeforeHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
146+
WritableStream streamAfterHTML(SafeHTMLSetterOptions options);
147+
WritableStream streamAfterHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
148+
WritableStream streamAppendHTML(SafeHTMLSetterOptions options);
149+
WritableStream streamAppendHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
150+
WritableStream streamPrependHTML(SafeHTMLSetterOptions options);
151+
WritableStream streamPrependHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
152+
WritableStream streamReplaceWithHTML(SafeHTMLSetterOptions options);
153+
WritableStream streamReplaceWithHTMLUnsafe(optional UnsafeHTMLSetterOptions options = {});
154+
};
155+
```
156+
157+
## Existing methods
158+
159+
Apart from the `createContextualFragment` quirk and special template behavior like declarative shadow roots,
160+
all of the existing APIs can be expressed in terms of the above APIs,
161+
implicitly being unsafe, having a false `runScripts` and no sanitizer:
162+
163+
```js
164+
class Element {
165+
set innerHTML(html) {
166+
this.setHTMLUnsafe(html);
167+
}
168+
169+
set outerHTML(html) {
170+
this.replaceWithHTMLUnsafe(html);
171+
}
172+
173+
insertAdjacentHTML(html, insertion_point) {
174+
switch (insertion_point) {
175+
case "beforebegin":
176+
this.beforeHTMLUnsafe(html);
177+
break;
178+
case "afterbegin":
179+
this.prependHTMLUnsafe(html);
180+
break;
181+
case "beforeend":
182+
this.appendHTMLUnsafe(html);
183+
break;
184+
case "afterend":
185+
this.afterHTMLUnsafe(html);
186+
break;
187+
}
188+
}
189+
};
190+
191+
```
192+
193+
## Security & Privacy Questionnaire
194+
195+
1. What information does this feature expose, and for what purposes?
196+
It does not expose new information.
197+
198+
2. Do features in your specification expose the minimum amount of information necessary to implement the intended functionality?
199+
N/A
200+
201+
3. Do the features in your specification expose personal information, personally-identifiable information (PII), or information derived from either?
202+
No
203+
204+
4. How do the features in your specification deal with sensitive information?
205+
N/A
206+
207+
5. Does data exposed by your specification carry related but distinct information that may not be obvious to users?
208+
209+
No
210+
211+
6. Do the features in your specification introduce state that persists across browsing sessions?
212+
No
213+
214+
7. Do the features in your specification expose information about the underlying platform to origins?
215+
No
216+
217+
8. Does this specification allow an origin to send data to the underlying platform?
218+
No
219+
220+
9. Do features in this specification enable access to device sensors?
221+
No
222+
223+
10. Do features in this specification enable new script execution/loading mechanisms?
224+
Yes, and this is handled specifically and deliberately by integrating with the sanitizer, trusted types, and the `runScripts` option.
225+
226+
11. Do features in this specification allow an origin to access other devices?
227+
No.
228+
229+
12. Do features in this specification allow an origin some measure of control over a user agent's native UI?
230+
No.
231+
232+
13. What temporary identifiers do the features in this specification create or expose to the web?
233+
N/A
234+
235+
14. How does this specification distinguish between behavior in first-party and third-party contexts?
236+
It integrates with trusted types. The 1st party can create separate trusted-types policies for 1st party and 3rd party contexts.
237+
238+
15. How do the features in this specification work in the context of a browser’s Private Browsing or Incognito mode?
239+
N/A
240+
241+
16. Does this specification have both "Security Considerations" and "Privacy Considerations" sections?
242+
It is intended to be part of the HTML standard, so yes.
243+
244+
117. Do features in your specification enable origins to downgrade default security protections?
245+
No
246+
247+
18. What happens when a document that uses your feature is kept alive in BFCache (instead of getting destroyed) after navigation, and potentially gets reused on future navigations back to the document?
248+
Nothing in particular.
249+
250+
19. What happens when a document that uses your feature gets disconnected?
251+
Being connected/disconnected doesn't affect this feature atm.
252+
253+
20. Does your spec define when and how new kinds of errors should be raised?
254+
It will.
255+
256+
21. Does your feature allow sites to learn about the user's use of assistive technology?
257+
No
258+
259+
22. What should this questionnaire have asked?
260+
Does this feature allow new ways of changing the DOM/injecting HTML.
261+
262+
263+
264+

0 commit comments

Comments
 (0)