How do we detect if a shadow root was made with v0

2019-02-25 10:41发布

问题:

Suppose a JS module exports shadowRoot which was created with either el.createShadowRoot or el.attachShadow (we don't know which). How do we detect if the root is a v0 shadow root or a v1 shadow root (i.e. how do we detect which method was used to create the root)?

f.e., What would I fill in the following conditional statements?

// for argument's sake, we don't create the root, we only get a reference
// to it:
import shadowRoot from 'somewhere'

function getShadowRootVersion(root) {
    if ( ... )
        return 'v0'

    if ( ... )
        return 'v1'
}

console.log(getShadowRootVersion(shadowRoot)) // should output "v0" or "v1".

More info:

We want to find out if a shadow root was created from createShadowRoot or from attachShadow. The resulting roots are different: in the root created with createShadowRoot, <content> elements are used for distributing elements. In roots created with attachShadow, <content> elements don't do anything, and <slot> elements are used instead. How do we detect which method was used to create a root (i.e. whether we have a v0 root or a v1 root)?

回答1:

I went a similar direction to Hayato Ito's answer. However, instead of creating slot elements, I target content elements. I was not able to find a way to detect the version off of any API method detection.

I targeted content elements since content elements do not natively have events for them, unlike slotchange on the slot event, which hopefully could lead to a small performance boost. Plus the function returns v1 a little faster if the browser does not support v0 at all.

function shadowType(shadowRoot) {
    if (!shadowRoot) {
        // closed shadow dom does not appear to have a shadowRoot...
        // It could be assumed that it is v1, but for now return undefined
        return;
    }

    const content = document.createElement('content');
    // In browsers that support v1, but not v0 (ex: Safari)
    if (!content.getDistributedNodes) {
        return 'v1';
    }

    content.setAttribute('select', 'test-shadow-dom-version');
    shadowRoot.appendChild(content);

    const testElement = document.createElement('test-shadow-dom-version');
    shadowRoot.host.appendChild(testElement);
    const type = (content.getDistributedNodes().length) ? 'v0' : 'v1';
    shadowRoot.removeChild(content);
    shadowRoot.host.removeChild(testElement);

    return type;
}

It definitely feels like a "hack", because of need to append random dom :(. I did test this in Chrome, Firefox, Safari, IE11, and Edge. I tested components made using the webcomponentsjs (v0) polyfill, and it correctly returned v0 for each component. I also tested those same browsers with just the shadydom (v1) polyfill with components created with the v1 spec, and received v1 in all of those browsers.



回答2:

The following hack should work:

function isV1(shadowRoot) {
  const slot = document.createElement('slot');
  shadowRoot.appendChild(slot);
  slot.appendChild(document.createElement('div'));
  const assignedNodes = slot.assignedNodes({ flatten: true });
  slot.remove();
  return assignedNodes.length !== 0;
}

IMO, there is something wrong when you have to detect it.