88
99import {
1010 Platform ,
11+ normalizePassiveListenerOptions ,
1112 _getShadowRoot ,
1213 _getEventTarget ,
13- _bindEventWithOptions ,
1414} from '@angular/cdk/platform' ;
1515import {
1616 Directive ,
@@ -23,7 +23,6 @@ import {
2323 Output ,
2424 AfterViewInit ,
2525 inject ,
26- RendererFactory2 ,
2726} from '@angular/core' ;
2827import { Observable , of as observableOf , Subject , Subscription } from 'rxjs' ;
2928import { takeUntil } from 'rxjs/operators' ;
@@ -77,18 +76,16 @@ type MonitoredElementInfo = {
7776 * Event listener options that enable capturing and also
7877 * mark the listener as passive if the browser supports it.
7978 */
80- const captureEventListenerOptions = {
79+ const captureEventListenerOptions = normalizePassiveListenerOptions ( {
8180 passive : true ,
8281 capture : true ,
83- } ;
82+ } ) ;
8483
8584/** Monitors mouse and keyboard events to determine the cause of focus events. */
8685@Injectable ( { providedIn : 'root' } )
8786export class FocusMonitor implements OnDestroy {
8887 private _ngZone = inject ( NgZone ) ;
8988 private _platform = inject ( Platform ) ;
90- private _renderer = inject ( RendererFactory2 ) . createRenderer ( null , null ) ;
91- private _cleanupWindowFocus : ( ( ) => void ) | undefined ;
9289 private readonly _inputModalityDetector = inject ( InputModalityDetector ) ;
9390
9491 /** The focus origin that the next focus event is a result of. */
@@ -124,13 +121,7 @@ export class FocusMonitor implements OnDestroy {
124121 * handlers differently from the rest of the events, because the browser won't emit events
125122 * to the document when focus moves inside of a shadow root.
126123 */
127- private _rootNodeFocusListeners = new Map <
128- HTMLElement | Document | ShadowRoot ,
129- {
130- count : number ;
131- cleanups : ( ( ) => void ) [ ] ;
132- }
133- > ( ) ;
124+ private _rootNodeFocusListenerCount = new Map < HTMLElement | Document | ShadowRoot , number > ( ) ;
134125
135126 /**
136127 * The specified detection mode, used for attributing the origin of a focus
@@ -316,6 +307,12 @@ export class FocusMonitor implements OnDestroy {
316307 return this . _document || document ;
317308 }
318309
310+ /** Use defaultView of injected document if available or fallback to global window reference */
311+ private _getWindow ( ) : Window {
312+ const doc = this . _getDocument ( ) ;
313+ return doc . defaultView || window ;
314+ }
315+
319316 private _getFocusOrigin ( focusEventTarget : HTMLElement | null ) : FocusOrigin {
320317 if ( this . _origin ) {
321318 // If the origin was realized via a touch interaction, we need to perform additional checks
@@ -471,45 +468,32 @@ export class FocusMonitor implements OnDestroy {
471468 }
472469
473470 const rootNode = elementInfo . rootNode ;
474- const listeners = this . _rootNodeFocusListeners . get ( rootNode ) ;
471+ const rootNodeFocusListeners = this . _rootNodeFocusListenerCount . get ( rootNode ) || 0 ;
475472
476- if ( listeners ) {
477- listeners . count ++ ;
478- } else {
473+ if ( ! rootNodeFocusListeners ) {
479474 this . _ngZone . runOutsideAngular ( ( ) => {
480- this . _rootNodeFocusListeners . set ( rootNode , {
481- count : 1 ,
482- cleanups : [
483- _bindEventWithOptions (
484- this . _renderer ,
485- rootNode ,
486- 'focus' ,
487- this . _rootNodeFocusAndBlurListener ,
488- captureEventListenerOptions ,
489- ) ,
490- _bindEventWithOptions (
491- this . _renderer ,
492- rootNode ,
493- 'blur' ,
494- this . _rootNodeFocusAndBlurListener ,
495- captureEventListenerOptions ,
496- ) ,
497- ] ,
498- } ) ;
475+ rootNode . addEventListener (
476+ 'focus' ,
477+ this . _rootNodeFocusAndBlurListener ,
478+ captureEventListenerOptions ,
479+ ) ;
480+ rootNode . addEventListener (
481+ 'blur' ,
482+ this . _rootNodeFocusAndBlurListener ,
483+ captureEventListenerOptions ,
484+ ) ;
499485 } ) ;
500486 }
501487
488+ this . _rootNodeFocusListenerCount . set ( rootNode , rootNodeFocusListeners + 1 ) ;
489+
502490 // Register global listeners when first element is monitored.
503491 if ( ++ this . _monitoredElementCount === 1 ) {
504492 // Note: we listen to events in the capture phase so we
505493 // can detect them even if the user stops propagation.
506494 this . _ngZone . runOutsideAngular ( ( ) => {
507- this . _cleanupWindowFocus ?.( ) ;
508- this . _cleanupWindowFocus = this . _renderer . listen (
509- 'window' ,
510- 'focus' ,
511- this . _windowFocusListener ,
512- ) ;
495+ const window = this . _getWindow ( ) ;
496+ window . addEventListener ( 'focus' , this . _windowFocusListener ) ;
513497 } ) ;
514498
515499 // The InputModalityDetector is also just a collection of global listeners.
@@ -522,20 +506,32 @@ export class FocusMonitor implements OnDestroy {
522506 }
523507
524508 private _removeGlobalListeners ( elementInfo : MonitoredElementInfo ) {
525- const listeners = this . _rootNodeFocusListeners . get ( elementInfo . rootNode ) ;
509+ const rootNode = elementInfo . rootNode ;
526510
527- if ( listeners ) {
528- if ( listeners . count > 1 ) {
529- listeners . count -- ;
511+ if ( this . _rootNodeFocusListenerCount . has ( rootNode ) ) {
512+ const rootNodeFocusListeners = this . _rootNodeFocusListenerCount . get ( rootNode ) ! ;
513+
514+ if ( rootNodeFocusListeners > 1 ) {
515+ this . _rootNodeFocusListenerCount . set ( rootNode , rootNodeFocusListeners - 1 ) ;
530516 } else {
531- listeners . cleanups . forEach ( cleanup => cleanup ( ) ) ;
532- this . _rootNodeFocusListeners . delete ( elementInfo . rootNode ) ;
517+ rootNode . removeEventListener (
518+ 'focus' ,
519+ this . _rootNodeFocusAndBlurListener ,
520+ captureEventListenerOptions ,
521+ ) ;
522+ rootNode . removeEventListener (
523+ 'blur' ,
524+ this . _rootNodeFocusAndBlurListener ,
525+ captureEventListenerOptions ,
526+ ) ;
527+ this . _rootNodeFocusListenerCount . delete ( rootNode ) ;
533528 }
534529 }
535530
536531 // Unregister global listeners when last element is unmonitored.
537532 if ( ! -- this . _monitoredElementCount ) {
538- this . _cleanupWindowFocus ?.( ) ;
533+ const window = this . _getWindow ( ) ;
534+ window . removeEventListener ( 'focus' , this . _windowFocusListener ) ;
539535
540536 // Equivalently, stop our InputModalityDetector subscription.
541537 this . _stopInputModalityDetector . next ( ) ;
0 commit comments