Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
capture, passive modes not working for svelte:body special element.
**Describe the bug**

once, capture, passive options are not working for the svelte:body element.

**Observation**

`<svelte:window on:keydown|passive={handleKeydown}>`
generates 
`listen(window, "keydown", /*handleKeydown*/ ctx[2], { passive: true }),`

*while*

`<svelte:body on:keydown|passive={handleKeydown}/>`
generates
`document.body.addEventListener("keydown", /*handleKeydown*/ ctx[2]);`

instead of using the listen method.





Discussion:

Resolving patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 * Disallow two-way binding to a variable declared by an `{#await}` block ([#4012](https://github.com/sveltejs/svelte/issues/4012))
 * Allow access to `let:` variables in sibling attributes on slot root ([#4173](https://github.com/sveltejs/svelte/issues/4173))
 * Add some more known globals ([#4276](https://github.com/sveltejs/svelte/pull/4276))
+* Correctly apply event modifiers to `<svelte:body>` events ([#4278](https://github.com/sveltejs/svelte/issues/4278))
 
 ## 3.17.1
 
diff --git a/src/compiler/compile/render_dom/wrappers/Body.ts b/src/compiler/compile/render_dom/wrappers/Body.ts
--- a/src/compiler/compile/render_dom/wrappers/Body.ts
+++ b/src/compiler/compile/render_dom/wrappers/Body.ts
@@ -1,26 +1,23 @@
 import Block from '../Block';
 import Wrapper from './shared/Wrapper';
-import { b } from 'code-red';
+import { x } from 'code-red';
 import Body from '../../nodes/Body';
 import { Identifier } from 'estree';
 import EventHandler from './Element/EventHandler';
+import add_event_handlers from './shared/add_event_handlers';
+import { TemplateNode } from '../../../interfaces';
+import Renderer from '../Renderer';
 
 export default class BodyWrapper extends Wrapper {
 	node: Body;
+	handlers: EventHandler[];
 
-	render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
-		this.node.handlers
-			.map(handler => new EventHandler(handler, this))
-			.forEach(handler => {
-				const snippet = handler.get_snippet(block);
-
-				block.chunks.init.push(b`
-					@_document.body.addEventListener("${handler.node.name}", ${snippet});
-				`);
+	constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
+		super(renderer, block, parent, node);
+		this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this));
+	}
 
-				block.chunks.destroy.push(b`
-					@_document.body.removeEventListener("${handler.node.name}", ${snippet});
-				`);
-			});
+	render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
+		add_event_handlers(block, x`@_document.body`, this.handlers);
 	}
 }
diff --git a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts
--- a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts
+++ b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts
@@ -2,6 +2,7 @@ import EventHandler from '../../../nodes/EventHandler';
 import Wrapper from '../shared/Wrapper';
 import Block from '../../Block';
 import { b, x, p } from 'code-red';
+import { Expression } from 'estree';
 
 const TRUE = x`true`;
 const FALSE = x`false`;
@@ -35,7 +36,7 @@ export default class EventHandlerWrapper {
 		return snippet;
 	}
 
-	render(block: Block, target: string) {
+	render(block: Block, target: string | Expression) {
 		let snippet = this.get_snippet(block);
 
 		if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`;
diff --git a/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts b/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts
--- a/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts
+++ b/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts
@@ -1,9 +1,10 @@
 import Block from '../../Block';
 import EventHandler from '../Element/EventHandler';
+import { Expression } from 'estree';
 
 export default function add_event_handlers(
 	block: Block,
-	target: string,
+	target: string | Expression,
 	handlers: EventHandler[]
 ) {
 	handlers.forEach(handler => add_event_handler(block, target, handler));
@@ -11,7 +12,7 @@ export default function add_event_handlers(
 
 export function add_event_handler(
 	block: Block,
-	target: string,
+	target: string | Expression,
 	handler: EventHandler
 ) {
 	handler.render(block, target);


Test patch:
diff --git a/test/runtime/samples/event-handler-modifier-body-once/_config.js b/test/runtime/samples/event-handler-modifier-body-once/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/event-handler-modifier-body-once/_config.js
@@ -0,0 +1,11 @@
+export default {
+	async test({ assert, component, window }) {
+		const event = new window.MouseEvent('click');
+
+		await window.document.body.dispatchEvent(event);
+		assert.equal(component.count, 1);
+
+		await window.document.body.dispatchEvent(event);
+		assert.equal(component.count, 1);
+	}
+};
diff --git a/test/runtime/samples/event-handler-modifier-body-once/main.svelte b/test/runtime/samples/event-handler-modifier-body-once/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/event-handler-modifier-body-once/main.svelte
@@ -0,0 +1,5 @@
+<script>
+	export let count = 0;
+</script>
+
+<svelte:body on:click|once="{() => count += 1}"/>
\ No newline at end of file