Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

fix(ripple): Re-flow logic to avoid crashing Edge #2542

Merged
merged 2 commits into from
Apr 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 30 additions & 12 deletions packages/mdc-ripple/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -330,25 +330,43 @@ class MDCRippleFoundation extends MDCFoundation {
this.registerDeactivationHandlers_(e);
}

activationState.wasElementMadeActive = this.checkElementMadeActive_(e);
if (activationState.wasElementMadeActive) {
this.animateActivation_();
}

requestAnimationFrame(() => {
// This needs to be wrapped in an rAF call b/c web browsers
// report active states inconsistently when they're called within
// event handling code:
// - https://bugs.chromium.org/p/chromium/issues/detail?id=635971
// - https://bugzilla.mozilla.org/show_bug.cgi?id=1293741
activationState.wasElementMadeActive = (e && e.type === 'keydown') ? this.adapter_.isSurfaceActive() : true;
if (activationState.wasElementMadeActive) {
this.animateActivation_();
} else {
// Reset array on next frame after the current event has had a chance to bubble to prevent ancestor ripples
activatedTargets = [];

if (!activationState.wasElementMadeActive && (e.key === ' ' || e.keyCode === 32)) {
// If space was pressed, try again within an rAF call to detect :active, because different UAs report
// active states inconsistently when they're called within event handling code:
// - https://bugs.chromium.org/p/chromium/issues/detail?id=635971
// - https://bugzilla.mozilla.org/show_bug.cgi?id=1293741
// We try first outside rAF to support Edge, which does not exhibit this problem, but will crash if a CSS
// variable is set within a rAF callback for a submit button interaction (#2241).
activationState.wasElementMadeActive = this.checkElementMadeActive_(e);
if (activationState.wasElementMadeActive) {
this.animateActivation_();
}
}

if (!activationState.wasElementMadeActive) {
// Reset activation state immediately if element was not made active.
this.activationState_ = this.defaultActivationState_();
}

// Reset array on next frame after the current event has had a chance to bubble to prevent ancestor ripples
activatedTargets = [];
});
}

/**
* @param {?Event} e
* @private
*/
checkElementMadeActive_(e) {
return (e && e.type === 'keydown') ? this.adapter_.isSurfaceActive() : true;
}

/**
* @param {?Event=} event Optional event containing position information.
*/
Expand Down
29 changes: 27 additions & 2 deletions test/unit/mdc-ripple/foundation-activation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,17 +176,42 @@ testFoundation('sets FG position from the coords to the center within surface on
));
});

testFoundation('adds activation classes on keydown when surface is made active',
testFoundation('adds activation classes on keydown when surface is made active on same frame',
({foundation, adapter, mockRaf}) => {
const handlers = captureHandlers(adapter, 'registerInteractionHandler');
td.when(adapter.isSurfaceActive()).thenReturn(true);
foundation.init();
mockRaf.flush();

handlers.keydown();
td.verify(adapter.addClass(cssClasses.FG_ACTIVATION));
});

testFoundation('adds activation classes on keydown when surface only reflects :active on next frame for space keydown',
({foundation, adapter, mockRaf}) => {
const handlers = captureHandlers(adapter, 'registerInteractionHandler');
td.when(adapter.isSurfaceActive()).thenReturn(false, true);
foundation.init();
mockRaf.flush();

td.verify(adapter.addClass(cssClasses.FG_ACTIVATION));
handlers.keydown({key: ' '});
td.verify(adapter.addClass(cssClasses.FG_ACTIVATION), {times: 0});

mockRaf.flush();
td.verify(adapter.addClass(cssClasses.FG_ACTIVATION), {times: 1});
});

testFoundation('does not add activation classes on keydown when surface is not made active',
({foundation, adapter, mockRaf}) => {
const handlers = captureHandlers(adapter, 'registerInteractionHandler');
td.when(adapter.isSurfaceActive()).thenReturn(false, false);
foundation.init();
mockRaf.flush();

handlers.keydown({key: ' '});
mockRaf.flush();

td.verify(adapter.addClass(cssClasses.FG_ACTIVATION), {times: 0});
});

testFoundation('sets FG position to center on non-pointer activation', ({foundation, adapter, mockRaf}) => {
Expand Down