Skip to content

Commit

Permalink
Implement form-associated custom elements
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=197963

Reviewed by Ryosuke Niwa.

This change implements form-associated custom elements as per spec [1], with exception of
formStateRestoreCallback() being called for autofill and support of File interface
for saving / restoring state.

For not yet upgraded custom elements to become fully operational form controls if
their interface gets defined as form-associated, HTMLMaybeFormAssociatedCustomElement
class has beed introduced.

To avoid severe memory bloat for all custom elements, HTMLMaybeFormAssociatedCustomElement
keeps its form-associated internals in ElementRareData, initializing only on demand.

That internal data is a subclass of ValidatedFormListedElement, which was extracted from
HTMLFormControlElement to include only the logic relevant to both native and custom element
form controls, such as constraint validation, watching disabled state, "readonly" attribute,
and error reporting.

Since HTMLFormControlElementWithState used to extend HTMLFormControlElement, and with this
change form-associated custom elements can also have state, it has been merged into
ValidatedFormListedElement, with exception of the insertionIndex() method, which was removed
to avoid increasing sizeof(HTMLButtonElement).

insertionIndex() wasn't actually necessary (DOM order is sufficient), and the sorting by `formKey`
was refactored using a HashMap. Another important change is in form signature generator: we need
to account for not yet upgraded, potentially form-associated custom elements, otherwise signatures
for the same <form> won't match, hence the introduction of shouldBeUsedForFormSignature().

Per spec, ElementInternals can only be attached in "precustomized" or "custom" state [2],
but it's not clear whether or not its methods should throw in "error" state. This change
aligns WebKit with Firefox in this regard, arguing that if internals can't be attached in
given state, they shouldn't be operational.

During the upgrade, in the constructor, form-associated custom element has no notion of its
surroundings: its form owner, whether it's inside a <datalist> or disabled <fieldset>,
has a "readonly" attribute etc, so all that won't affect its `willValidate` / `validity`.
That is pretty sensible given the best practices for authoring custom elements forbid DOM
access in constructor to support `new XFoo()` usage.

This detail greatly reduces fragility: by delaying form-association until the "custom"
state, we ensure that once an element becomes "form listed", it will never roll back,
which we assert multiple times in HTMLFormElement.cpp.

However, if an author performs setValidity() in the constructor, that will work as expected.
Aligns WebKit with Blink and partly Firefox; the spec in unclear.

[1] whatwg/html#4383
[2] https://html.spec.whatwg.org/multipage/custom-elements.html#dom-attachinternals

* LayoutTests/fast/forms/state-restore-per-form-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/ElementInternals-NotSupportedError-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/ElementInternals-form-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/ElementInternals-labels-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/ElementInternals-labels.html:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/ElementInternals-setFormValue-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/ElementInternals-validation-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/ElementInternals-validation.html:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/fieldset-elements-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/form-associated-callback-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/form-disabled-callback-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/form-elements-namedItem-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/form-reset-callback-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/label-delegatesFocus-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/custom-elements/form-associated/upgrading/*: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/dom/idlharness.https-expected.txt:
* Source/WebCore/CMakeLists.txt:
* Source/WebCore/DerivedSources-input.xcfilelist:
* Source/WebCore/DerivedSources-output.xcfilelist:
* Source/WebCore/DerivedSources.make:
* Source/WebCore/Headers.cmake:
* Source/WebCore/Sources.txt:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::isShowingValidationMessage const):
(WebCore::AccessibilityObject::validationMessage const):
* Source/WebCore/bindings/js/JSCustomElementInterface.cpp:
(WebCore::JSCustomElementInterface::createElement):
(WebCore::JSCustomElementInterface::upgradeElement):
(WebCore::JSCustomElementInterface::invokeFormAssociatedCallback):
(WebCore::JSCustomElementInterface::invokeFormResetCallback):
(WebCore::JSCustomElementInterface::invokeFormDisabledCallback):
(WebCore::JSCustomElementInterface::invokeFormStateRestoreCallback):
* Source/WebCore/bindings/js/JSCustomElementInterface.h:
(WebCore::JSCustomElementInterface::isFormAssociated const):
(WebCore::JSCustomElementInterface::hasFormAssociatedCallback const):
(WebCore::JSCustomElementInterface::hasFormResetCallback const):
(WebCore::JSCustomElementInterface::hasFormDisabledCallback const):
(WebCore::JSCustomElementInterface::hasFormStateRestoreCallback const):
* Source/WebCore/bindings/js/JSHTMLElementCustom.cpp:
(WebCore::constructJSHTMLElement):
(WebCore::JSHTMLElement::pushEventHandlerScope const):
* Source/WebCore/css/SelectorCheckerTestFunctions.h:
(WebCore::matchesUserInvalidPseudoClass):
(WebCore::matchesUserValidPseudoClass):
* Source/WebCore/dom/CustomElementReactionQueue.cpp:
(WebCore::CustomElementReactionQueueItem::CustomElementReactionQueueItem):
(WebCore::CustomElementReactionQueueItem::invoke):
(WebCore::CustomElementReactionQueue::enqueueFormAssociatedCallbackIfNeeded):
(WebCore::CustomElementReactionQueue::enqueueFormResetCallbackIfNeeded):
(WebCore::CustomElementReactionQueue::enqueueFormDisabledCallbackIfNeeded):
(WebCore::CustomElementReactionQueue::enqueueFormStateRestoreCallbackIfNeeded):
(WebCore::CustomElementReactionQueue::isFormAssociated const):
* Source/WebCore/dom/CustomElementReactionQueue.h:
* Source/WebCore/dom/Document.cpp:
(WebCore::createUpgradeCandidateElement):
(WebCore::createFallbackHTMLElement):
* Source/WebCore/dom/Element.cpp:
(WebCore::Element::isFormAssociatedCustomElement const):
(WebCore::Element::canAccessFormAssociatedInternals const):
(WebCore::Element::asFormListedElement):
(WebCore::Element::asValidatedFormListedElement):
(WebCore::Element::ensureFormAssociatedCustomElement):
(WebCore::Element::matchesUserValidPseudoClass const):
(WebCore::Element::matchesUserInvalidPseudoClass const):
* Source/WebCore/dom/Element.h:
(WebCore::Element::isFormListedElement const):
(WebCore::Element::isValidatedFormListedElement const):
(WebCore::Element::isMaybeFormAssociatedCustomElement const):
(WebCore::Element::isFormControlElementWithState const): Deleted.
* Source/WebCore/dom/ElementInternals.cpp:
(WebCore::ElementInternals::form const):
(WebCore::ElementInternals::setFormValue):
(WebCore::ElementInternals::setValidity):
(WebCore::ElementInternals::willValidate):
(WebCore::ElementInternals::validity):
(WebCore::ElementInternals::validationMessage):
(WebCore::ElementInternals::reportValidity):
(WebCore::ElementInternals::checkValidity):
(WebCore::ElementInternals::labels):
* Source/WebCore/dom/ElementInternals.h:
* Source/WebCore/dom/ElementInternals.idl:
* Source/WebCore/dom/ElementRareData.cpp:
* Source/WebCore/dom/ElementRareData.h:
(WebCore::ElementRareData::formAssociatedCustomElement):
(WebCore::ElementRareData::setFormAssociatedCustomElement):
(WebCore::ElementRareData::useTypes const):
* Source/WebCore/dom/NodeRareData.h:
* Source/WebCore/dom/ValidityStateFlags.h: Copied from Source/WebCore/dom/ElementInternals.h.
(WebCore::ValidityStateFlags::isValid const):
* Source/WebCore/dom/ValidityStateFlags.idl: Copied from Source/WebCore/dom/ElementInternals.h.
* Source/WebCore/editing/FrameSelection.cpp:
(WebCore::scanForForm):
(WebCore::findFormControlElementAncestor):
(WebCore::FrameSelection::currentForm const):
* Source/WebCore/html/FormAssociatedCustomElement.cpp: Added.
(WebCore::FormAssociatedCustomElement::FormAssociatedCustomElement):
(WebCore::FormAssociatedCustomElement::create):
(WebCore::FormAssociatedCustomElement::setValidity):
(WebCore::FormAssociatedCustomElement::validationMessage const):
(WebCore::cloneFormDataIfNeccessary):
(WebCore::FormAssociatedCustomElement::setFormValue):
(WebCore::FormAssociatedCustomElement::anchorElement):
(WebCore::FormAssociatedCustomElement::computeValidity const):
(WebCore::FormAssociatedCustomElement::appendFormData):
(WebCore::FormAssociatedCustomElement::formWillBeDestroyed):
(WebCore::FormAssociatedCustomElement::reset):
(WebCore::FormAssociatedCustomElement::disabledStateChanged):
(WebCore::FormAssociatedCustomElement::didChangeForm):
(WebCore::FormAssociatedCustomElement::didUpgrade):
(WebCore::FormAssociatedCustomElement::invalidateElementsCollectionCachesInAncestors):
(WebCore::FormAssociatedCustomElement::formControlType const):
(WebCore::FormAssociatedCustomElement::saveFormControlState const):
(WebCore::FormAssociatedCustomElement::restoreFormControlState):
* Source/WebCore/html/FormAssociatedCustomElement.h: Added.
* Source/WebCore/html/FormAssociatedElement.h:
(WebCore::FormAssociatedElement::ref const):
(WebCore::FormAssociatedElement::deref const):
(WebCore::FormAssociatedElement::ref): Deleted.
(WebCore::FormAssociatedElement::deref): Deleted.
* Source/WebCore/html/FormController.cpp:
(WebCore::ownerForm):
(WebCore::formSignature):
(WebCore::FormController::FormKeyGenerator::formKey):
(WebCore::FormController::formElementsState const):
(WebCore::FormController::takeStateForFormElement):
(WebCore::FormController::restoreControlStateFor):
(WebCore::FormController::restoreControlStateIn):
* Source/WebCore/html/FormController.h:
* Source/WebCore/html/FormListedElement.cpp:
(WebCore::FormListedElement::didMoveToNewDocument):
(WebCore::FormListedElement::resetFormOwner):
(WebCore::FormListedElement::parseAttribute):
(WebCore::FormListedElement::parseFormAttribute):
(WebCore::FormListedElement::formAttributeChanged): Deleted.
(WebCore::FormListedElement::isFormControlElementWithState const): Deleted.
* Source/WebCore/html/FormListedElement.h:
(WebCore::FormListedElement::clearForm):
(): Deleted.
* Source/WebCore/html/GenericCachedHTMLCollection.cpp:
(WebCore::GenericCachedHTMLCollection<traversalType>::elementMatches const):
* Source/WebCore/html/HTMLElement.cpp:
(WebCore::HTMLElement::canBeActuallyDisabled const):
(WebCore::HTMLElement::form const): Deleted.
(WebCore::HTMLElement::asFormListedElement): Deleted.
* Source/WebCore/html/HTMLElement.h:
* Source/WebCore/html/HTMLFieldSetElement.cpp:
(WebCore::firstFormControlElementWithin):
(WebCore::nextFormControlElementSkippingChildren):
(WebCore::nextFormControlElement):
(WebCore::updateFromControlElementsAncestorDisabledStateUnder):
(WebCore::HTMLFieldSetElement::addInvalidDescendant):
(WebCore::HTMLFieldSetElement::removeInvalidDescendant):
* Source/WebCore/html/HTMLFieldSetElement.h:
* Source/WebCore/html/HTMLFieldSetElement.idl:
* Source/WebCore/html/HTMLFormControlElement.cpp:
(WebCore::HTMLFormControlElement::HTMLFormControlElement):
(WebCore::HTMLFormControlElement::parseAttribute):
(WebCore::HTMLFormControlElement::disabledStateChanged):
(WebCore::HTMLFormControlElement::readOnlyStateChanged):
(WebCore::HTMLFormControlElement::didFinishInsertingNode):
(WebCore::HTMLFormControlElement::didMoveToNewDocument):
(WebCore::HTMLFormControlElement::finishParsingChildren):
(WebCore::HTMLFormControlElement::insertedIntoAncestor):
(WebCore::HTMLFormControlElement::removedFromAncestor):
(WebCore::HTMLFormControlElement::~HTMLFormControlElement): Deleted.
(WebCore::HTMLFormControlElement::computeIsDisabledByFieldsetAncestor const): Deleted.
(WebCore::HTMLFormControlElement::setAncestorDisabled): Deleted.
(WebCore::HTMLFormControlElement::disabledAttributeChanged): Deleted.
(WebCore::addInvalidElementToAncestorFromInsertionPoint): Deleted.
(WebCore::removeInvalidElementToAncestorFromInsertionPoint): Deleted.
(WebCore::HTMLFormControlElement::setInteractedWithSinceLastFormSubmitEvent): Deleted.
(WebCore::HTMLFormControlElement::supportsFocus const): Deleted.
(WebCore::HTMLFormControlElement::matchesValidPseudoClass const): Deleted.
(WebCore::HTMLFormControlElement::matchesInvalidPseudoClass const): Deleted.
(WebCore::HTMLFormControlElement::endDelayingUpdateValidity): Deleted.
(WebCore::HTMLFormControlElement::computeWillValidate const): Deleted.
(WebCore::HTMLFormControlElement::willValidate const): Deleted.
(WebCore::HTMLFormControlElement::updateWillValidateAndValidity): Deleted.
(WebCore::HTMLFormControlElement::updateVisibleValidationMessage): Deleted.
(WebCore::HTMLFormControlElement::hideVisibleValidationMessage): Deleted.
(WebCore::HTMLFormControlElement::checkValidity): Deleted.
(WebCore::HTMLFormControlElement::isFocusingWithValidationMessage const): Deleted.
(WebCore::HTMLFormControlElement::isShowingValidationMessage const): Deleted.
(WebCore::HTMLFormControlElement::reportValidity): Deleted.
(WebCore::HTMLFormControlElement::focusAndShowValidationMessage): Deleted.
(WebCore::HTMLFormControlElement::isValidFormControlElement const): Deleted.
(WebCore::HTMLFormControlElement::willChangeForm): Deleted.
(WebCore::HTMLFormControlElement::didChangeForm): Deleted.
(WebCore::HTMLFormControlElement::updateValidity): Deleted.
(WebCore::HTMLFormControlElement::setCustomValidity): Deleted.
(WebCore::HTMLFormControlElement::validationMessageShadowTreeContains const): Deleted.
(WebCore::HTMLFormControlElement::matchesUserInvalidPseudoClass const): Deleted.
(WebCore::HTMLFormControlElement::matchesUserValidPseudoClass const): Deleted.
* Source/WebCore/html/HTMLFormControlElement.h:
(WebCore::HTMLFormControlElement::isReadOnly const):
(WebCore::HTMLFormControlElement::isMutable const):
(WebCore::HTMLFormControlElement::reset): Deleted.
(WebCore::HTMLFormControlElement::wasInteractedWithSinceLastFormSubmitEvent const): Deleted.
(WebCore::HTMLFormControlElement::supportsReadOnly const): Deleted.
(WebCore::HTMLFormControlElement::disabledByAncestorFieldset const): Deleted.
(WebCore::HTMLFormControlElement::startDelayingUpdateValidity): Deleted.
(WebCore::DelayedUpdateValidityScope::DelayedUpdateValidityScope): Deleted.
(WebCore::DelayedUpdateValidityScope::~DelayedUpdateValidityScope): Deleted.
* Source/WebCore/html/HTMLFormControlElementWithState.cpp: Removed.
* Source/WebCore/html/HTMLFormControlElementWithState.h: Removed.
* Source/WebCore/html/HTMLFormElement.cpp:
(WebCore::HTMLFormElement::validateInteractively):
(WebCore::HTMLFormElement::resetListedFormControlElements):
(WebCore::HTMLFormElement::shouldAutocorrect const):
(WebCore::HTMLFormElement::formElementIndex):
(WebCore::HTMLFormElement::addInvalidFormControl):
(WebCore::HTMLFormElement::removeInvalidFormControlIfNeeded):
(WebCore::HTMLFormElement::checkValidity):
(WebCore::HTMLFormElement::checkInvalidControlsAndCollectUnhandled):
(WebCore::HTMLFormElement::assertItemCanBeInPastNamesMap const):
(WebCore::HTMLFormElement::copyValidatedListedElementsVector const):
* Source/WebCore/html/HTMLFormElement.h:
* Source/WebCore/html/HTMLFormElement.idl:
* Source/WebCore/html/HTMLImageElement.h:
* Source/WebCore/html/HTMLInputElement.cpp:
(WebCore::HTMLInputElement::shouldSaveAndRestoreFormControlState const):
* Source/WebCore/html/HTMLLabelElement.cpp:
(WebCore::HTMLLabelElement::form const):
* Source/WebCore/html/HTMLLabelElement.h:
* Source/WebCore/html/HTMLLegendElement.h:
* Source/WebCore/html/HTMLMaybeFormAssociatedCustomElement.cpp: Added.
(WebCore::HTMLMaybeFormAssociatedCustomElement::HTMLMaybeFormAssociatedCustomElement):
(WebCore::HTMLMaybeFormAssociatedCustomElement::create):
(WebCore::HTMLMaybeFormAssociatedCustomElement::isFormListedElement const):
(WebCore::HTMLMaybeFormAssociatedCustomElement::isValidatedFormListedElement const):
(WebCore::HTMLMaybeFormAssociatedCustomElement::asFormAssociatedElement):
(WebCore::HTMLMaybeFormAssociatedCustomElement::asFormListedElement):
(WebCore::HTMLMaybeFormAssociatedCustomElement::asValidatedFormListedElement):
(WebCore::HTMLMaybeFormAssociatedCustomElement::asFormAssociatedCustomElement):
(WebCore::HTMLMaybeFormAssociatedCustomElement::matchesValidPseudoClass const):
(WebCore::HTMLMaybeFormAssociatedCustomElement::matchesInvalidPseudoClass const):
(WebCore::HTMLMaybeFormAssociatedCustomElement::matchesUserValidPseudoClass const):
(WebCore::HTMLMaybeFormAssociatedCustomElement::matchesUserInvalidPseudoClass const):
(WebCore::HTMLMaybeFormAssociatedCustomElement::supportsFocus const):
(WebCore::HTMLMaybeFormAssociatedCustomElement::supportLabels const):
(WebCore::HTMLMaybeFormAssociatedCustomElement::isDisabledFormControl const):
(WebCore::HTMLMaybeFormAssociatedCustomElement::impl const):
(WebCore::HTMLMaybeFormAssociatedCustomElement::finishParsingChildren):
(WebCore::HTMLMaybeFormAssociatedCustomElement::didFinishInsertingNode):
(WebCore::HTMLMaybeFormAssociatedCustomElement::didMoveToNewDocument):
(WebCore::HTMLMaybeFormAssociatedCustomElement::insertedIntoAncestor):
(WebCore::HTMLMaybeFormAssociatedCustomElement::removedFromAncestor):
(WebCore::HTMLMaybeFormAssociatedCustomElement::parseAttribute):
(WebCore::HTMLMaybeFormAssociatedCustomElement::didUpgrade):
* Source/WebCore/html/HTMLMaybeFormAssociatedCustomElement.h: Added.
(isType):
* Source/WebCore/html/HTMLObjectElement.cpp:
(WebCore::HTMLObjectElement::parseAttribute):
(WebCore::HTMLObjectElement::didMoveToNewDocument):
* Source/WebCore/html/HTMLObjectElement.h:
* Source/WebCore/html/HTMLOptionElement.h:
* Source/WebCore/html/HTMLSelectElement.cpp:
* Source/WebCore/html/HTMLSelectElement.h:
* Source/WebCore/html/HTMLTextAreaElement.h:
* Source/WebCore/html/HTMLTextFormControlElement.cpp:
* Source/WebCore/html/HTMLTextFormControlElement.h:
* Source/WebCore/html/RadioNodeList.cpp:
(WebCore::RadioNodeList::elementMatches const):
* Source/WebCore/html/ValidatedFormListedElement.cpp: Added.
(WebCore::ValidatedFormListedElement::ValidatedFormListedElement):
(WebCore::ValidatedFormListedElement::~ValidatedFormListedElement):
(WebCore::ValidatedFormListedElement::willValidate const):
(WebCore::ValidatedFormListedElement::computeWillValidate const):
(WebCore::ValidatedFormListedElement::updateVisibleValidationMessage):
(WebCore::ValidatedFormListedElement::hideVisibleValidationMessage):
(WebCore::ValidatedFormListedElement::checkValidity):
(WebCore::ValidatedFormListedElement::reportValidity):
(WebCore::ValidatedFormListedElement::focusableAnchorElementForValidationMessage):
(WebCore::ValidatedFormListedElement::focusAndShowValidationMessage):
(WebCore::ValidatedFormListedElement::reportNonFocusableControlError):
(WebCore::ValidatedFormListedElement::isShowingValidationMessage const):
(WebCore::ValidatedFormListedElement::validationMessageShadowTreeContains const):
(WebCore::ValidatedFormListedElement::isFocusingWithValidationMessage const):
(WebCore::ValidatedFormListedElement::setAncestorDisabled):
(WebCore::addInvalidElementToAncestorFromInsertionPoint):
(WebCore::removeInvalidElementToAncestorFromInsertionPoint):
(WebCore::ValidatedFormListedElement::updateValidity):
(WebCore::ValidatedFormListedElement::parseAttribute):
(WebCore::ValidatedFormListedElement::parseDisabledAttribute):
(WebCore::ValidatedFormListedElement::parseReadonlyAttribute):
(WebCore::ValidatedFormListedElement::disabledAttributeChanged):
(WebCore::ValidatedFormListedElement::insertedIntoAncestor):
(WebCore::ValidatedFormListedElement::resetDataListAncestorState):
(WebCore::ValidatedFormListedElement::syncWithFieldsetAncestors):
(WebCore::ValidatedFormListedElement::removedFromAncestor):
(WebCore::ValidatedFormListedElement::computeIsDisabledByFieldsetAncestor const):
(WebCore::ValidatedFormListedElement::willChangeForm):
(WebCore::ValidatedFormListedElement::didChangeForm):
(WebCore::ValidatedFormListedElement::disabledStateChanged):
(WebCore::ValidatedFormListedElement::readOnlyStateChanged):
(WebCore::ValidatedFormListedElement::updateWillValidateAndValidity):
(WebCore::ValidatedFormListedElement::didFinishInsertingNode):
(WebCore::ValidatedFormListedElement::setCustomValidity):
(WebCore::ValidatedFormListedElement::endDelayingUpdateValidity):
(WebCore::ValidatedFormListedElement::isCandidateForSavingAndRestoringState const):
(WebCore::ValidatedFormListedElement::shouldAutocomplete const):
(WebCore::ValidatedFormListedElement::saveFormControlState const):
(WebCore::ValidatedFormListedElement::finishParsingChildren):
(WebCore::ValidatedFormListedElement::matchesValidPseudoClass const):
(WebCore::ValidatedFormListedElement::matchesInvalidPseudoClass const):
(WebCore::ValidatedFormListedElement::matchesUserInvalidPseudoClass const):
(WebCore::ValidatedFormListedElement::matchesUserValidPseudoClass const):
(WebCore::ValidatedFormListedElement::setInteractedWithSinceLastFormSubmitEvent):
* Source/WebCore/html/ValidatedFormListedElement.h: Added.
(WebCore::ValidatedFormListedElement::reset):
(WebCore::ValidatedFormListedElement::supportsReadOnly const):
(WebCore::ValidatedFormListedElement::isDisabled const):
(WebCore::ValidatedFormListedElement::hasReadOnlyAttribute const):
(WebCore::ValidatedFormListedElement::isValidFormControlElement const):
(WebCore::ValidatedFormListedElement::wasInteractedWithSinceLastFormSubmitEvent const):
(WebCore::ValidatedFormListedElement::shouldSaveAndRestoreFormControlState const):
(WebCore::ValidatedFormListedElement::restoreFormControlState):
(WebCore::ValidatedFormListedElement::disabledByAncestorFieldset const):
(WebCore::ValidatedFormListedElement::startDelayingUpdateValidity):
(WebCore::DelayedUpdateValidityScope::DelayedUpdateValidityScope):
(WebCore::DelayedUpdateValidityScope::~DelayedUpdateValidityScope):
* Source/WebCore/html/ValidationMessage.cpp:
(WebCore::ValidationMessage::ValidationMessage):
(WebCore::ValidationMessage::updateValidationMessage):
* Source/WebCore/html/ValidationMessage.h:
* Source/WebCore/html/parser/HTMLConstructionSite.cpp:
(WebCore::HTMLConstructionSite::createHTMLElementOrFindCustomElementInterface):
* Source/WebCore/page/Frame.cpp:
(WebCore::Frame::searchForLabelsBeforeElement):
* Source/WebCore/style/StyleSharingResolver.cpp:
(WebCore::Style::SharingResolver::canShareStyleWithElement const):

Canonical link: https://commits.webkit.org/258561@main
  • Loading branch information
Alexey Shvayka committed Jan 6, 2023
1 parent fb19921 commit 8ab7fbc
Show file tree
Hide file tree
Showing 119 changed files with 3,192 additions and 1,064 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

Upgraded form-associated custom elements without an owner:
PASS $("noowner-upgrade1").restoredState is "foo"
PASS $("noowner-upgrade2").restoredState is "bar"
PASS isFormDataEqual($("noowner-upgrade3").restoredState, __formData2) is true
PASS isFormDataEqual($("noowner-upgrade4").restoredState, __formData1) is true

Upgraded form-associated custom elements with a form owner:
PASS isFormDataEqual($("upgrade1").restoredState, __formData2) is true
PASS $("upgrade2").restoredState is "foo"
PASS isFormDataEqual($("upgrade3").restoredState, __formData1) is true
PASS $("upgrade4").restoredState is "bar"

Predefined form-associated custom elements without an owner:
PASS $("noowner-predefined1").restoredState is "foo"
PASS $("noowner-predefined2").restoredState is "bar"
PASS isFormDataEqual($("noowner-predefined3").restoredState, __formData1) is true
PASS isFormDataEqual($("noowner-predefined4").restoredState, __formData2) is true

Predefined form-associated custom elements with a form owner:
PASS $("predefined1").restoredState is "foo"
PASS isFormDataEqual($("predefined2").restoredState, __formData1) is true
PASS $("predefined3").restoredState is "bar"
PASS $("predefined4").restoredState is "foo"
PASS successfullyParsed is true

TEST COMPLETE


Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<!DOCTYPE html>
<html>
<head>
<script src="../../resources/js-test-pre.js"></script>
<script src="resources/common.js"></script>
</head>
<body>
<div id="console"></div>

<input id=emptyOnFirstVisit>

<script>
jsTestIsAsync = true;

window.__formData1 = new FormData;
__formData1.append("foo", "bar");

window.__formData2 = new FormData;
__formData2.append("a", "1");
__formData2.append("b", "2");

function getXFooConstructor() {
return class extends HTMLElement {
static formAssociated = true;

constructor() {
super();
this._internals = this.attachInternals();
}

connectedCallback() {
this._internals.setFormValue(
this._parseFormValue(this.dataset.submissionValue),
this._parseFormValue(this.dataset.state),
);
}

formStateRestoreCallback(state) {
this.restoredState = state;
}

_parseFormValue(attrValue) {
return typeof attrValue === "string" && attrValue.startsWith("@") ? window[`__${attrValue.slice(1)}`] : attrValue;
}
};
}

function isFormDataEqual(a, b) {
const bEntries = [...b];

return [...a].every(([aKey, aValue], index) => {
const [bKey, bValue] = bEntries[index];
return aKey === bKey && aValue === bValue;
});
}

customElements.define("x-foo-predefined", getXFooConstructor());

function makeForms(stage) {
var beforeForms = '<div id=parent>' +
'<x-foo-upgrade id="noowner-upgrade2" name="noowner-upgrade2" data-submission-value="@formData2" data-state="bar"></x-foo-upgrade>' +
'<x-foo-upgrade id="noowner-upgrade4" name="noowner-upgrade4" data-submission-value="foo" data-state="@formData1"></x-foo-upgrade>' +
'<x-foo-predefined id="noowner-predefined2" name="noowner-predefined2" data-submission-value="foo" data-state="bar"></x-foo-predefined>' +
'<x-foo-predefined id="noowner-predefined4" name="noowner-predefined4" data-submission-value="@formData1" data-state="@formData2"></x-foo-predefined>';

var backForm = '<form action="data:text/html,&lt;script>history.back();&lt;/script>" id=form1 name=form1>' +
'</form>';

var query = stage == 1 ? "?session=0123456" : "?session=7654321111";
var sameActionForm1 = '<form action="http://example.com/foo.cgi' + query + '#bar" id=form2 name=form2>' +
'<x-foo-upgrade id="upgrade2" name="upgrade2" data-submission-value="foo"></x-foo-upgrade>' +
'<x-foo-upgrade id="upgrade4" name="upgrade4" data-submission-value="foo" data-state="bar"></x-foo-upgrade>' +
'<x-foo-predefined id="predefined2" name="predefined2" data-submission-value="foo" data-state="@formData1"></x-foo-predefined>' +
'<x-foo-predefined id="predefined4" name="predefined4" data-submission-value="@formData2" data-state="foo"></x-foo-predefined>' +
'</form>';

var sameActionForm2 = '<form action="http://example.com/foo.cgi?action=login#bar" id=form3 name=form3>' +
'<x-foo-upgrade id="upgrade1" name="upgrade1" data-submission-value="@formData2"></x-foo-upgrade>' +
'<x-foo-upgrade id="upgrade3" name="upgrade3" data-submission-value="@formData2" data-state="@formData1"></x-foo-upgrade>' +
'<x-foo-predefined id="predefined1" name="predefined1" data-submission-value="foo"></x-foo-predefined>' +
'<x-foo-predefined id="predefined3" name="predefined3" data-submission-value="bar"></x-foo-predefined>' +
'</form>';

var afterForms =
'<x-foo-upgrade id="noowner-upgrade1" name="noowner-upgrade1" data-submission-value="foo"></x-foo-upgrade>' +
'<x-foo-upgrade id="noowner-upgrade3" name="noowner-upgrade3" data-submission-value="@formData2"></x-foo-upgrade>' +
'<x-foo-predefined id="noowner-predefined1" name="noowner-predefined1" data-submission-value="foo"></x-foo-predefined>' +
'<x-foo-predefined id="noowner-predefined3" name="noowner-predefined3" data-submission-value="@formData1"></x-foo-predefined>' +
'</div>';

document.write(
beforeForms +
(stage === 1 ? backForm + sameActionForm1 : sameActionForm1 + backForm) +
sameActionForm2 +
afterForms
);

customElements.define("x-foo-upgrade", getXFooConstructor());
}

function runTest()
{
var state = $('emptyOnFirstVisit');
if (!state.value) {
state.value = 'visited';
makeForms(1);

setTimeout(function() {
$('form1').submit();
});
} else {
makeForms(2);

debug('\nUpgraded form-associated custom elements without an owner:');
shouldBeEqualToString('$("noowner-upgrade1").restoredState', 'foo');
shouldBeEqualToString('$("noowner-upgrade2").restoredState', 'bar');
shouldBeTrue('isFormDataEqual($("noowner-upgrade3").restoredState, __formData2)');
shouldBeTrue('isFormDataEqual($("noowner-upgrade4").restoredState, __formData1)');

debug('\nUpgraded form-associated custom elements with a form owner:');
shouldBeTrue('isFormDataEqual($("upgrade1").restoredState, __formData2)');
shouldBeEqualToString('$("upgrade2").restoredState', 'foo');
shouldBeTrue('isFormDataEqual($("upgrade3").restoredState, __formData1)');
shouldBeEqualToString('$("upgrade4").restoredState', 'bar');

debug('\nPredefined form-associated custom elements without an owner:');
shouldBeEqualToString('$("noowner-predefined1").restoredState', 'foo');
shouldBeEqualToString('$("noowner-predefined2").restoredState', 'bar');
shouldBeTrue('isFormDataEqual($("noowner-predefined3").restoredState, __formData1)');
shouldBeTrue('isFormDataEqual($("noowner-predefined4").restoredState, __formData2)');

debug('\nPredefined form-associated custom elements with a form owner:');
shouldBeEqualToString('$("predefined1").restoredState', 'foo');
shouldBeTrue('isFormDataEqual($("predefined2").restoredState, __formData1)');
shouldBeEqualToString('$("predefined3").restoredState', 'bar');
shouldBeEqualToString('$("predefined4").restoredState', 'foo');

$('parent').innerHTML = '';
setTimeout(function() { finishJSTest(); });
}
}

runTest();
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL Form-related operations and attributes should throw NotSupportedErrors for non-form-associated custom elements. assert_throws_dom: function "() => i.setFormValue('')" threw object "TypeError: i.setFormValue is not a function. (In 'i.setFormValue('')', 'i.setFormValue' is undefined)" that is not a DOMException NotSupportedError: property "code" is equal to undefined, expected 9
PASS Form-related operations and attributes should throw NotSupportedErrors for non-form-associated custom elements.

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

FAIL ElementInternals.form should return the target element's form owner assert_equals: expected (object) Element node <form id="custom-form">
<custom-input-parser id="custom... but got (undefined) undefined
FAIL ElementInternals.form should throws NotSupportedError if the target element is not a form-associated custom element assert_throws_dom: function "() => i.form" did not throw
PASS ElementInternals.form should return the target element's form owner
PASS ElementInternals.form should throws NotSupportedError if the target element is not a form-associated custom element

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

FAIL LABEL association assert_equals: expected Element node <my-control></my-control> but got null
FAIL LABEL click assert_equals: expected 1 but got 0
FAIL ElementInternals.labels should throw NotSupportedError if the target element is not a form-associated custom element assert_throws_dom: function "() => i.labels" did not throw
PASS LABEL association
PASS LABEL click
PASS ElementInternals.labels should throw NotSupportedError if the target element is not a form-associated custom element

Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
container.innerHTML = '<my-control></my-control>';
control = container.querySelector('my-control');
assert_array_equals(control.i.labels, []);

container.innerHTML = '<label><x-foo></x-foo></label>';
label = container.querySelector('label');
assert_equals(label.control, null);
}, 'LABEL association');

test(() => {
Expand Down
Loading

0 comments on commit 8ab7fbc

Please sign in to comment.