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

[scoped-custom-element-registry] Improve issues related to formAssociated #581

Merged
merged 8 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,22 @@
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
if (!ShadowRoot.prototype.createElement) {
if (!(ShadowRoot.prototype.createElement && ShadowRoot.prototype.importNode)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this feature check be here 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.

Reverted for now. Will have more to change/discuss to potentially better support Chrome's partial implementation in a follow-on.

Copy link
Collaborator Author

@sorvell sorvell May 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on dicussion with @justinfagnani and suggestion in #549, check was removed.

const NativeHTMLElement = window.HTMLElement;
const nativeDefine = window.customElements.define;
const nativeGet = window.customElements.get;
const nativeRegistry = window.customElements;

// Polyfill helper object
window['CustomElementRegistryPolyfill'] = {
// Note, `formAssociated` cannot be properly scoped and can only be set
// once per name. This is determined by how it is set on the first defined
// tag name. However, adding the name to
// `CustomElementsRegistryPolyfill.add(tagName)` reserves the given tag
// so it's always formAssociated.
'formAssociated': new Set(),
};

const definitionForElement = new WeakMap();
const pendingRegistryForElement = new WeakMap();
const globalDefinitionForConstructor = new WeakMap();
Expand Down Expand Up @@ -81,9 +91,28 @@ if (!ShadowRoot.prototype.createElement) {
// Register a stand-in class which will handle the registry lookup & delegation
let standInClass = nativeGet.call(nativeRegistry, tagName);
if (!standInClass) {
// `formAssociated` cannot be scoped so it's set to true if
// the first defined element sets it or it's reserved in
// `CustomElementRegistryPolyfill.formAssociated`.
if (definition['formAssociated']) {
window['CustomElementRegistryPolyfill']['formAssociated'].add(
tagName
);
}
standInClass = createStandInElement(tagName);
nativeDefine.call(nativeRegistry, tagName, standInClass);
}
// Sync `formAssociated` to its effective setting:
if (
window['CustomElementRegistryPolyfill']['formAssociated'].has(tagName)
) {
definition['formAssociated'] = true;
try {
elementClass['formAssociated'] = true;
} catch (e) {
/** squelch */
}
}
if (this === window.customElements) {
globalDefinitionForConstructor.set(elementClass, definition);
definition.standInClass = standInClass;
Expand Down Expand Up @@ -216,7 +245,9 @@ if (!ShadowRoot.prototype.createElement) {
const createStandInElement = (tagName) => {
return class ScopedCustomElementBase {
static get ['formAssociated']() {
return true;
return window['CustomElementRegistryPolyfill']['formAssociated'].has(
tagName
);
}
constructor() {
// Create a raw HTMLElement first
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {expect} from '@open-wc/testing';

import {
getTestTagName,
getTestElement,
getFormAssociatedTestElement,
getFormAssociatedErrorTestElement,
Expand Down Expand Up @@ -117,4 +118,41 @@ export const commonRegistryTests = (registry) => {
expect(element.internals.checkValidity()).to.be.false;
});
});

if (window.CustomElementRegistryPolyfill) {
describe('formAssociated scoping limitations', () => {
it('is formAssociated if set in CustomElementRegistryPolyfill.formAssociated', () => {
const tagName = getTestTagName();
window.CustomElementRegistryPolyfill.formAssociated.add(tagName);
class El extends HTMLElement {}
customElements.define(tagName, El);
expect(customElements.get(tagName).formAssociated).to.be.true;
});
it('is always formAssociated if first defined tag is formAssociated', () => {
const tagName = getTestTagName();
class FormAssociatedEl extends HTMLElement {
static formAssociated = true;
}
class El extends HTMLElement {}
customElements.define(tagName, FormAssociatedEl);
const registry = new CustomElementRegistry();
registry.define(tagName, El);
expect(customElements.get(tagName).formAssociated).to.be.true;
expect(registry.get(tagName).formAssociated).to.be.true;
});
});
}

describe('When formAssociated is not set', () => {
it('should not prevent clicks when disabled', () => {
const {tagName, CustomElementClass} = getTestElement();
customElements.define(tagName, CustomElementClass);
const el = document.createElement(tagName);
let clicked = false;
el.setAttribute('disabled', '');
el.addEventListener('click', () => (clicked = true));
el.click();
expect(clicked).to.be.true;
});
});
};