Skip to content

Commit 5834723

Browse files
committed
observe shadow root child nodes
1 parent 7c3baec commit 5834723

File tree

2 files changed

+93
-1
lines changed

2 files changed

+93
-1
lines changed

inert.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,44 @@ class InertRoot {
6969
// Make all focusable elements in the subtree unfocusable and add them to _managedNodes
7070
this._makeSubtreeUnfocusable(this._rootElement);
7171

72+
const boundOnMutation = this._onMutation.bind(this);
7273
// Watch for:
7374
// - any additions in the subtree: make them unfocusable too
7475
// - any removals from the subtree: remove them from this inert root's managed nodes
7576
// - attribute changes: if `tabindex` is added, or removed from an intrinsically focusable element,
7677
// make that node a managed node.
77-
this._observer = new MutationObserver(this._onMutation.bind(this));
78+
this._observer = new MutationObserver(boundOnMutation);
7879
this._observer.observe(this._rootElement, { attributes: true, childList: true, subtree: true });
80+
81+
// Watch for:
82+
// - any additions in the shadowRoot subtree: make them unfocusable too
83+
// - any removals from the shadowRoot subtree: remove them from this inert root's managed nodes
84+
const rootObserver = new MutationObserver(boundOnMutation);
85+
const shadowRoot = this._rootElement.shadowRoot || this._rootElement.webkitShadowRoot;
86+
if (shadowRoot) {
87+
rootObserver.observe(shadowRoot, { childList: true, subtree: true });
88+
} else {
89+
// Might create a shadowRoot in the future, either by attachShadow
90+
// (ShadowDOM v1) or by createShadowRoot (ShadowDOM v0), so we patch both
91+
// of them.
92+
const attachShadowFn = this._rootElement.attachShadow;
93+
if (attachShadowFn) {
94+
this._rootElement.attachShadow = function patchedAttachShadow() {
95+
const shadowRoot = attachShadowFn.apply(this, arguments);
96+
rootObserver.observe(shadowRoot, { childList: true, subtree: true });
97+
return shadowRoot;
98+
};
99+
}
100+
const createShadowRootFn = this._rootElement.createShadowRoot;
101+
if (createShadowRootFn) {
102+
this._rootElement.createShadowRoot = function patchedCreateShadowRoot() {
103+
const shadowRoot = createShadowRootFn.apply(this, arguments);
104+
rootObserver.observe(shadowRoot, { childList: true, subtree: true });
105+
return shadowRoot;
106+
};
107+
}
108+
}
109+
this._rootObserver = rootObserver;
79110
}
80111

81112
/**
@@ -86,6 +117,9 @@ class InertRoot {
86117
this._observer.disconnect();
87118
delete this._observer;
88119

120+
this._rootObserver.disconnect();
121+
delete this._rootObserver;
122+
89123
if (this._rootElement)
90124
this._rootElement.removeAttribute('aria-hidden');
91125
delete this._rootElement;

test/index.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,34 @@ describe('Basic', function() {
149149
expect(isUnfocusable(shadowButton)).to.equal(true);
150150
});
151151

152+
it('should apply to elements added to shadow trees later in time', function(done) {
153+
host.inert = true;
154+
const shadowButton = document.createElement('button');
155+
shadowButton.textContent = 'Shadow button';
156+
host.shadowRoot.appendChild(shadowButton);
157+
// Give time to mutation observers.
158+
setTimeout(function() {
159+
expect(isUnfocusable(shadowButton)).to.equal(true);
160+
done();
161+
});
162+
});
163+
164+
it('should apply to elements that create shadow tree later in time', function(done) {
165+
fixture.removeChild(host);
166+
host = document.createElement('div');
167+
fixture.appendChild(host);
168+
host.inert = true;
169+
const shadowRoot = host.createShadowRoot();
170+
const shadowButton = document.createElement('button');
171+
shadowButton.textContent = 'Shadow button';
172+
shadowRoot.appendChild(shadowButton);
173+
// Give time to mutation observers.
174+
setTimeout(function() {
175+
expect(isUnfocusable(shadowButton)).to.equal(true);
176+
done();
177+
});
178+
});
179+
152180
it('should apply inert styles inside shadow trees', function() {
153181
const shadowButton = document.createElement('button');
154182
shadowButton.textContent = 'Shadow button';
@@ -196,6 +224,36 @@ describe('Basic', function() {
196224
expect(isUnfocusable(shadowButton)).to.equal(true);
197225
});
198226

227+
it('should apply to elements added to shadow trees later in time', function(done) {
228+
host.inert = true;
229+
const shadowButton = document.createElement('button');
230+
shadowButton.textContent = 'Shadow button';
231+
host.shadowRoot.appendChild(shadowButton);
232+
// Give time to mutation observers.
233+
setTimeout(function() {
234+
expect(isUnfocusable(shadowButton)).to.equal(true);
235+
done();
236+
});
237+
});
238+
239+
it('should apply to elements that create shadow tree later in time', function(done) {
240+
fixture.removeChild(host);
241+
host = document.createElement('div');
242+
fixture.appendChild(host);
243+
host.inert = true;
244+
const shadowRoot = host.attachShadow({
245+
mode: 'open'
246+
});
247+
const shadowButton = document.createElement('button');
248+
shadowButton.textContent = 'Shadow button';
249+
shadowRoot.appendChild(shadowButton);
250+
// Give time to mutation observers.
251+
setTimeout(function() {
252+
expect(isUnfocusable(shadowButton)).to.equal(true);
253+
done();
254+
});
255+
});
256+
199257
it('should apply inert styles inside shadow trees', function() {
200258
const shadowButton = document.createElement('button');
201259
shadowButton.textContent = 'Shadow button';

0 commit comments

Comments
 (0)