Skip to content

Commit

Permalink
fix(material/core): add fallback if ripples get stuck
Browse files Browse the repository at this point in the history
Currently ripples assume that after the transition is started, either a `transitionend` or `transitioncancel` event will occur. That doesn't seem to be the case in some browser/OS combinations and when there's a high load on the browser.

These changes add a fallback timer that will clear the ripples if they get stuck.

Fixes angular#29159.
  • Loading branch information
crisbeto committed Jun 26, 2024
1 parent 202f058 commit 73f2fa3
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 4 deletions.
25 changes: 23 additions & 2 deletions src/material/core/ripple/ripple-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface RippleTarget {
interface RippleEventListeners {
onTransitionEnd: EventListener;
onTransitionCancel: EventListener;
fallbackTimer: ReturnType<typeof setTimeout> | null;
}

/**
Expand Down Expand Up @@ -193,14 +194,31 @@ export class RippleRenderer implements EventListenerObject {
// are set to zero. The events won't fire anyway and we can save resources here.
if (!animationForciblyDisabledThroughCss && (enterDuration || animationConfig.exitDuration)) {
this._ngZone.runOutsideAngular(() => {
const onTransitionEnd = () => this._finishRippleTransition(rippleRef);
const onTransitionEnd = () => {
// Clear the fallback timer since the transition fired correctly.
if (eventListeners) {
eventListeners.fallbackTimer = null;
}
clearTimeout(fallbackTimer);
this._finishRippleTransition(rippleRef);
};
const onTransitionCancel = () => this._destroyRipple(rippleRef);

// In some cases where there's a higher load on the browser, it can choose not to dispatch
// neither `transitionend` nor `transitioncancel` (see b/227356674). This timer serves as a
// fallback for such cases so that the ripple doesn't become stuck. We add a 100ms buffer
// because timers aren't precise. Note that another approach can be to transition the ripple
// to the `VISIBLE` state immediately above and to `FADING_IN` afterwards inside
// `transitionstart`. We go with the timer because it's one less event listener and
// it's less likely to break existing tests.
const fallbackTimer = setTimeout(onTransitionCancel, enterDuration + 100);

ripple.addEventListener('transitionend', onTransitionEnd);
// If the transition is cancelled (e.g. due to DOM removal), we destroy the ripple
// directly as otherwise we would keep it part of the ripple container forever.
// https://www.w3.org/TR/css-transitions-1/#:~:text=no%20longer%20in%20the%20document.
ripple.addEventListener('transitioncancel', onTransitionCancel);
eventListeners = {onTransitionEnd, onTransitionCancel};
eventListeners = {onTransitionEnd, onTransitionCancel, fallbackTimer};
});
}

Expand Down Expand Up @@ -352,6 +370,9 @@ export class RippleRenderer implements EventListenerObject {
if (eventListeners !== null) {
rippleRef.element.removeEventListener('transitionend', eventListeners.onTransitionEnd);
rippleRef.element.removeEventListener('transitioncancel', eventListeners.onTransitionCancel);
if (eventListeners.fallbackTimer !== null) {
clearTimeout(eventListeners.fallbackTimer);
}
}
rippleRef.element.remove();
}
Expand Down
4 changes: 2 additions & 2 deletions src/material/slider/slider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -691,8 +691,8 @@ describe('MDC-based MatSlider', () => {
it('should show the active ripple on pointerdown', fakeAsync(() => {
expect(isRippleVisible('active')).toBeFalse();
pointerdown();
flush();
expect(isRippleVisible('active')).toBeTrue();
flush();
}));

it('should hide the active ripple on pointerup', fakeAsync(() => {
Expand Down Expand Up @@ -1831,7 +1831,7 @@ function setValueByClick(
input.focus();
dispatchPointerEvent(inputElement, 'pointerup', x, y);
dispatchEvent(input._hostElement, new Event('change'));
tick();
flush();
}

/** Slides the MatSlider's thumb to the given value. */
Expand Down

0 comments on commit 73f2fa3

Please sign in to comment.