Skip to content

Commit

Permalink
perf(focus-monitor): use passive touch listener
Browse files Browse the repository at this point in the history
The `FocusMonitor` works by binding a `touchstart` (as well as some other events) listener to the `document` in order to keep track of focus. These changes make the `touchstart` listener `passive`, which means that on supported browsers it won't block rendering and should make scrolling smoother.
  • Loading branch information
crisbeto committed Oct 22, 2017
1 parent 76a6e7b commit cc6acde
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 5 deletions.
4 changes: 2 additions & 2 deletions src/cdk/a11y/focus-monitor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('FocusMonitor', () => {

it('should detect focus via touch', fakeAsync(() => {
// Simulate focus via touch.
dispatchMouseEvent(buttonElement, 'touchstart');
dispatchFakeEvent(buttonElement, 'touchstart');
buttonElement.focus();
fixture.detectChanges();
tick(TOUCH_BUFFER_MS);
Expand Down Expand Up @@ -262,7 +262,7 @@ describe('cdkMonitorFocus', () => {

it('should detect focus via touch', fakeAsync(() => {
// Simulate focus via touch.
dispatchMouseEvent(buttonElement, 'touchstart');
dispatchFakeEvent(buttonElement, 'touchstart');
buttonElement.focus();
fixture.detectChanges();
tick(TOUCH_BUFFER_MS);
Expand Down
9 changes: 6 additions & 3 deletions src/cdk/a11y/focus-monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Platform} from '@angular/cdk/platform';
import {Platform, supportsPassiveEventListeners} from '@angular/cdk/platform';
import {
Directive,
ElementRef,
Expand Down Expand Up @@ -168,13 +168,16 @@ export class FocusMonitor {
// When the touchstart event fires the focus event is not yet in the event queue. This means
// we can't rely on the trick used above (setting timeout of 0ms). Instead we wait 650ms to
// see if a focus happens.
document.addEventListener('touchstart', (event: Event) => {
document.addEventListener('touchstart', (event: TouchEvent) => {
if (this._touchTimeout != null) {
clearTimeout(this._touchTimeout);
}
this._lastTouchTarget = event.target;
this._touchTimeout = setTimeout(() => this._lastTouchTarget = null, TOUCH_BUFFER_MS);
}, true);

// Note that we need to cast the event options to `any`, because at the time of writing
// (TypeScript 2.5), the built-in types don't support the `addEventListener` options param.
}, supportsPassiveEventListeners() ? ({passive: true, useCapture: true} as any) : true);

// Make a note of when the window regains focus, so we can restore the origin info for the
// focused element.
Expand Down
21 changes: 21 additions & 0 deletions src/cdk/platform/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@
* found in the LICENSE file at https://angular.io/license
*/

/** Cached result of whether the user's browser supports passive event listeners. */
let supportsPassiveEvents: boolean;

/**
* Checks whether the user's browser supports passive event listeners.
* See: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
*/
export function supportsPassiveEventListeners(): boolean {
if (supportsPassiveEvents == null) {
try {
window.addEventListener('test', null!, Object.defineProperty({}, 'passive', {
get: () => supportsPassiveEvents = true
}));
} finally {
supportsPassiveEvents = supportsPassiveEvents || false;
}
}

return supportsPassiveEvents;
}

/** Cached result Set of input types support by the current browser. */
let supportedInputTypes: Set<string>;

Expand Down
4 changes: 4 additions & 0 deletions src/demo-app/focus-origin/focus-origin-demo.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.demo-focusable {
border: 2px solid transparent;
}

.demo-focusable.cdk-focused {
border: 2px solid red;
}
Expand Down

0 comments on commit cc6acde

Please sign in to comment.