Skip to content

Commit

Permalink
Merge pull request #445 from kleinfreund/fix-shadow-dom-handling
Browse files Browse the repository at this point in the history
Shadow DOM: Stop callbacks from inside an open shadow tree
  • Loading branch information
ccampbell committed Mar 4, 2019
2 parents 6254001 + 49580b9 commit 9dd6e13
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 2 deletions.
14 changes: 14 additions & 0 deletions mousetrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,20 @@
return false;
}

// Events originating from a shadow DOM are re-targetted and `e.target` is the shadow host,
// not the initial event target in the shadow tree. Note that not all events cross the
// shadow boundary.
// For shadow trees with `mode: 'open'`, the initial event target is the first element in
// the event’s composed path. For shadow trees with `mode: 'closed'`, the initial event
// target cannot be obtained.
if ('composedPath' in e && typeof e.composedPath === 'function') {
// For open shadow trees, update `element` so that the following check works.
var initialEventTarget = e.composedPath()[0];
if (initialEventTarget !== e.target) {
element = initialEventTarget;
}
}

// stop for input, select, and textarea
return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
};
Expand Down
19 changes: 18 additions & 1 deletion tests/libs/key-event.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

// simulates complete key event as if the user pressed the key in the browser
// triggers a keydown, then a keypress, then a keyup
KeyEvent.simulate = function(charCode, keyCode, modifiers, element, repeat) {
KeyEvent.simulate = function(charCode, keyCode, modifiers, element, repeat, options) {
if (modifiers === undefined) {
modifiers = [];
}
Expand All @@ -59,6 +59,23 @@
repeat = 1;
}

if (options === undefined) {
options = {};
}

// Re-target the element so that `event.target` becomes the shadow host. See:
// https://developers.google.com/web/fundamentals/web-components/shadowdom#events
// This is a bit of a lie because true events would re-target the event target both for
// closed and open shadow trees. `KeyEvent` is not a true event and will fire the event
// directly from the shadow host for closed shadow trees. For open trees, this would make
// the tests fail as the actual event that will be eventually dispatched would have an
// incorrect `Event.composedPath()` starting with the shadow host instead of the
// initial event target.
if (options.shadowHost && options.shadowHost.shadowRoot === null) {
// closed shadow dom
element = options.shadowHost;
}

var modifierToKeyCode = {
'shift': 16,
'ctrl': 17,
Expand Down
36 changes: 35 additions & 1 deletion tests/test.mousetrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,41 @@ describe('Mousetrap.bind', function () {
}
});

it('keyup events should fire', function () {
it('z key does not fire when inside an input element in an open shadow dom', function() {
var spy = sinon.spy();

var shadowHost = document.createElement('div');
var shadowRoot = shadowHost.attachShadow({ mode: 'open' });
document.body.appendChild(shadowHost);

var inputElement = document.createElement('input');
shadowRoot.appendChild(inputElement);
expect(shadowHost.shadowRoot).to.equal(shadowRoot, 'shadow root accessible');

Mousetrap.bind('z', spy);
KeyEvent.simulate('Z'.charCodeAt(0), 90, [], inputElement, 1, { shadowHost: shadowHost });
document.body.removeChild(shadowHost);
expect(spy.callCount).to.equal(0, 'callback should not have fired');
});

it('z key does fire when inside an input element in a closed shadow dom', function() {
var spy = sinon.spy();

var shadowHost = document.createElement('div');
var shadowRoot = shadowHost.attachShadow({ mode: 'closed' });
document.body.appendChild(shadowHost);

var inputElement = document.createElement('input');
shadowRoot.appendChild(inputElement);
expect(shadowHost.shadowRoot).to.equal(null, 'shadow root unaccessible');

Mousetrap.bind('z', spy);
KeyEvent.simulate('Z'.charCodeAt(0), 90, [], inputElement, 1, { shadowHost: shadowHost });
document.body.removeChild(shadowHost);
expect(spy.callCount).to.equal(1, 'callback should have fired once');
});

it('keyup events should fire', function() {
var spy = sinon.spy();

Mousetrap.bind('z', spy, 'keyup');
Expand Down

0 comments on commit 9dd6e13

Please sign in to comment.