Commit 34bc581e authored by Kent Tamura's avatar Kent Tamura Committed by Commit Bot

Form-associated custom elements: Add more state checks

 - HTMLElement.prototype.attachInternals() should throw InvalidStateErrors
   for customized built-in elements.
 - Form-related operations and attributes of ElementInternals should throw
   InvalidStateErrors for non-form-associated custom elements.

Bug: 905922
Bug: https://github.com/w3c/webcomponents/issues/187
Change-Id: I09e574a843296b236fe4c63eb2cd0912c7a673b1
Reviewed-on: https://chromium-review.googlesource.com/c/1353035Reviewed-by: default avatarHayato Ito <hayato@chromium.org>
Commit-Queue: Kent Tamura <tkent@chromium.org>
Cr-Commit-Position: refs/heads/master@{#611960}
parent 1bcdee9c
...@@ -25,12 +25,20 @@ void ElementInternals::Trace(Visitor* visitor) { ...@@ -25,12 +25,20 @@ void ElementInternals::Trace(Visitor* visitor) {
ScriptWrappable::Trace(visitor); ScriptWrappable::Trace(visitor);
} }
void ElementInternals::setFormValue(const FileOrUSVString& value) { void ElementInternals::setFormValue(const FileOrUSVString& value,
setFormValue(value, nullptr); ExceptionState& exception_state) {
setFormValue(value, nullptr, exception_state);
} }
void ElementInternals::setFormValue(const FileOrUSVString& value, void ElementInternals::setFormValue(const FileOrUSVString& value,
FormData* entry_source) { FormData* entry_source,
ExceptionState& exception_state) {
if (!Target().IsFormAssociatedCustomElement()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The target element is not a form-associated custom element.");
return;
}
if (!entry_source || entry_source->size() == 0u) { if (!entry_source || entry_source->size() == 0u) {
value_ = value; value_ = value;
entry_source_ = nullptr; entry_source_ = nullptr;
...@@ -40,7 +48,13 @@ void ElementInternals::setFormValue(const FileOrUSVString& value, ...@@ -40,7 +48,13 @@ void ElementInternals::setFormValue(const FileOrUSVString& value,
entry_source_ = MakeGarbageCollected<FormData>(*entry_source); entry_source_ = MakeGarbageCollected<FormData>(*entry_source);
} }
HTMLFormElement* ElementInternals::form() const { HTMLFormElement* ElementInternals::form(ExceptionState& exception_state) const {
if (!Target().IsFormAssociatedCustomElement()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The target element is not a form-associated custom element.");
return nullptr;
}
return ListedElement::Form(); return ListedElement::Form();
} }
...@@ -49,7 +63,7 @@ void ElementInternals::DidUpgrade() { ...@@ -49,7 +63,7 @@ void ElementInternals::DidUpgrade() {
if (!parent) if (!parent)
return; return;
InsertedInto(*parent); InsertedInto(*parent);
if (auto* owner_form = form()) { if (auto* owner_form = Form()) {
if (auto* lists = owner_form->NodeLists()) if (auto* lists = owner_form->NodeLists())
lists->InvalidateCaches(nullptr); lists->InvalidateCaches(nullptr);
} }
...@@ -97,7 +111,7 @@ void ElementInternals::AppendToFormData(FormData& form_data) { ...@@ -97,7 +111,7 @@ void ElementInternals::AppendToFormData(FormData& form_data) {
void ElementInternals::DidChangeForm() { void ElementInternals::DidChangeForm() {
ListedElement::DidChangeForm(); ListedElement::DidChangeForm();
CustomElement::EnqueueFormAssociatedCallback(Target(), form()); CustomElement::EnqueueFormAssociatedCallback(Target(), Form());
} }
} // namespace blink } // namespace blink
...@@ -26,9 +26,12 @@ class ElementInternals : public ScriptWrappable, public ListedElement { ...@@ -26,9 +26,12 @@ class ElementInternals : public ScriptWrappable, public ListedElement {
void DidUpgrade(); void DidUpgrade();
// IDL attributes/operations // IDL attributes/operations
void setFormValue(const FileOrUSVString& value); void setFormValue(const FileOrUSVString& value,
void setFormValue(const FileOrUSVString& value, FormData* entry_source); ExceptionState& exception_state);
HTMLFormElement* form() const; void setFormValue(const FileOrUSVString& value,
FormData* entry_source,
ExceptionState& exception_state);
HTMLFormElement* form(ExceptionState& exception_state) const;
private: private:
// ListedElement overrides: // ListedElement overrides:
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
] ]
interface ElementInternals { interface ElementInternals {
// Attributes and operations for form-associated custom elements. // Attributes and operations for form-associated custom elements.
void setFormValue(FormDataEntryValue value, optional FormData entrySource); [RaisesException] void setFormValue(FormDataEntryValue value, optional FormData entrySource);
readonly attribute HTMLFormElement? form; [RaisesException] readonly attribute HTMLFormElement? form;
}; };
...@@ -1436,6 +1436,12 @@ ElementInternals* HTMLElement::attachInternals( ...@@ -1436,6 +1436,12 @@ ElementInternals* HTMLElement::attachInternals(
"Unable to attach ElementInternals to non-custom elements."); "Unable to attach ElementInternals to non-custom elements.");
return nullptr; return nullptr;
} }
if (!definition->Descriptor().IsAutonomous()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Unable to attach ElementInternals to a customized built-in element.");
return nullptr;
}
if (definition->DisableInternals()) { if (definition->DisableInternals()) {
exception_state.ThrowDOMException( exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError, DOMExceptionCode::kInvalidStateError,
......
<!DOCTYPE html>
<body>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
test(() => {
class MyElement1 extends HTMLElement {}
customElements.define('my-element1', MyElement1);
const element = new MyElement1();
const internals = element.attachInternals();
assert_throws('InvalidStateError', () => { internals.setFormValue(''); });
assert_throws('InvalidStateError', () => { internals.form; });
}, 'Form-related operations and attributes should throw InvalidStateErrors for' +
'non-form-associated custom elements.');
</script>
</body>
...@@ -30,6 +30,14 @@ test(() => { ...@@ -30,6 +30,14 @@ test(() => {
}, 'Parser - 2nd call'); }, 'Parser - 2nd call');
}, 'Successful attachInternals() and the second call.'); }, 'Successful attachInternals() and the second call.');
test(() => {
class MyDiv extends HTMLDivElement {}
customElements.define('my-div', MyDiv, { extends: 'div' });
const customizedBuiltin = document.createElement('div', { is: 'my-div'});
assert_throws('InvalidStateError', () => { customizedBuiltin.attachInternals() });
}, 'attachInternals() throws an InvalidStateError if it is called for ' +
'a customized built-in element');
test(() => { test(() => {
const builtin = document.createElement('div'); const builtin = document.createElement('div');
assert_throws('InvalidStateError', () => { builtin.attachInternals() }); assert_throws('InvalidStateError', () => { builtin.attachInternals() });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment