From b8df098e35cdc98939d07c7be9279cf68389f63f Mon Sep 17 00:00:00 2001 From: Joshua Cain Date: Wed, 29 Jul 2026 14:21:33 -0400 Subject: [PATCH] AAE-49328 Form console errors (#12108) * fix: form console errors * fix: copilot feedback --- .../idle-activity-tracker.spec.ts | 42 +++++++++++++------ .../session-timeout/idle-activity-tracker.ts | 14 +++++-- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.spec.ts b/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.spec.ts index 62542fb1fe..d2c6e93c09 100644 --- a/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.spec.ts +++ b/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.spec.ts @@ -18,7 +18,7 @@ import { DOCUMENT } from '@angular/common'; import { NgZone } from '@angular/core'; import { fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { ACTIVITY_THROTTLE_MS, IdleActivityTracker } from './idle-activity-tracker'; +import { ACTIVITY_EVENTS, ACTIVITY_THROTTLE_MS, IdleActivityTracker } from './idle-activity-tracker'; describe('IdleActivityTracker', () => { let tracker: IdleActivityTracker; @@ -83,20 +83,36 @@ describe('IdleActivityTracker', () => { expect(spy).toHaveBeenCalledTimes(1); }); - it('should allow a later listener to prevent default when an activity event fires', () => { - const preventDefault = jasmine.createSpy('preventDefault').and.callFake((event: Event) => event.preventDefault()); + [ + { phase: 'bubbling', listenerOptions: { capture: false, passive: false } }, + { phase: 'capturing', listenerOptions: { capture: true, passive: false } } + ].forEach(({ phase, listenerOptions }) => { + it(`should allow a later active ${phase} listener to prevent default when an activity event fires`, () => { + const preventDefault = jasmine.createSpy('preventDefault').and.callFake((event: Event) => event.preventDefault()); + tracker.start(); + doc.addEventListener('mousemove', preventDefault, listenerOptions); + + try { + const event = new MouseEvent('mousemove', { cancelable: true }); + doc.dispatchEvent(event); + + expect(preventDefault).toHaveBeenCalledTimes(1); + expect(event.defaultPrevented).toBeTrue(); + } finally { + doc.removeEventListener('mousemove', preventDefault, listenerOptions); + } + }); + }); + + it('should keep scroll-sensitive activity listeners passive when tracking starts', () => { + const addEventListener = spyOn(doc, 'addEventListener').and.callThrough(); + tracker.start(); - doc.addEventListener('mousemove', preventDefault); - try { - const event = new MouseEvent('mousemove', { cancelable: true }); - doc.dispatchEvent(event); - - expect(preventDefault).toHaveBeenCalledTimes(1); - expect(event.defaultPrevented).toBeTrue(); - } finally { - doc.removeEventListener('mousemove', preventDefault); - } + expect(addEventListener).toHaveBeenCalledTimes(ACTIVITY_EVENTS.length + 1); + expect(addEventListener).toHaveBeenCalledWith('mousemove', jasmine.any(Function), { capture: true, passive: false }); + expect(addEventListener).toHaveBeenCalledWith('touchstart', jasmine.any(Function), { capture: true, passive: true }); + expect(addEventListener).toHaveBeenCalledWith('wheel', jasmine.any(Function), { capture: true, passive: true }); }); it('is a no-op when stop() is called without a prior start()', () => { diff --git a/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.ts b/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.ts index 8fcff0f25d..cc957b5277 100644 --- a/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.ts +++ b/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.ts @@ -25,7 +25,11 @@ export const ACTIVITY_EVENTS = ['click', 'keydown', 'mousedown', 'mousemove', 'p /** High-frequency activity events (e.g. mousemove, scroll) are throttled to avoid rescheduling the idle timer on every DOM event. */ export const ACTIVITY_THROTTLE_MS = 1000; -const ACTIVITY_LISTENER_OPTIONS: AddEventListenerOptions = { capture: true, passive: true }; +const ACTIVE_ACTIVITY_LISTENER_OPTIONS: AddEventListenerOptions = { capture: true, passive: false }; +const PASSIVE_ACTIVITY_LISTENER_OPTIONS: AddEventListenerOptions = { capture: true, passive: true }; + +const getActivityListenerOptions = (eventName: (typeof ACTIVITY_EVENTS)[number]): AddEventListenerOptions => + eventName === 'mousemove' ? ACTIVE_ACTIVITY_LISTENER_OPTIONS : PASSIVE_ACTIVITY_LISTENER_OPTIONS; @Injectable() export class IdleActivityTracker implements OnDestroy { @@ -46,7 +50,9 @@ export class IdleActivityTracker implements OnDestroy { } this.ngZone.runOutsideAngular(() => { - ACTIVITY_EVENTS.forEach((eventName) => this.document.addEventListener(eventName, this.handleActivity, ACTIVITY_LISTENER_OPTIONS)); + ACTIVITY_EVENTS.forEach((eventName) => + this.document.addEventListener(eventName, this.handleActivity, getActivityListenerOptions(eventName)) + ); this.document.addEventListener('visibilitychange', this.handleVisibilityChange); }); this.isRegistered = true; @@ -57,7 +63,9 @@ export class IdleActivityTracker implements OnDestroy { return; } - ACTIVITY_EVENTS.forEach((eventName) => this.document.removeEventListener(eventName, this.handleActivity, ACTIVITY_LISTENER_OPTIONS)); + ACTIVITY_EVENTS.forEach((eventName) => + this.document.removeEventListener(eventName, this.handleActivity, getActivityListenerOptions(eventName)) + ); this.document.removeEventListener('visibilitychange', this.handleVisibilityChange); this.isRegistered = false; }