88
99import {
1010 Platform ,
11- normalizePassiveListenerOptions ,
1211 _getShadowRoot ,
1312 _getEventTarget ,
13+ _bindEventWithOptions ,
1414} from '@angular/cdk/platform' ;
1515import {
1616 Directive ,
@@ -23,6 +23,7 @@ import {
2323 Output ,
2424 AfterViewInit ,
2525 inject ,
26+ RendererFactory2 ,
2627} from '@angular/core' ;
2728import { Observable , of as observableOf , Subject , Subscription } from 'rxjs' ;
2829import { takeUntil } from 'rxjs/operators' ;
@@ -76,16 +77,18 @@ type MonitoredElementInfo = {
7677 * Event listener options that enable capturing and also
7778 * mark the listener as passive if the browser supports it.
7879 */
79- const captureEventListenerOptions = normalizePassiveListenerOptions ( {
80+ const captureEventListenerOptions = {
8081 passive : true ,
8182 capture : true ,
82- } ) ;
83+ } ;
8384
8485/** Monitors mouse and keyboard events to determine the cause of focus events. */
8586@Injectable ( { providedIn : 'root' } )
8687export class FocusMonitor implements OnDestroy {
8788 private _ngZone = inject ( NgZone ) ;
8889 private _platform = inject ( Platform ) ;
90+ private _renderer = inject ( RendererFactory2 ) . createRenderer ( null , null ) ;
91+ private _cleanupWindowFocus : ( ( ) => void ) | undefined ;
8992 private readonly _inputModalityDetector = inject ( InputModalityDetector ) ;
9093
9194 /** The focus origin that the next focus event is a result of. */
@@ -121,7 +124,13 @@ export class FocusMonitor implements OnDestroy {
121124 * handlers differently from the rest of the events, because the browser won't emit events
122125 * to the document when focus moves inside of a shadow root.
123126 */
124- private _rootNodeFocusListenerCount = new Map < HTMLElement | Document | ShadowRoot , number > ( ) ;
127+ private _rootNodeFocusListeners = new Map <
128+ HTMLElement | Document | ShadowRoot ,
129+ {
130+ count : number ;
131+ cleanups : ( ( ) => void ) [ ] ;
132+ }
133+ > ( ) ;
125134
126135 /**
127136 * The specified detection mode, used for attributing the origin of a focus
@@ -307,12 +316,6 @@ export class FocusMonitor implements OnDestroy {
307316 return this . _document || document ;
308317 }
309318
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-
316319 private _getFocusOrigin ( focusEventTarget : HTMLElement | null ) : FocusOrigin {
317320 if ( this . _origin ) {
318321 // If the origin was realized via a touch interaction, we need to perform additional checks
@@ -468,32 +471,45 @@ export class FocusMonitor implements OnDestroy {
468471 }
469472
470473 const rootNode = elementInfo . rootNode ;
471- const rootNodeFocusListeners = this . _rootNodeFocusListenerCount . get ( rootNode ) || 0 ;
474+ const listeners = this . _rootNodeFocusListeners . get ( rootNode ) ;
472475
473- if ( ! rootNodeFocusListeners ) {
476+ if ( listeners ) {
477+ listeners . count ++ ;
478+ } else {
474479 this . _ngZone . runOutsideAngular ( ( ) => {
475- rootNode . addEventListener (
476- 'focus' ,
477- this . _rootNodeFocusAndBlurListener ,
478- captureEventListenerOptions ,
479- ) ;
480- rootNode . addEventListener (
481- 'blur' ,
482- this . _rootNodeFocusAndBlurListener ,
483- captureEventListenerOptions ,
484- ) ;
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+ } ) ;
485499 } ) ;
486500 }
487501
488- this . _rootNodeFocusListenerCount . set ( rootNode , rootNodeFocusListeners + 1 ) ;
489-
490502 // Register global listeners when first element is monitored.
491503 if ( ++ this . _monitoredElementCount === 1 ) {
492504 // Note: we listen to events in the capture phase so we
493505 // can detect them even if the user stops propagation.
494506 this . _ngZone . runOutsideAngular ( ( ) => {
495- const window = this . _getWindow ( ) ;
496- window . addEventListener ( 'focus' , this . _windowFocusListener ) ;
507+ this . _cleanupWindowFocus ?.( ) ;
508+ this . _cleanupWindowFocus = this . _renderer . listen (
509+ 'window' ,
510+ 'focus' ,
511+ this . _windowFocusListener ,
512+ ) ;
497513 } ) ;
498514
499515 // The InputModalityDetector is also just a collection of global listeners.
@@ -506,32 +522,20 @@ export class FocusMonitor implements OnDestroy {
506522 }
507523
508524 private _removeGlobalListeners ( elementInfo : MonitoredElementInfo ) {
509- const rootNode = elementInfo . rootNode ;
525+ const listeners = this . _rootNodeFocusListeners . get ( elementInfo . rootNode ) ;
510526
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 ) ;
527+ if ( listeners ) {
528+ if ( listeners . count > 1 ) {
529+ listeners . count -- ;
516530 } else {
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 ) ;
531+ listeners . cleanups . forEach ( cleanup => cleanup ( ) ) ;
532+ this . _rootNodeFocusListeners . delete ( elementInfo . rootNode ) ;
528533 }
529534 }
530535
531536 // Unregister global listeners when last element is unmonitored.
532537 if ( ! -- this . _monitoredElementCount ) {
533- const window = this . _getWindow ( ) ;
534- window . removeEventListener ( 'focus' , this . _windowFocusListener ) ;
538+ this . _cleanupWindowFocus ?.( ) ;
535539
536540 // Equivalently, stop our InputModalityDetector subscription.
537541 this . _stopInputModalityDetector . next ( ) ;
0 commit comments