3

The HTML spec describes several kinds of elements stored in the CustomElementRegistry.

  1. Builtin elements like <a> that are represented by a builtin subclass of HTMLElement like HTMLAnchorElement.
  2. Autonomous custom elements like <my-element> which extend HTMLElement and register with the CustomElementRegistry.
  3. Customized builtin elements like <a is="my-link"> which extend a subclass of HTMLElement and use the is="customized-tag-name" to specify the class.
  4. Legacy elements like <isindex> which extend HTMLUnknownElement
  5. Customizable elements that have not (yet?) been registered perhaps due to delayed script loading.

Is there a way to figure, at runtime, which bucket an element belongs to?

1 Answer 1

1

The below [jsbin] kind of does the job and manages without testing constructors for "[native code]" but there's got to be a better way to tell whether an element is builtin.

//<script>

// https://w3c.github.io/webcomponents/spec/custom/#valid-custom-element-name
var VALID_CUSTOM_ELEMENT_NAME_REGEX = /^(?!(?:annotation-xml|color-profile|font-face|font-face(?:-(?:src|uri|format|name))?|missing-glyph)$)[a-z][a-z.0-9_\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u200C\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD]*-[\-a-z.0-9_\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u200C\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD]*$/;

function classify(el) {
  var name = el.localName;
  // TODO: are elements from a different document dealt with by that
  // document's CustomElementRegistry?
  // How do we get from a document to a CustomElementRegistry?
  // Maybe just assert el.ownerDocument === document?
  // Does that screw up elements from <template> content.
  if (customElements.get(name)) { return 'custom'; }
  // TODO: check that the value of is="..." is registered
  if (el.getAttribute('is')) { return 'custom-builtin'; }
  var ctor = el.constructor;
  if (ctor === HTMLUnknownElement) { return 'legacy'; }
  if (ctor === HTMLElement) {
    if (VALID_CUSTOM_ELEMENT_NAME_REGEX.test(name)) {
      return 'customizable';
    }
  }
  return 'builtin';
}


// A bunch of element names from MDN and elsewhere to test
var names = [
  "a", "abbr", "acronym", "address", "applet",
  "annotation-xml", "area",
  "article", "aside", "audio", "b", "base", "basefont", "bdi", "bdo",
  "bgsound", "big", "blink", "blockquote", "body", "br", "button",
  "canvas", "caption", "center", "cite", "code", "col", "colgroup",
  "command", "content", "data", "datalist", "dd", "del", "details",
  "dfn", "dialog", "dir", "div", "dl", "dt", "element", "em", "embed",
  "fieldset", "figcaption", "figure", "font", "footer", "form",
  "frame", "frameset", "h1", "h6", "head", "header", "hgroup", "hr",
  "html", "i", "iframe", "image", "img", "input", "ins", "isindex",
  "kbd", "keygen", "label", "legend", "li", "link", "listing", "main",
  "map", "mark", "marquee", "menu", "menuitem", "meta", "meter",
  "multicol", "nav", "nobr", "noembed", "noframes", "noscript",
  "object", "ol", "optgroup", "option", "output", "p", "param",
  "picture", "plaintext", "pre", "progress", "q", "rp", "rt", "rtc",
  "ruby", "s", "samp", "script", "section", "select", "shadow",
  "slot", "small", "source", "spacer", "span", "strike", "strong",
  "style", "sub", "summary", "sup", "table", "tbody", "td",
  "template", "textarea", "tfoot", "th", "thead", "time", "title",
  "tr", "track", "tt", "u", "ul", "var", "video", "wbr", "xmp",
  "isindex",  // Known legacy element
  "un-registered", "reg-istered"];  // Test custom / customizable

//</script><script>

class MyElement extends HTMLElement {
  constructor() {
    super();
  }
}

customElements.define('reg-istered', MyElement);

//</script><script>
var table = document.createElement('table');
document.body.appendChild(table);
var tbody = document.createElement('tbody');
table.appendChild(tbody);
function addRowForElement(el) {
  var tr = document.createElement('tr');
  tbody.appendChild(tr);
  var td0 = document.createElement('td');
  tr.appendChild(td0);
  td0.appendChild(document.createTextNode(el.outerHTML));
  var td1 = document.createElement('td');
  tr.appendChild(td1);
  td1.appendChild(document.createTextNode('' + classify(el)));
}
for (var i = 0, n = names.length; i < n; ++i) {
  addRowForElement(document.createElement(names[i]));
}
var customBi = document.createElement('input');
customBi.setAttribute('is', 'my-custom-input');  // Not yet registered.
addRowForElement(customBi);
//</script><style>td:first-child { font-family: monospace }</style>
Sign up to request clarification or add additional context in comments.

1 Comment

(customElements.get()) will also return true for a registered customized built-in element.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.