-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
use ShadowDOM v1 #49
base: main
Are you sure you want to change the base?
use ShadowDOM v1 #49
Conversation
@alice @robdodson synced with master, and finally tests are green everywhere! 💪 💚 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies for taking a million years to get to this! This looks very promising but I don't fully understand it - see comments inline.
src/inert.js
Outdated
* has an `inert` attribute. | ||
* | ||
* Its main functions are: | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these points are now just a single line, we can probably get rid of the extra blank lines in between.
src/inert.js
Outdated
// https://html.spec.whatwg.org/multipage/scripting.html#valid-custom-element-name | ||
if (!matches(rootElement, acceptsShadowRootSelector)) { | ||
const potentialCustomElement = rootElement.tagName.indexOf('-') !== -1; | ||
if (!potentialCustomElement) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we stick with Chromium style and put the return on a new line?
https://chromium.googlesource.com/chromium/src/+/master/styleguide/web/web.md#Style
src/inert.js
Outdated
// We already failed inerting this shadow root. | ||
if (rootElement.__failedAttachShadow) return; | ||
|
||
// Force shadowRoot. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite understand this comment, could you flesh it out a little?
|
||
// Force shadowRoot. | ||
if (!rootElement.shadowRoot) { | ||
// Detect if this is a closed shadowRoot with try/catch (sigh). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we scope the try/catch more tightly? e.g.
let shadowRoot = null;
try {
shadowRoot = rootElement.attachShadow({mode: 'open'});
} catch (e) {
// Most likely a closed shadowRoot was already attached.
rootElement.__failedAttachShadow = true;
console.warn('Could not inert element shadowRoot', rootElement, e);
return;
}
shadowRoot.appendChild(document.createElement('slot'));
rootElement.attachShadow = function() {
const slot = this.shadowRoot.querySelector('slot');
slot && this.shadowRoot.removeChild(slot);
delete this.attachShadow;
return this.shadowRoot;
};
src/inert.js
Outdated
// for polyfilling inert. We ensure the shadowRoot is empty and return it. | ||
rootElement.attachShadow = function() { | ||
const slot = this.shadowRoot.querySelector('slot'); | ||
slot && this.shadowRoot.removeChild(slot); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there some reason not to just use an if statement here?
src/inert.js
Outdated
} | ||
} | ||
if (typeof onShadowRootMutation === 'function') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not to just explicitly check != null
?
src/inert.js
Outdated
* @type {MutationObserver} | ||
*/ | ||
this._observer = new MutationObserver(this.watchForInert); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mysterious extra line
src/inert.js
Outdated
*/ | ||
this._inertRoots = new Map(); | ||
|
||
this.watchForInert = this._watchForInert.bind(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this "private"? Maybe call it something like this._boundWatchForInert
?
@@ -127,9 +124,9 @@ describe('Basic', function() { | |||
}); | |||
}); | |||
|
|||
describe('ShadowDOM v0', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we simply won't support v0 at all any more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that was the simplistic idea explored by this PR 😁
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it support browsers which don't support shadow DOM at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the current form, no. We'd have to adopt the previous technique of searching focusable nodes inside an inert node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be more precise: this case is currently not supported by this PR:
<div inert>
<input>
</div>
^ the input will still be reachable via TAB
HTMLElement.prototype.focus = function() { | ||
// If it is inert or into an inert node, no focus! | ||
let target = this; | ||
while (target && !target.inert) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this checks for inertness when focus is attempted? If we're tabbing through a document, and traverse into an inert subtree, how do we know to skip over it and look for the next focusable element?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This override is meant for when focus is forced programmatically (e.g. myElement.focus()
), not via user interaction.
On user interactions, there is no situation where the focus would move to an inert node, as we set tabindex=-1
to them. ShadowDom behavior guarantees that these nodes and their content are removed from the tab order.
Did you find any case where this happens on user interactions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhh I see. No, that makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed the feedback in the last commit 👌
src/inert.js
Outdated
return; | ||
} | ||
} else { | ||
// Ensure inerted elements in the shadowRoot have the property updated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be that rootElement had inert children in its shadowRoot and this is the first time we see them, hence we have to update their inert
property.
HTMLElement.prototype.focus = function() { | ||
// If it is inert or into an inert node, no focus! | ||
let target = this; | ||
while (target && !target.inert) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This override is meant for when focus is forced programmatically (e.g. myElement.focus()
), not via user interaction.
On user interactions, there is no situation where the focus would move to an inert node, as we set tabindex=-1
to them. ShadowDom behavior guarantees that these nodes and their content are removed from the tab order.
Did you find any case where this happens on user interactions?
@@ -127,9 +124,9 @@ describe('Basic', function() { | |||
}); | |||
}); | |||
|
|||
describe('ShadowDOM v0', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that was the simplistic idea explored by this PR 😁
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alice this PR was made to see how it would look like if we could rely solely on native ShadowDOM.
To support the browsers without native ShadowDOM, we can use the old technique. Would it make sense to handle both cases in the same file? I was thinking to ship a separate file inert-sd.js
for browsers with native ShadowDOM in order to deliver a smaller payload. WDYT?
@@ -127,9 +124,9 @@ describe('Basic', function() { | |||
}); | |||
}); | |||
|
|||
describe('ShadowDOM v0', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the current form, no. We'd have to adopt the previous technique of searching focusable nodes inside an inert node.
Implementation of #48, supporting only native ShadowDOM v1.
Test page with
inert
andblockingElements
- works on Chrome and Safari 🎉 http://jsbin.com/hohegoj/6/edit?html,outputWe override
HTMLElement.prototype.focus
which will do nothing if the node is inert or contained in an inert parent node.InertManager
now keeps track of all inerted nodes: it observes mutations on the light dom, andInertRoot
just provides to the manager a way to observe shadowRoot content.Like this, the manager has visibility on the whole document, even in the shadows!
Tests are 💚. We should figure what strategy to take in polyfilled land.