Skip to content

Commit

Permalink
Implement HTMLElement.attachInternals()
Browse files Browse the repository at this point in the history
  • Loading branch information
zjffun committed Jun 2, 2023
1 parent a39e0ec commit 088eb44
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class CustomElementRegistryImpl {

this._elementDefinitionIsRunning = true;

let disableInternals = false;
let disableShadow = false;
let observedAttributes = [];
const lifecycleCallbacks = {
Expand Down Expand Up @@ -167,6 +168,7 @@ class CustomElementRegistryImpl {
disabledFeatures = convertToSequenceDOMString(disabledFeaturesIterable);
}

disableInternals = disabledFeatures.includes("internals");
disableShadow = disabledFeatures.includes("shadow");
} catch (err) {
caughtError = err;
Expand All @@ -185,6 +187,7 @@ class CustomElementRegistryImpl {
objectReference: ctor,
observedAttributes,
lifecycleCallbacks,
disableInternals,
disableShadow,
constructionStack: []
};
Expand Down
1 change: 1 addition & 0 deletions lib/jsdom/living/interfaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ const generatedInterfaces = {
Storage: require("./generated/Storage"),

CustomElementRegistry: require("./generated/CustomElementRegistry"),
ElementInternals: require("./generated/ElementInternals"),
ShadowRoot: require("./generated/ShadowRoot"),

MutationObserver: require("./generated/MutationObserver"),
Expand Down
7 changes: 7 additions & 0 deletions lib/jsdom/living/nodes/ARIAMixin-impl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";

class ARIAMixinImpl {}

module.exports = {
implementation: ARIAMixinImpl
};
52 changes: 52 additions & 0 deletions lib/jsdom/living/nodes/ARIAMixin.webidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
interface mixin ARIAMixin {
[CEReactions] attribute DOMString? role;
[CEReactions] attribute Element? ariaActiveDescendantElement;
[CEReactions] attribute DOMString? ariaAtomic;
[CEReactions] attribute DOMString? ariaAutoComplete;
[CEReactions] attribute DOMString? ariaBusy;
[CEReactions] attribute DOMString? ariaChecked;
[CEReactions] attribute DOMString? ariaColCount;
[CEReactions] attribute DOMString? ariaColIndex;
[CEReactions] attribute DOMString? ariaColIndexText;
[CEReactions] attribute DOMString? ariaColSpan;
[CEReactions] attribute FrozenArray<Element>? ariaControlsElements;
[CEReactions] attribute DOMString? ariaCurrent;
[CEReactions] attribute FrozenArray<Element>? ariaDescribedByElements;
[CEReactions] attribute DOMString? ariaDescription;
[CEReactions] attribute FrozenArray<Element>? ariaDetailsElements;
[CEReactions] attribute DOMString? ariaDisabled;
[CEReactions] attribute FrozenArray<Element>? ariaErrorMessageElements;
[CEReactions] attribute DOMString? ariaExpanded;
[CEReactions] attribute FrozenArray<Element>? ariaFlowToElements;
[CEReactions] attribute DOMString? ariaHasPopup;
[CEReactions] attribute DOMString? ariaHidden;
[CEReactions] attribute DOMString? ariaInvalid;
[CEReactions] attribute DOMString? ariaKeyShortcuts;
[CEReactions] attribute DOMString? ariaLabel;
[CEReactions] attribute FrozenArray<Element>? ariaLabelledByElements;
[CEReactions] attribute DOMString? ariaLevel;
[CEReactions] attribute DOMString? ariaLive;
[CEReactions] attribute DOMString? ariaModal;
[CEReactions] attribute DOMString? ariaMultiLine;
[CEReactions] attribute DOMString? ariaMultiSelectable;
[CEReactions] attribute DOMString? ariaOrientation;
[CEReactions] attribute FrozenArray<Element>? ariaOwnsElements;
[CEReactions] attribute DOMString? ariaPlaceholder;
[CEReactions] attribute DOMString? ariaPosInSet;
[CEReactions] attribute DOMString? ariaPressed;
[CEReactions] attribute DOMString? ariaReadOnly;

[CEReactions] attribute DOMString? ariaRequired;
[CEReactions] attribute DOMString? ariaRoleDescription;
[CEReactions] attribute DOMString? ariaRowCount;
[CEReactions] attribute DOMString? ariaRowIndex;
[CEReactions] attribute DOMString? ariaRowIndexText;
[CEReactions] attribute DOMString? ariaRowSpan;
[CEReactions] attribute DOMString? ariaSelected;
[CEReactions] attribute DOMString? ariaSetSize;
[CEReactions] attribute DOMString? ariaSort;
[CEReactions] attribute DOMString? ariaValueMax;
[CEReactions] attribute DOMString? ariaValueMin;
[CEReactions] attribute DOMString? ariaValueNow;
[CEReactions] attribute DOMString? ariaValueText;
};
12 changes: 12 additions & 0 deletions lib/jsdom/living/nodes/ElementInternals-impl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use strict";

const { mixin } = require("../../utils");
const ARIAMixinImpl = require("./ARIAMixin-impl").implementation;

class ElementInternals {}

mixin(ElementInternals.prototype, ARIAMixinImpl.prototype);

module.exports = {
implementation: ElementInternals
};
39 changes: 39 additions & 0 deletions lib/jsdom/living/nodes/ElementInternals.webidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals
[Exposed=Window]
interface ElementInternals {
// Shadow root access
readonly attribute ShadowRoot? shadowRoot;

// Form-associated custom elements
// undefined setFormValue((File or USVString or FormData)? value,
// optional (File or USVString or FormData)? state);

readonly attribute HTMLFormElement? form;

// undefined setValidity(optional ValidityStateFlags flags = {},
// optional DOMString message,
// optional HTMLElement anchor);
readonly attribute boolean willValidate;
readonly attribute ValidityState validity;
readonly attribute DOMString validationMessage;
boolean checkValidity();
boolean reportValidity();

readonly attribute NodeList labels;
};

// Accessibility semantics
ElementInternals includes ARIAMixin;

dictionary ValidityStateFlags {
boolean valueMissing = false;
boolean typeMismatch = false;
boolean patternMismatch = false;
boolean tooLong = false;
boolean tooShort = false;
boolean rangeUnderflow = false;
boolean rangeOverflow = false;
boolean stepMismatch = false;
boolean badInput = false;
boolean customError = false;
};
55 changes: 55 additions & 0 deletions lib/jsdom/living/nodes/HTMLElement-impl.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
"use strict";
const DOMException = require("domexception/webidl2js-wrapper");
const { mixin } = require("../../utils");
const ElementImpl = require("./Element-impl").implementation;
const MouseEvent = require("../generated/MouseEvent");
const ElementInternals = require("../generated/ElementInternals");
const ElementCSSInlineStyleImpl = require("./ElementCSSInlineStyle-impl").implementation;
const GlobalEventHandlersImpl = require("./GlobalEventHandlers-impl").implementation;
const HTMLOrSVGElementImpl = require("./HTMLOrSVGElement-impl").implementation;
const { firstChildWithLocalName } = require("../helpers/traversal");
const { isDisabled } = require("../helpers/form-controls");
const { fireAnEvent } = require("../helpers/events");
const { asciiLowercase } = require("../helpers/strings");
const { lookupCEDefinition } = require("../helpers/custom-elements");
const { wrapperForImpl } = require("../generated/utils");

class HTMLElementImpl extends ElementImpl {
constructor(globalObject, args, privateData) {
Expand All @@ -21,6 +25,9 @@ class HTMLElementImpl extends ElementImpl {

// <summary> uses HTMLElement and has activation behavior
this._hasActivationBehavior = this._localName === "summary";

// https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals
this._attachedInternals = null;
}

_activationBehavior() {
Expand All @@ -35,6 +42,54 @@ class HTMLElementImpl extends ElementImpl {
}
}

// https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals
attachInternals() {
if (this._isValue !== null) {
throw DOMException.create(this._globalObject, [
"Unable to attach ElementInternals to a customized built-in element.",
"NotSupportedError"
]);
}

const definition = lookupCEDefinition(this.ownerDocument, this.namespaceURI, this.localName, null);

if (definition === null) {
throw DOMException.create(this._globalObject, [
"Unable to attach ElementInternals to non-custom elements.",
"NotSupportedError"
]);
}

if (definition.disableInternals === true) {
throw DOMException.create(this._globalObject, [
"ElementInternals is disabled by disabledFeature static field.",
"NotSupportedError"
]);
}

if (this._attachedInternals !== null) {
throw DOMException.create(this._globalObject, [
"ElementInternals for the specified element was already attached.",
"NotSupportedError"
]);
}

if (!["precustomized", "custom"].includes(this._ceState)) {
throw DOMException.create(this._globalObject, [
"The attachInternals() function cannot be called prior to the execution of the custom element constructor.",
"NotSupportedError"
]);
}

this._attachedInternals = ElementInternals.new(this._globalObject);
const attachedInternalsImpl = wrapperForImpl(this._attachedInternals);

// https://html.spec.whatwg.org/multipage/custom-elements.html#internals-target
attachedInternalsImpl._targetElement = this;

return attachedInternalsImpl;
}

// https://html.spec.whatwg.org/multipage/dom.html#the-translate-attribute
get translate() {
const translateAttr = this.getAttributeNS(null, "translate");
Expand Down
2 changes: 1 addition & 1 deletion lib/jsdom/living/nodes/HTMLElement.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface HTMLElement : Element {

// [CEReactions] attribute [LegacyNullToEmptyString] DOMString innerText;

// ElementInternals attachInternals();
ElementInternals attachInternals();
};

HTMLElement includes GlobalEventHandlers;
Expand Down
1 change: 0 additions & 1 deletion test/web-platform-tests/to-run.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ Document-createElement.html:
"document.createElement must report a NotSupportedError when the local name of the element does not match that of the custom element": [fail, throws TypeError instead]
"document.createElement must report an exception thrown by a custom built-in element constructor": [fail, Unknown]
ElementInternals-accessibility.html: [fail, attachInternals is not implemented]
HTMLElement-attachInternals.html: [fail, Not implemented]
HTMLElement-constructor.html:
"HTMLElement constructor must throw a TypeError when NewTarget is equal to itself": [fail, Unknown]
"HTMLElement constructor must throw a TypeError when NewTarget is equal to itself via a Proxy object": [fail, webidl2js doesn't deal well with tests using Proxies to verify properties access]
Expand Down

0 comments on commit 088eb44

Please sign in to comment.