Skip to content

Commit 9dd6e13

Browse files
authored
Merge pull request #445 from kleinfreund/fix-shadow-dom-handling
Shadow DOM: Stop callbacks from inside an open shadow tree
2 parents 6254001 + 49580b9 commit 9dd6e13

File tree

3 files changed

+67
-2
lines changed

3 files changed

+67
-2
lines changed

mousetrap.js

+14
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,20 @@
982982
return false;
983983
}
984984

985+
// Events originating from a shadow DOM are re-targetted and `e.target` is the shadow host,
986+
// not the initial event target in the shadow tree. Note that not all events cross the
987+
// shadow boundary.
988+
// For shadow trees with `mode: 'open'`, the initial event target is the first element in
989+
// the event’s composed path. For shadow trees with `mode: 'closed'`, the initial event
990+
// target cannot be obtained.
991+
if ('composedPath' in e && typeof e.composedPath === 'function') {
992+
// For open shadow trees, update `element` so that the following check works.
993+
var initialEventTarget = e.composedPath()[0];
994+
if (initialEventTarget !== e.target) {
995+
element = initialEventTarget;
996+
}
997+
}
998+
985999
// stop for input, select, and textarea
9861000
return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
9871001
};

tests/libs/key-event.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646

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

62+
if (options === undefined) {
63+
options = {};
64+
}
65+
66+
// Re-target the element so that `event.target` becomes the shadow host. See:
67+
// https://developers.google.com/web/fundamentals/web-components/shadowdom#events
68+
// This is a bit of a lie because true events would re-target the event target both for
69+
// closed and open shadow trees. `KeyEvent` is not a true event and will fire the event
70+
// directly from the shadow host for closed shadow trees. For open trees, this would make
71+
// the tests fail as the actual event that will be eventually dispatched would have an
72+
// incorrect `Event.composedPath()` starting with the shadow host instead of the
73+
// initial event target.
74+
if (options.shadowHost && options.shadowHost.shadowRoot === null) {
75+
// closed shadow dom
76+
element = options.shadowHost;
77+
}
78+
6279
var modifierToKeyCode = {
6380
'shift': 16,
6481
'ctrl': 17,

tests/test.mousetrap.js

+35-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,41 @@ describe('Mousetrap.bind', function () {
8787
}
8888
});
8989

90-
it('keyup events should fire', function () {
90+
it('z key does not fire when inside an input element in an open shadow dom', function() {
91+
var spy = sinon.spy();
92+
93+
var shadowHost = document.createElement('div');
94+
var shadowRoot = shadowHost.attachShadow({ mode: 'open' });
95+
document.body.appendChild(shadowHost);
96+
97+
var inputElement = document.createElement('input');
98+
shadowRoot.appendChild(inputElement);
99+
expect(shadowHost.shadowRoot).to.equal(shadowRoot, 'shadow root accessible');
100+
101+
Mousetrap.bind('z', spy);
102+
KeyEvent.simulate('Z'.charCodeAt(0), 90, [], inputElement, 1, { shadowHost: shadowHost });
103+
document.body.removeChild(shadowHost);
104+
expect(spy.callCount).to.equal(0, 'callback should not have fired');
105+
});
106+
107+
it('z key does fire when inside an input element in a closed shadow dom', function() {
108+
var spy = sinon.spy();
109+
110+
var shadowHost = document.createElement('div');
111+
var shadowRoot = shadowHost.attachShadow({ mode: 'closed' });
112+
document.body.appendChild(shadowHost);
113+
114+
var inputElement = document.createElement('input');
115+
shadowRoot.appendChild(inputElement);
116+
expect(shadowHost.shadowRoot).to.equal(null, 'shadow root unaccessible');
117+
118+
Mousetrap.bind('z', spy);
119+
KeyEvent.simulate('Z'.charCodeAt(0), 90, [], inputElement, 1, { shadowHost: shadowHost });
120+
document.body.removeChild(shadowHost);
121+
expect(spy.callCount).to.equal(1, 'callback should have fired once');
122+
});
123+
124+
it('keyup events should fire', function() {
91125
var spy = sinon.spy();
92126

93127
Mousetrap.bind('z', spy, 'keyup');

0 commit comments

Comments
 (0)