diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 8a7d3af6da..e678481a5f 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -154,7 +154,13 @@ export function get_root_for_style(node: Node): ShadowRoot | Document { return node.ownerDocument; } -export function append_stylesheet(node: ShadowRoot | Document, style: HTMLStyleElement) { +export function append_empty_stylesheet(node: Node) { + const style_element = element('style') as HTMLStyleElement; + append_stylesheet(get_root_for_style(node), style_element); + return style_element.sheet as CSSStyleSheet; +} + +function append_stylesheet(node: ShadowRoot | Document, style: HTMLStyleElement) { append((node as Document).head || node, style); return style.sheet as CSSStyleSheet; } diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 67236cabab..36dc1e6044 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -1,8 +1,8 @@ -import { append_stylesheet, detach, element, get_root_for_style } from './dom'; +import { append_empty_stylesheet, detach, get_root_for_style } from './dom'; import { raf } from './environment'; interface StyleInformation { - style_element: HTMLStyleElement; + stylesheet: CSSStyleSheet; rules: Record; } @@ -20,8 +20,8 @@ function hash(str: string) { return hash >>> 0; } -function create_style_information(doc: Document | ShadowRoot) { - const info = { style_element: element('style'), rules: {} }; +function create_style_information(doc: Document | ShadowRoot, node: Element & ElementCSSInlineStyle) { + const info = { stylesheet: append_empty_stylesheet(node), rules: {} }; managed_styles.set(doc, info); return info; } @@ -39,10 +39,9 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: const name = `__svelte_${hash(rule)}_${uid}`; const doc = get_root_for_style(node); - const { style_element, rules } = managed_styles.get(doc) || create_style_information(doc); + const { stylesheet, rules } = managed_styles.get(doc) || create_style_information(doc, node); if (!rules[name]) { - const stylesheet = append_stylesheet(doc, style_element); rules[name] = true; stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length); } @@ -72,8 +71,9 @@ export function clear_rules() { raf(() => { if (active) return; managed_styles.forEach(info => { - const { style_element } = info; - detach(style_element); + const { ownerNode } = info.stylesheet; + // there is no ownerNode if it runs on jsdom. + if (ownerNode) detach(ownerNode); }); managed_styles.clear(); }); diff --git a/test/runtime-puppeteer/index.ts b/test/runtime-puppeteer/index.ts index 8e6f813de4..62952ad9f7 100644 --- a/test/runtime-puppeteer/index.ts +++ b/test/runtime-puppeteer/index.ts @@ -117,8 +117,12 @@ describe('runtime (puppeteer)', function() { load(id) { if (id === 'main') { return ` - import SvelteComponent from ${JSON.stringify(path.join(__dirname, 'samples', dir, 'main.svelte'))}; - import config from ${JSON.stringify(path.join(__dirname, 'samples', dir, '_config.js'))}; + import SvelteComponent from ${JSON.stringify( + path.join(__dirname, 'samples', dir, 'main.svelte') + )}; + import config from ${JSON.stringify( + path.join(__dirname, 'samples', dir, '_config.js') + )}; import * as assert from 'assert'; export default async function (target) { @@ -140,6 +144,14 @@ describe('runtime (puppeteer)', function() { const component = new SvelteComponent(options); + const waitUntil = async (fn, ms = 500) => { + const start = new Date().getTime(); + do { + if (fn()) return; + await new Promise(resolve => window.setTimeout(resolve, 1)); + } while (new Date().getTime() <= start + ms); + }; + if (config.html) { assert.htmlEqual(target.innerHTML, config.html); } @@ -150,6 +162,7 @@ describe('runtime (puppeteer)', function() { component, target, window, + waitUntil, }); component.$destroy(); diff --git a/test/runtime-puppeteer/samples/style_manager-cleanup/_config.js b/test/runtime-puppeteer/samples/style_manager-cleanup/_config.js new file mode 100644 index 0000000000..b3a5a97e45 --- /dev/null +++ b/test/runtime-puppeteer/samples/style_manager-cleanup/_config.js @@ -0,0 +1,17 @@ +export default { + skip_if_ssr: true, + skip_if_hydrate: true, + skip_if_hydrate_from_ssr: true, + test: async ({ component, assert, window, waitUntil }) => { + assert.htmlEqual(window.document.head.innerHTML, ''); + component.visible = true; + assert.htmlEqual(window.document.head.innerHTML, ''); + await waitUntil(() => window.document.head.innerHTML === ''); + assert.htmlEqual(window.document.head.innerHTML, ''); + + component.visible = false; + assert.htmlEqual(window.document.head.innerHTML, ''); + await waitUntil(() => window.document.head.innerHTML === ''); + assert.htmlEqual(window.document.head.innerHTML, ''); + } +}; diff --git a/test/runtime-puppeteer/samples/style_manager-cleanup/main.svelte b/test/runtime-puppeteer/samples/style_manager-cleanup/main.svelte new file mode 100644 index 0000000000..ab13073b1e --- /dev/null +++ b/test/runtime-puppeteer/samples/style_manager-cleanup/main.svelte @@ -0,0 +1,16 @@ + + +{#if visible} +
+{/if} diff --git a/test/runtime-puppeteer/samples/transition-css-out-in/_config.js b/test/runtime-puppeteer/samples/transition-css-out-in/_config.js new file mode 100644 index 0000000000..a50d28b257 --- /dev/null +++ b/test/runtime-puppeteer/samples/transition-css-out-in/_config.js @@ -0,0 +1,14 @@ +export default { + test: async ({ assert, component, window, waitUntil }) => { + component.visible = true; + await waitUntil(() => window.document.head.querySelector('style').sheet.rules.length === 2); + assert.equal(window.document.head.querySelector('style').sheet.rules.length, 2); + await waitUntil(() => window.document.head.querySelector('style') === null); + assert.equal(window.document.head.querySelector('style'), null); + component.visible = false; + await waitUntil(() => window.document.head.querySelector('style').sheet.rules.length === 2); + assert.equal(window.document.head.querySelector('style').sheet.rules.length, 2); + await waitUntil(() => window.document.head.querySelector('style') === null); + assert.equal(window.document.head.querySelector('style'), null); + } +}; diff --git a/test/runtime-puppeteer/samples/transition-css-out-in/main.svelte b/test/runtime-puppeteer/samples/transition-css-out-in/main.svelte new file mode 100644 index 0000000000..3ee93f0b55 --- /dev/null +++ b/test/runtime-puppeteer/samples/transition-css-out-in/main.svelte @@ -0,0 +1,20 @@ + + +{#if visible} +
+{/if} + +{#if !visible} +
+{/if} diff --git a/test/runtime/samples/style_manager-cleanup/_config.js b/test/runtime/samples/style_manager-cleanup/_config.js deleted file mode 100644 index 7609d76cf2..0000000000 --- a/test/runtime/samples/style_manager-cleanup/_config.js +++ /dev/null @@ -1,14 +0,0 @@ -export default { - skip_if_ssr: true, - skip_if_hydrate: true, - skip_if_hydrate_from_ssr: true, - test({ raf, assert, component, window }) { - component.visible = true; - raf.tick(100); - component.visible = false; - raf.tick(200); - raf.tick(0); - - assert.htmlEqual(window.document.head.innerHTML, ''); - } -}; diff --git a/test/runtime/samples/style_manager-cleanup/main.svelte b/test/runtime/samples/style_manager-cleanup/main.svelte deleted file mode 100644 index c2ccdc7c09..0000000000 --- a/test/runtime/samples/style_manager-cleanup/main.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - - {#if visible} -
- {/if} \ No newline at end of file