-
Notifications
You must be signed in to change notification settings - Fork 9
Create "dynamic markup" explainer #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
8addfee
Create "dynamic markup" explainer
noamr 4373b2a
Update dynamic-markup-revamped-explainer.md
noamr 9b32ce8
Update dynamic-markup-revamped-explainer.md
noamr 9cc3876
Update dynamic-markup-revamped-explainer.md
noamr 027606d
Enhance dynamic markup explainer with new features
noamr 041fed8
Update UnsafeHTMLSetterOptions and SafeHTMLSetterOptions
noamr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,264 @@ | ||
| # Dynamic markup - revamped | ||
|
|
||
| ## Overview | ||
| The web platform has multiple various ways to dynamically inject HTML into an existing document using script: | ||
| - `setHTML` | ||
| - `setHTMLUnsafe` | ||
| - `innerHTML` and `outerHTML` setters | ||
| - `createContextualFragment` | ||
| - `insertAdjacentHTML` | ||
|
|
||
| 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. | ||
|
|
||
| These methods all have explicit and implicit knobs and consideration: | ||
| - What is the insertion point (replace children? append? etc) | ||
| - Synchronous vs. streaming | ||
| - [Safe vs. unsafe](https://wicg.github.io/sanitizer-api/#safe-and-unsafe) | ||
| - Is there a sanitizer? | ||
| - Do scripts run? If so, when? | ||
| - Relationship with trusted types. | ||
| - Element creation side-effects (e.g. image preloading). | ||
|
|
||
| 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. | ||
|
|
||
| ## API design | ||
|
|
||
| Following API decisions in the DOM spec, the direction of the APIs here is to expose separate methods for the following permutations: | ||
| - Synchronous vs. streaming, as those have different arguments and return values | ||
| - Insertion point (replaceChildren, replaceWith, before, after, append, prepend), | ||
| as making a "positional" argument doesn't add much to readability and discoverability. | ||
| - Safe vs. unsafe, to have the differences explicit when looking at call sites. | ||
|
|
||
| The following are optional (or implicit): | ||
| - Sanitizer | ||
| - Do scripts run | ||
|
|
||
| ## Script Execution | ||
|
|
||
| ### `runScripts` | ||
|
|
||
| Currently, only `createContextualFragment` is capable of running scripts in dynamic markup. | ||
| The scripts are executed after the markup is inserted. Unlike regular parsing, classic external scripts are not | ||
| parser-blocking, as it's awkward to block a synchronous call on an asynchronous fetch. | ||
|
|
||
| The new proposal here is to add a `{runScripts: boolean}` option, false by default, to the `SetHTMLUnsafeOptions` dictionary. | ||
| This would allow any unsafe HTML setter to run scripts in a similar fashion to `createContextualFragment`. | ||
|
|
||
| Note that `runScripts` is not available for safe dynamic markup injection. | ||
|
|
||
| ### Scripts & streaming | ||
|
|
||
| For streaming, the processing model would be different, as the streaming parser is not a synchronous call. | ||
| In the streaming case, script execution would behave more like the main parser, where classic scripts block furher parsing, | ||
| and scripts with `defer` (including `module` scripts) are executed when the stream is closed and the parser finishes | ||
| processing the entire markup. | ||
|
|
||
| ## Sanitizer integration | ||
|
|
||
| Both safe and unafe variants can receive a `sanitizer` option. As per the sanitizer spec, | ||
| the safe variants ensure that the sanitizer config has a few baseline features. See https://wicg.github.io/sanitizer-api/. | ||
|
|
||
| ## Trusted types integration | ||
|
|
||
| The current API for trusted types policies rely on transforming HTML strings before they are passed to the parser. | ||
| This is incompatible with how the sanitizer works, and also doesn't work well with streaming, as userspace sanitation | ||
| libraries such as `DOMPurify` would have to support streaming as well. | ||
|
|
||
| The proposal is that trusted types would be able to participate in a flow that involves streaming and/or sanitizer | ||
| 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)): | ||
|
|
||
| ```webidl | ||
| interface TrustedTypePolicy { | ||
| TrustedHTMLParserOptions createHTMLParserOptions((SetHTMLOptions or SetHTMLUnsafeOptions) options = {}); | ||
| } | ||
| ``` | ||
|
|
||
| By providing a method as such, the policy can: | ||
| - Modify a sanitizer, or inject one if it doesn't exist | ||
| - Change the `runScripts` option | ||
| - Bless the options as-is, e.g. to allow first-party scripts to inject unsafe and non-sanitized markup. | ||
|
|
||
| Passing a (non-fungible) `TrustedHTMLParserOptions` to one of the HTML setting/streaming methods would bypass the default policy, | ||
| and unlike `createHTML`, would also allow streaming. | ||
| If this method is provided in the default policy, it would transform any incoming options, after also going through the `createHTML` call. | ||
| (Alternatively, they can be mutually exclusive, using `createHTML` as graceful degradation). | ||
|
|
||
| See [discussion](https://github.com/w3c/trusted-types/issues/594). | ||
|
|
||
| ## Node-creation side effects | ||
|
|
||
| Currently, `createContextualFragment` has a somewhat quirky side-effect of preloading images, even before the fragment is connected. | ||
| See https://github.com/whatwg/html/issues/12010. | ||
|
|
||
| This is intended to stay as a quirk specific to `createContextualFragment`, as no other API separates between the fragment creation and insertion. | ||
|
|
||
| ## Special template behavior | ||
|
|
||
| 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. | ||
| This is another difference between APIs, as older APIs might rely on userspace sanitizers that don't know about the existence of these features. | ||
|
|
||
| Open issue: define how this should behave going forward. | ||
|
|
||
| ## Resulting API | ||
|
|
||
| This results in the following API, which includes 24 methods: | ||
|
|
||
| ```webidl | ||
| enum SanitizerPresets { "default" }; | ||
| dictionary SetHTMLOptions { | ||
| (Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = "default"; | ||
| }; | ||
| dictionary SetHTMLUnsafeOptions { | ||
| (Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = {}; | ||
| boolean runScripts = false; | ||
| }; | ||
|
|
||
| interface TrustedSetHTMLOptions { | ||
| (Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer; | ||
| } | ||
|
|
||
| interface TrustedSetHTMLUnsafeOptions { | ||
| (Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer; | ||
| boolean runScripts; | ||
| } | ||
|
|
||
| typedef (SetHTMLUnsafeOptions or TrustedHTMLParserOptions) UnsafeHTMLSetterOptions; | ||
| typedef (SetHTMLOptions or TrustedHTMLParserOptions) SafeHTMLSetterOptions; | ||
|
|
||
| [Exposed=Window] | ||
| mixin interface ElementOrShadowRoot { | ||
| 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 = {}); | ||
| }; | ||
| ``` | ||
|
|
||
| ## Existing methods | ||
|
|
||
| Apart from the `createContextualFragment` quirk and special template behavior like declarative shadow roots, | ||
| all of the existing APIs can be expressed in terms of the above APIs, | ||
| implicitly being unsafe, having a false `runScripts` and no sanitizer: | ||
|
|
||
| ```js | ||
| class Element { | ||
| set innerHTML(html) { | ||
| this.setHTMLUnsafe(html); | ||
| } | ||
|
|
||
| set outerHTML(html) { | ||
| this.replaceWithHTMLUnsafe(html); | ||
| } | ||
|
|
||
| insertAdjacentHTML(html, insertion_point) { | ||
| switch (insertion_point) { | ||
| case "beforebegin": | ||
| this.beforeHTMLUnsafe(html); | ||
| break; | ||
| case "afterbegin": | ||
| this.prependHTMLUnsafe(html); | ||
| break; | ||
| case "beforeend": | ||
| this.appendHTMLUnsafe(html); | ||
| break; | ||
| case "afterend": | ||
| this.afterHTMLUnsafe(html); | ||
| break; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| ``` | ||
|
|
||
| ## Security & Privacy Questionnaire | ||
|
|
||
| 1. What information does this feature expose, and for what purposes? | ||
| It does not expose new information. | ||
|
|
||
| 2. Do features in your specification expose the minimum amount of information necessary to implement the intended functionality? | ||
| N/A | ||
|
|
||
| 3. Do the features in your specification expose personal information, personally-identifiable information (PII), or information derived from either? | ||
| No | ||
|
|
||
| 4. How do the features in your specification deal with sensitive information? | ||
| N/A | ||
|
|
||
| 5. Does data exposed by your specification carry related but distinct information that may not be obvious to users? | ||
|
|
||
| No | ||
|
|
||
| 6. Do the features in your specification introduce state that persists across browsing sessions? | ||
| No | ||
|
|
||
| 7. Do the features in your specification expose information about the underlying platform to origins? | ||
| No | ||
|
|
||
| 8. Does this specification allow an origin to send data to the underlying platform? | ||
| No | ||
|
|
||
| 9. Do features in this specification enable access to device sensors? | ||
| No | ||
|
|
||
| 10. Do features in this specification enable new script execution/loading mechanisms? | ||
| Yes, and this is handled specifically and deliberately by integrating with the sanitizer, trusted types, and the `runScripts` option. | ||
|
|
||
| 11. Do features in this specification allow an origin to access other devices? | ||
| No. | ||
|
|
||
| 12. Do features in this specification allow an origin some measure of control over a user agent's native UI? | ||
| No. | ||
|
|
||
| 13. What temporary identifiers do the features in this specification create or expose to the web? | ||
| N/A | ||
|
|
||
| 14. How does this specification distinguish between behavior in first-party and third-party contexts? | ||
| It integrates with trusted types. The 1st party can create separate trusted-types policies for 1st party and 3rd party contexts. | ||
|
|
||
| 15. How do the features in this specification work in the context of a browser’s Private Browsing or Incognito mode? | ||
| N/A | ||
|
|
||
| 16. Does this specification have both "Security Considerations" and "Privacy Considerations" sections? | ||
| It is intended to be part of the HTML standard, so yes. | ||
|
|
||
| 117. Do features in your specification enable origins to downgrade default security protections? | ||
| No | ||
|
|
||
| 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? | ||
| Nothing in particular. | ||
|
|
||
| 19. What happens when a document that uses your feature gets disconnected? | ||
| Being connected/disconnected doesn't affect this feature atm. | ||
|
|
||
| 20. Does your spec define when and how new kinds of errors should be raised? | ||
| It will. | ||
|
|
||
| 21. Does your feature allow sites to learn about the user's use of assistive technology? | ||
| No | ||
|
|
||
| 22. What should this questionnaire have asked? | ||
| Does this feature allow new ways of changing the DOM/injecting HTML. | ||
|
|
||
|
|
||
|
|
||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think WebIDL doesn't allow a union of
SetHTMLOptionsandSetHTMLUnsafeOptionsbecause they cannot be distinguished. If we want the result to not require further checking when passed tosetHTML(), maybe add acreateHTMLParserOptionsUnsafe()here with a different return type?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that's necessary. The resulting object can be the same type, and the
Safevariant can ignorerunScriptsand assert a safe baseline.