Skip to content

Commit

Permalink
observe shadow root child nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
valdrinkoshi committed Oct 26, 2016
1 parent dcf2e4d commit f6bbc2f
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 1 deletion.
36 changes: 35 additions & 1 deletion inert.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,44 @@ class InertRoot {
// Make all focusable elements in the subtree unfocusable and add them to _managedNodes
this._makeSubtreeUnfocusable(this._rootElement);

const boundOnMutation = this._onMutation.bind(this);
// Watch for:
// - any additions in the subtree: make them unfocusable too
// - any removals from the subtree: remove them from this inert root's managed nodes
// - attribute changes: if `tabindex` is added, or removed from an intrinsically focusable element,
// make that node a managed node.
this._observer = new MutationObserver(this._onMutation.bind(this));
this._observer = new MutationObserver(boundOnMutation);
this._observer.observe(this._rootElement, { attributes: true, childList: true, subtree: true });

// Watch for:
// - any additions in the shadowRoot subtree: make them unfocusable too
// - any removals from the shadowRoot subtree: remove them from this inert root's managed nodes
const rootObserver = new MutationObserver(boundOnMutation);
const shadowRoot = this._rootElement.shadowRoot || this._rootElement.webkitShadowRoot;
if (shadowRoot) {
rootObserver.observe(shadowRoot, { childList: true, subtree: true });
} else {
// Might create a shadowRoot in the future, either by attachShadow
// (ShadowDOM v1) or by createShadowRoot (ShadowDOM v0), so we patch both
// of them.
const attachShadowFn = this._rootElement.attachShadow;
if (attachShadowFn) {
this._rootElement.attachShadow = function patchedAttachShadow() {
const shadowRoot = attachShadowFn.apply(this, arguments);
rootObserver.observe(shadowRoot, { childList: true, subtree: true });
return shadowRoot;
};
}
const createShadowRootFn = this._rootElement.createShadowRoot;
if (createShadowRootFn) {
this._rootElement.createShadowRoot = function patchedCreateShadowRoot() {
const shadowRoot = createShadowRootFn.apply(this, arguments);
rootObserver.observe(shadowRoot, { childList: true, subtree: true });
return shadowRoot;
};
}
}
this._rootObserver = rootObserver;
}

/**
Expand All @@ -86,6 +117,9 @@ class InertRoot {
this._observer.disconnect();
this._observer = null;

this._rootObserver.disconnect();
delete this._rootObserver;

if (this._rootElement)
this._rootElement.removeAttribute('aria-hidden');
this._rootElement = null;
Expand Down
58 changes: 58 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,34 @@ describe('Basic', function() {
expect(isUnfocusable(shadowButton)).to.equal(true);
});

it('should apply to elements added to shadow trees later in time', function(done) {
host.inert = true;
const shadowButton = document.createElement('button');
shadowButton.textContent = 'Shadow button';
host.shadowRoot.appendChild(shadowButton);
// Give time to mutation observers.
setTimeout(function() {
expect(isUnfocusable(shadowButton)).to.equal(true);
done();
});
});

it('should apply to elements that create shadow tree later in time', function(done) {
fixture.removeChild(host);
host = document.createElement('div');
fixture.appendChild(host);
host.inert = true;
const shadowRoot = host.createShadowRoot();
const shadowButton = document.createElement('button');
shadowButton.textContent = 'Shadow button';
shadowRoot.appendChild(shadowButton);
// Give time to mutation observers.
setTimeout(function() {
expect(isUnfocusable(shadowButton)).to.equal(true);
done();
});
});

it('should apply inert styles inside shadow trees', function() {
const shadowButton = document.createElement('button');
shadowButton.textContent = 'Shadow button';
Expand Down Expand Up @@ -200,6 +228,36 @@ describe('Basic', function() {
expect(isUnfocusable(shadowButton)).to.equal(true);
});

it('should apply to elements added to shadow trees later in time', function(done) {
host.inert = true;
const shadowButton = document.createElement('button');
shadowButton.textContent = 'Shadow button';
host.shadowRoot.appendChild(shadowButton);
// Give time to mutation observers.
setTimeout(function() {
expect(isUnfocusable(shadowButton)).to.equal(true);
done();
});
});

it('should apply to elements that create shadow tree later in time', function(done) {
fixture.removeChild(host);
host = document.createElement('div');
fixture.appendChild(host);
host.inert = true;
const shadowRoot = host.attachShadow({
mode: 'open'
});
const shadowButton = document.createElement('button');
shadowButton.textContent = 'Shadow button';
shadowRoot.appendChild(shadowButton);
// Give time to mutation observers.
setTimeout(function() {
expect(isUnfocusable(shadowButton)).to.equal(true);
done();
});
});

it('should apply inert styles inside shadow trees', function() {
const shadowButton = document.createElement('button');
shadowButton.textContent = 'Shadow button';
Expand Down

0 comments on commit f6bbc2f

Please sign in to comment.