Skip to content
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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

use ShadowDOM v1 #49

wants to merge 13 commits into from

Conversation

valdrinkoshi
Copy link
Collaborator

@valdrinkoshi valdrinkoshi commented Mar 23, 2017

Implementation of #48, supporting only native ShadowDOM v1.
Test page with inert and blockingElements - works on Chrome and Safari 🎉 http://jsbin.com/hohegoj/6/edit?html,output

We 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, and InertRoot 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.

@valdrinkoshi
Copy link
Collaborator Author

@alice @robdodson synced with master, and finally tests are green everywhere! 💪 💚
Mind taking a look?

Copy link
Member

@alice alice left a 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:
*
Copy link
Member

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;
Copy link
Member

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.
Copy link
Member

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).
Copy link
Member

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);
Copy link
Member

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') {
Copy link
Member

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);

Copy link
Member

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);
Copy link
Member

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() {
Copy link
Member

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?

Copy link
Collaborator Author

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 😁

Copy link
Member

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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) {
Copy link
Member

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?

Copy link
Collaborator Author

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?

Copy link
Member

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.

Copy link
Collaborator Author

@valdrinkoshi valdrinkoshi left a 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.
Copy link
Collaborator Author

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) {
Copy link
Collaborator Author

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() {
Copy link
Collaborator Author

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 😁

Copy link
Collaborator Author

@valdrinkoshi valdrinkoshi left a 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() {
Copy link
Collaborator Author

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.

@yoavweiss yoavweiss changed the base branch from master to main March 5, 2021 21:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants