From c9527f683f786402db02a0e0e683d1834c4e69ec Mon Sep 17 00:00:00 2001 From: Jacob Wright Date: Thu, 26 Sep 2019 13:54:21 -0600 Subject: [PATCH 1/6] Allow transitions and animations to work within iframes Svelte works great in iframes except for transitions and animations. This fixes that issue. See #3624. --- src/runtime/internal/style_manager.ts | 35 +++++++---- .../samples/transition-css-iframe/Foo.svelte | 16 +++++ .../transition-css-iframe/Frame.svelte | 58 +++++++++++++++++++ .../samples/transition-css-iframe/_config.js | 18 ++++++ .../samples/transition-css-iframe/main.svelte | 8 +++ 5 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 test/runtime/samples/transition-css-iframe/Foo.svelte create mode 100644 test/runtime/samples/transition-css-iframe/Frame.svelte create mode 100644 test/runtime/samples/transition-css-iframe/_config.js create mode 100644 test/runtime/samples/transition-css-iframe/main.svelte diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index d9264e3c0843..61a8540626d2 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -1,9 +1,11 @@ import { element } from './dom'; import { raf } from './environment'; -let stylesheet; +type DocStyles = [CSSStyleSheet, Record]; + +let activeDocs = new Set(); +let docStyles = new WeakMap(); let active = 0; -let current_rules = {}; // https://github.com/darkskyapp/string-hash/blob/master/index.js function hash(str: string) { @@ -25,14 +27,18 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`; const name = `__svelte_${hash(rule)}_${uid}`; + const doc = node.ownerDocument; + activeDocs.add(doc); + let [ stylesheet, current_rules ] = (docStyles.get(doc) || []) as DocStyles; + if (!stylesheet) { + current_rules = {}; + const style = element('style'); + doc.head.appendChild(style); + stylesheet = style.sheet as CSSStyleSheet; + docStyles.set(doc, [ stylesheet, current_rules ]); + } if (!current_rules[name]) { - if (!stylesheet) { - const style = element('style'); - document.head.appendChild(style); - stylesheet = style.sheet; - } - current_rules[name] = true; stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length); } @@ -53,14 +59,19 @@ export function delete_rule(node: Element & ElementCSSInlineStyle, name?: string ) .join(', '); - if (name && !--active) clear_rules(); + active = Math.max(0, active - 1); + if (name && !active) clear_rules(); } export function clear_rules() { raf(() => { if (active) return; - let i = stylesheet.cssRules.length; - while (i--) stylesheet.deleteRule(i); - current_rules = {}; + activeDocs.forEach(doc => { + const [ stylesheet ] = docStyles.get(doc); + let i = stylesheet.cssRules.length; + while (i--) stylesheet.deleteRule(i); + docStyles.set(doc, [ stylesheet, {} ]); + }); + activeDocs.clear(); }); } diff --git a/test/runtime/samples/transition-css-iframe/Foo.svelte b/test/runtime/samples/transition-css-iframe/Foo.svelte new file mode 100644 index 000000000000..c79672f21b15 --- /dev/null +++ b/test/runtime/samples/transition-css-iframe/Foo.svelte @@ -0,0 +1,16 @@ + + +{#if visible} +
+{/if} \ No newline at end of file diff --git a/test/runtime/samples/transition-css-iframe/Frame.svelte b/test/runtime/samples/transition-css-iframe/Frame.svelte new file mode 100644 index 000000000000..c7ac9cf76f81 --- /dev/null +++ b/test/runtime/samples/transition-css-iframe/Frame.svelte @@ -0,0 +1,58 @@ + + + + + diff --git a/test/runtime/samples/transition-css-iframe/_config.js b/test/runtime/samples/transition-css-iframe/_config.js new file mode 100644 index 000000000000..507efe44f4f5 --- /dev/null +++ b/test/runtime/samples/transition-css-iframe/_config.js @@ -0,0 +1,18 @@ +export default { + skip_if_ssr: true, + + async test({ assert, component, target, window, raf }) { + const frame = target.querySelector('iframe'); + await Promise.resolve(); + + component.visible = true; + const div = frame.contentDocument.querySelector('div'); + + raf.tick(25); + + component.visible = false; + + raf.tick(26); + assert.ok(~div.style.animation.indexOf('25ms')); + }, +}; diff --git a/test/runtime/samples/transition-css-iframe/main.svelte b/test/runtime/samples/transition-css-iframe/main.svelte new file mode 100644 index 000000000000..b1686ff15e7f --- /dev/null +++ b/test/runtime/samples/transition-css-iframe/main.svelte @@ -0,0 +1,8 @@ + + + \ No newline at end of file From 9f98b975c1995df3baafee1a8e31e4c19a8a22da Mon Sep 17 00:00:00 2001 From: Jacob Wright Date: Tue, 19 Nov 2019 16:40:20 -0700 Subject: [PATCH 2/6] Updates names and saves 18 bytes. --- src/runtime/internal/style_manager.ts | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 61a8540626d2..8fc6514cbcb5 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -3,8 +3,8 @@ import { raf } from './environment'; type DocStyles = [CSSStyleSheet, Record]; -let activeDocs = new Set(); -let docStyles = new WeakMap(); +let active_docs = new Set(); +let doc_styles = new Map(); let active = 0; // https://github.com/darkskyapp/string-hash/blob/master/index.js @@ -28,15 +28,11 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`; const name = `__svelte_${hash(rule)}_${uid}`; const doc = node.ownerDocument; - activeDocs.add(doc); - let [ stylesheet, current_rules ] = (docStyles.get(doc) || []) as DocStyles; - if (!stylesheet) { - current_rules = {}; - const style = element('style'); - doc.head.appendChild(style); - stylesheet = style.sheet as CSSStyleSheet; - docStyles.set(doc, [ stylesheet, current_rules ]); - } + active_docs.add(doc); + const [ stylesheet, current_rules ] = (doc_styles.has(doc) ? doc_styles : doc_styles.set(doc, [ + (doc.head.appendChild(element('style') as HTMLStyleElement).sheet as CSSStyleSheet), + {} + ])).get(doc) as DocStyles; if (!current_rules[name]) { current_rules[name] = true; @@ -66,12 +62,12 @@ export function delete_rule(node: Element & ElementCSSInlineStyle, name?: string export function clear_rules() { raf(() => { if (active) return; - activeDocs.forEach(doc => { - const [ stylesheet ] = docStyles.get(doc); + active_docs.forEach(doc => { + const [ stylesheet ] = doc_styles.get(doc); let i = stylesheet.cssRules.length; while (i--) stylesheet.deleteRule(i); - docStyles.set(doc, [ stylesheet, {} ]); + doc_styles.set(doc, [ stylesheet, {} ]); }); - activeDocs.clear(); + active_docs.clear(); }); } From db0e7e82b7fb6024629928101e38f2570033ec1d Mon Sep 17 00:00:00 2001 From: Jacob Wright Date: Fri, 28 Feb 2020 15:41:30 -0700 Subject: [PATCH 3/6] Fixing lint issues --- src/runtime/internal/style_manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 8fc6514cbcb5..25dfa53169d0 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -3,8 +3,8 @@ import { raf } from './environment'; type DocStyles = [CSSStyleSheet, Record]; -let active_docs = new Set(); -let doc_styles = new Map(); +const active_docs = new Set(); +const doc_styles = new Map(); let active = 0; // https://github.com/darkskyapp/string-hash/blob/master/index.js From 4057e753be4b9812c5ee9f27beeaca6c6002824a Mon Sep 17 00:00:00 2001 From: Jacob Wright Date: Fri, 28 Feb 2020 15:54:02 -0700 Subject: [PATCH 4/6] Removing crutch for leak I will add a ticket to document the leak. --- src/runtime/internal/style_manager.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 25dfa53169d0..1641824de2db 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -55,8 +55,7 @@ export function delete_rule(node: Element & ElementCSSInlineStyle, name?: string ) .join(', '); - active = Math.max(0, active - 1); - if (name && !active) clear_rules(); + if (name && !--active) clear_rules(); } export function clear_rules() { From 6d3be4abb8177e745ce447f8665be6e14c5f3f78 Mon Sep 17 00:00:00 2001 From: Jacob Wright Date: Fri, 28 Feb 2020 17:10:12 -0700 Subject: [PATCH 5/6] Fix memory leaks. `doc_styles` was a leak as it was keeping old iframe docs in memory. By adding the stylesheet info on the document object it can be cleaned up (poor man's weakmap). `delete_rule` can potentially be called many times and if called without the `name` value it may remove all the rules but not track it correctly. The logic is updated to clear rules when there are no active transitions left. --- src/runtime/internal/style_manager.ts | 40 ++++++++++++++------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 1641824de2db..31d7573a769e 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -1,10 +1,12 @@ import { element } from './dom'; import { raf } from './environment'; -type DocStyles = [CSSStyleSheet, Record]; +interface ExtendedDoc extends Document { + __svelte_stylesheet: CSSStyleSheet; + __svelte_rules: Record; +} -const active_docs = new Set(); -const doc_styles = new Map(); +const active_docs = new Set(); let active = 0; // https://github.com/darkskyapp/string-hash/blob/master/index.js @@ -27,12 +29,10 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`; const name = `__svelte_${hash(rule)}_${uid}`; - const doc = node.ownerDocument; + const doc = node.ownerDocument as ExtendedDoc; active_docs.add(doc); - const [ stylesheet, current_rules ] = (doc_styles.has(doc) ? doc_styles : doc_styles.set(doc, [ - (doc.head.appendChild(element('style') as HTMLStyleElement).sheet as CSSStyleSheet), - {} - ])).get(doc) as DocStyles; + const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = doc.head.appendChild(element('style') as HTMLStyleElement).sheet as CSSStyleSheet); + const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {}); if (!current_rules[name]) { current_rules[name] = true; @@ -47,25 +47,27 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: } export function delete_rule(node: Element & ElementCSSInlineStyle, name?: string) { - node.style.animation = (node.style.animation || '') - .split(', ') - .filter(name - ? anim => anim.indexOf(name) < 0 // remove specific animation - : anim => anim.indexOf('__svelte') === -1 // remove all Svelte animations - ) - .join(', '); - - if (name && !--active) clear_rules(); + const previous = (node.style.animation || '').split(', '); + const next = previous.filter(name + ? anim => anim.indexOf(name) < 0 // remove specific animation + : anim => anim.indexOf('__svelte') === -1 // remove all Svelte animations + ); + const deleted = previous.length - next.length; + if (deleted) { + node.style.animation = next.join(', '); + active -= deleted; + if (!active) clear_rules(); + } } export function clear_rules() { raf(() => { if (active) return; active_docs.forEach(doc => { - const [ stylesheet ] = doc_styles.get(doc); + const stylesheet = doc.__svelte_stylesheet; let i = stylesheet.cssRules.length; while (i--) stylesheet.deleteRule(i); - doc_styles.set(doc, [ stylesheet, {} ]); + doc.__svelte_rules = {}; }); active_docs.clear(); }); From 8638f8e5b1da8a29091704c381047f6c75e70eef Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sat, 14 Mar 2020 16:31:43 -0400 Subject: [PATCH 6/6] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b9d08ea2a32..0fe0fa338118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Allow `` to be used in a slot ([#2798](https://github.com/sveltejs/svelte/issues/2798)) * Expose object of unknown props in `$$restProps` ([#2930](https://github.com/sveltejs/svelte/issues/2930)) +* Allow transitions and animations to work within iframes ([#3624](https://github.com/sveltejs/svelte/issues/3624)) * Disallow binding directly to `const` variables ([#4479](https://github.com/sveltejs/svelte/issues/4479)) * Fix updating keyed `{#each}` blocks with `{:else}` ([#4536](https://github.com/sveltejs/svelte/issues/4536), [#4549](https://github.com/sveltejs/svelte/issues/4549)) * Fix hydration of top-level content ([#4542](https://github.com/sveltejs/svelte/issues/4542))