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 2 years 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)) * 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)) * 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)) * 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) ## 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 * Finds the given node's previous sibling in the DOM
* *
* Unless the component is a custom element (web component), which in this * The Svelte <slot> is just a placeholder and is not actually real. Any children nodes
* case, the <slot> element is actually real, the Svelte <slot> is just a * in <slot> are 'flattened' and considered as the same level as the <slot>'s siblings
* 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>
* e.g. * <slot>
* <h1>Heading 1</h1> * <h2>Heading 2</h2>
* <slot> * </slot>
* <h2>Heading 2</h2> *
* </slot> * is considered to look like:
* * <h1>Heading 1</h1>
* is considered to look like: * <h2>Heading 2</h2>
* <h1>Heading 1</h1> */
* <h2>Heading 2</h2>
*/
function find_previous_sibling(node: INode): INode { function find_previous_sibling(node: INode): INode {
if (node.component.compile_options.customElement) {
return node.prev;
}
let current_node: INode = node; let current_node: INode = node;
do { do {
if (current_node.type === 'Slot') { 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) { export function append_empty_stylesheet(node: Node) {
const style_element = element('style') as HTMLStyleElement; 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); append_stylesheet(get_root_for_style(node), style_element);
return style_element.sheet as CSSStyleSheet; return style_element.sheet as CSSStyleSheet;
} }

@ -5,12 +5,12 @@ export default {
test: async ({ component, assert, window, waitUntil }) => { test: async ({ component, assert, window, waitUntil }) => {
assert.htmlEqual(window.document.head.innerHTML, ''); assert.htmlEqual(window.document.head.innerHTML, '');
component.visible = true; 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 === ''); await waitUntil(() => window.document.head.innerHTML === '');
assert.htmlEqual(window.document.head.innerHTML, ''); assert.htmlEqual(window.document.head.innerHTML, '');
component.visible = false; 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 === ''); await waitUntil(() => window.document.head.innerHTML === '');
assert.htmlEqual(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> <h1>Heading 1</h1>
<span>Span 1</span> <span>Span 1</span>
@ -8,13 +8,13 @@
</slot> </slot>
<style> <style>
/* This will not get picked up */ /* This will not be picked up */
h1 ~ p { h1 ~ slot > p {
color: red; color: red;
} }
/* This will be picked up */ /* This will get picked up */
h1 ~ slot > p { h1 ~ p {
color: red; color: red;
} }
</style> </style>

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

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

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

Loading…
Cancel
Save