fix: provide a workaround for `unsafe-inline` CSP that also works in Safari (#7800)

This changes the inserted style element for transitions to initially include the string '/* empty */'. This allows you to work around requiring unsafe-inline CSP discussed in #6662 by adding a hash to your CSP:

'sha256-9OlNO0DNEeaVzHL4RZwCLsBHA8WBQ8toBp/4F5XV2nc='

---------

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
pull/8566/head
Karl Erik Hofseth 1 year ago committed by GitHub
parent c0363966d3
commit afd38494b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -16,6 +16,8 @@
* Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251))
* Bind `null` option and input values consistently ([#8312](https://github.com/sveltejs/svelte/issues/8312))
* Allow `$store` to be used with changing values including nullish values ([#7555](https://github.com/sveltejs/svelte/issues/7555))
* Initialize stylesheet with `/* empty */` to enable setting CSP directive that also works in Safari ([#7800](https://github.com/sveltejs/svelte/pull/7800))
* Treat slots as if they don't exist when using CSS adjacent and general sibling combinators ([#8284](https://github.com/sveltejs/svelte/issues/8284))
## Unreleased (3.0)

@ -471,28 +471,22 @@ function get_element_parent(node: Element): Element | null {
}
/**
* Finds the given node's previous sibling in the DOM
*
* Unless the component is a custom element (web component), which in this
* case, the <slot> element is actually real, the Svelte <slot> is just a
* placeholder and is not actually real. Any children nodes in <slot>
* are 'flattened' and considered as the same level as the <slot>'s siblings
*
* e.g.
* <h1>Heading 1</h1>
* <slot>
* <h2>Heading 2</h2>
* </slot>
*
* is considered to look like:
* <h1>Heading 1</h1>
* <h2>Heading 2</h2>
*/
* Finds the given node's previous sibling in the DOM
*
* The Svelte <slot> is just a placeholder and is not actually real. Any children nodes
* in <slot> are 'flattened' and considered as the same level as the <slot>'s siblings
*
* e.g.
* <h1>Heading 1</h1>
* <slot>
* <h2>Heading 2</h2>
* </slot>
*
* is considered to look like:
* <h1>Heading 1</h1>
* <h2>Heading 2</h2>
*/
function find_previous_sibling(node: INode): INode {
if (node.component.compile_options.customElement) {
return node.prev;
}
let current_node: INode = node;
do {
if (current_node.type === 'Slot') {

@ -157,6 +157,12 @@ export function get_root_for_style(node: Node): ShadowRoot | Document {
export function append_empty_stylesheet(node: Node) {
const style_element = element('style') as HTMLStyleElement;
// For transitions to work without 'style-src: unsafe-inline' Content Security Policy,
// these empty tags need to be allowed with a hash as a workaround until we move to the Web Animations API.
// Using the hash for the empty string (for an empty tag) works in all browsers except Safari.
// So as a workaround for the workaround, when we append empty style tags we set their content to /* empty */.
// The hash 'sha256-9OlNO0DNEeaVzHL4RZwCLsBHA8WBQ8toBp/4F5XV2nc=' will then work even in Safari.
style_element.textContent = '/* empty */';
append_stylesheet(get_root_for_style(node), style_element);
return style_element.sheet as CSSStyleSheet;
}

@ -5,12 +5,12 @@ export default {
test: async ({ component, assert, window, waitUntil }) => {
assert.htmlEqual(window.document.head.innerHTML, '');
component.visible = true;
assert.htmlEqual(window.document.head.innerHTML, '<style></style>');
assert.htmlEqual(window.document.head.innerHTML, '<style>/* empty */</style>');
await waitUntil(() => window.document.head.innerHTML === '');
assert.htmlEqual(window.document.head.innerHTML, '');
component.visible = false;
assert.htmlEqual(window.document.head.innerHTML, '<style></style>');
assert.htmlEqual(window.document.head.innerHTML, '<style>/* empty */</style>');
await waitUntil(() => window.document.head.innerHTML === '');
assert.htmlEqual(window.document.head.innerHTML, '');
}

@ -1,4 +1,4 @@
<svelte:options tag="my-element" />
<svelte:options customElement="my-element" />
<h1>Heading 1</h1>
<span>Span 1</span>
@ -8,13 +8,13 @@
</slot>
<style>
/* This will not get picked up */
h1 ~ p {
/* This will not be picked up */
h1 ~ slot > p {
color: red;
}
/* This will be picked up */
h1 ~ slot > p {
/* This will get picked up */
h1 ~ p {
color: red;
}
</style>

@ -1,13 +1,13 @@
[
{
"code": "css-unused-selector",
"message": "Unused CSS selector \"h1 ~ p\"",
"message": "Unused CSS selector \"h1 ~ slot > p\"",
"start": {
"column": 1,
"line": 12
},
"end": {
"column": 7,
"column": 14,
"line": 12
}
}

@ -1,4 +1,4 @@
<svelte:options tag="custom-element" />
<svelte:options customElement="custom-element" />
<h1>test</h1>
<slot>
@ -7,12 +7,12 @@
<style>
/* This will not be picked up */
h1 + span {
color: red;
}
h1 + slot > span {
color: red;
}
/* This will be picked up */
h1 + slot > span {
h1 + span {
color: red;
}
</style>

@ -2,12 +2,12 @@
{
"code": "css-unused-selector",
"end": {
"column": 11,
"column": 17,
"line": 10
},
"message": "Unused CSS selector \"h1 + span\"",
"message": "Unused CSS selector \"h1 + slot > span\"",
"start": {
"column": 2,
"column": 1,
"line": 10
}
}

Loading…
Cancel
Save