Repository: capricorn86/happy-dom (ts)

Issue:
`blur` and `focus` events should not bubble through DOM
**Describe the bug**
When triggering `focus/blur` events on the element its ancestor nodes also receive `focus/blur` when they should not.

Per MDN docs `focus` and `blur` events don't bubble in DOM. But related `focusin` and `focusout` do bubble.
https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event
https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event

**Expected behavior**
`focus` or `blur` events should not bubble in DOM.

**Device:**
 - Version 8.7.1

Will open a PR soon with updated test cases.


Pull request:
#771@patch: Don't bubble `focus` and `blur` events through DOM.
Fixes #771.

Only updated existing `HTMLElement` tests that expect `bubbles` value of `blur/focus` events to be `false` from now.

Actual event bubbling is still covered by `Node.test.ts`.

Patch:
diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
index a37ada02..23ea6eb0 100644
--- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
+++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
@@ -383,15 +383,18 @@ export default class HTMLElement extends Element implements IHTMLElement {
 
 		this.ownerDocument['_activeElement'] = null;
 
-		for (const eventType of ['blur', 'focusout']) {
-			const event = new FocusEvent(eventType, {
+		this.dispatchEvent(
+			new FocusEvent('blur', {
+				bubbles: false,
+				composed: true
+			})
+		);
+		this.dispatchEvent(
+			new FocusEvent('focusout', {
 				bubbles: true,
 				composed: true
-			});
-			event._target = this;
-			event._currentTarget = this;
-			this.dispatchEvent(event);
-		}
+			})
+		);
 	}
 
 	/**
@@ -408,15 +411,18 @@ export default class HTMLElement extends Element implements IHTMLElement {
 
 		this.ownerDocument['_activeElement'] = this;
 
-		for (const eventType of ['focus', 'focusin']) {
-			const event = new FocusEvent(eventType, {
+		this.dispatchEvent(
+			new FocusEvent('focus', {
+				bubbles: false,
+				composed: true
+			})
+		);
+		this.dispatchEvent(
+			new FocusEvent('focusin', {
 				bubbles: true,
 				composed: true
-			});
-			event._target = this;
-			event._currentTarget = this;
-			this.dispatchEvent(event);
-		}
+			})
+		);
 	}
 
 	/**


Test patch:
diff --git a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts
index 4e420249..a7bf16ba 100644
--- a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts
+++ b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts
@@ -388,7 +388,7 @@ describe('HTMLElement', () => {
 			element.blur();
 
 			expect(triggeredBlurEvent.type).toBe('blur');
-			expect(triggeredBlurEvent.bubbles).toBe(true);
+			expect(triggeredBlurEvent.bubbles).toBe(false);
 			expect(triggeredBlurEvent.composed).toBe(true);
 			expect(triggeredBlurEvent.target === element).toBe(true);
 
@@ -446,7 +446,7 @@ describe('HTMLElement', () => {
 			element.focus();
 
 			expect(triggeredFocusEvent.type).toBe('focus');
-			expect(triggeredFocusEvent.bubbles).toBe(true);
+			expect(triggeredFocusEvent.bubbles).toBe(false);
 			expect(triggeredFocusEvent.composed).toBe(true);
 			expect(triggeredFocusEvent.target === element).toBe(true);
 
@@ -502,7 +502,7 @@ describe('HTMLElement', () => {
 			element.focus();
 
 			expect(triggeredEvent.type).toBe('blur');
-			expect(triggeredEvent.bubbles).toBe(true);
+			expect(triggeredEvent.bubbles).toBe(false);
 			expect(triggeredEvent.composed).toBe(true);
 			expect(triggeredEvent.target === previousElement).toBe(true);
 		});