LibWeb: Implement named and indexed property access for HTMLFormElement
This commit is contained in:
parent
521ed0e911
commit
b5ec520f84
Notes:
sideshowbarker
2024-07-17 06:39:26 +09:00
Author: https://github.com/ADKaster Commit: https://github.com/SerenityOS/serenity/commit/b5ec520f84 Pull-request: https://github.com/SerenityOS/serenity/pull/22675 Reviewed-by: https://github.com/shannonbooth ✅
7 changed files with 446 additions and 11 deletions
Tests/LibWeb/Text
expected/HTML
input/HTML
Userland/Libraries/LibWeb/HTML
|
@ -0,0 +1,14 @@
|
|||
form.length: 12
|
||||
elements.length: 12
|
||||
form[0].name: foo
|
||||
form[1].name: bar
|
||||
form[2].name: baz
|
||||
form[3].name: qux
|
||||
form[4].name: quux
|
||||
form[5].name: corge
|
||||
form[6].name: foo2
|
||||
form[7].name: bar2
|
||||
form[8].name: baz2
|
||||
form[9].name: qux2
|
||||
form[10].name: quux2
|
||||
form[11].name: corge2
|
|
@ -0,0 +1,34 @@
|
|||
== Elements and Names ==
|
||||
formy.length: 12
|
||||
elements.length: 12
|
||||
elements[0] === form.foo
|
||||
elements[1] === form.bar
|
||||
elements[2] === form.baz
|
||||
elements[3] === form.qux
|
||||
elements[4] === form.quux
|
||||
elements[5] === form.corge
|
||||
elements[6] === form.foo2
|
||||
elements[7] === form.bar2
|
||||
elements[8] === form.baz2
|
||||
elements[9] === form.qux2
|
||||
elements[10] === form.quux2
|
||||
elements[11] === form.corge2
|
||||
== If no listed elements, picks img ==
|
||||
form.inside == image: true
|
||||
== Form association ==
|
||||
elements in form2: 2
|
||||
elements in form3: 2
|
||||
== Same name and id for many elements ==
|
||||
elements in samename: 6
|
||||
samename.a.length: 6
|
||||
typeof samename.a: object
|
||||
elements in sameid: 6
|
||||
sameid.a.length: 6
|
||||
typeof sameid.a: object
|
||||
== Changing name/id ==
|
||||
elements in changy: 1
|
||||
hello is goodbye? true
|
||||
Can we still use the same name?: true
|
||||
new hello is goodbye? false
|
||||
new hello is old hello? false
|
||||
new hello is newInput? true
|
|
@ -0,0 +1,44 @@
|
|||
<form id="formy">
|
||||
<input type="text" name="foo">
|
||||
<button type="button" name="bar"></button>
|
||||
<object name="baz"></object>
|
||||
<select name="qux"></select>
|
||||
<textarea name="quux"></textarea>
|
||||
<fieldset name="corge">
|
||||
<!-- input elements in the ImageData type state are excluded -->
|
||||
<input type="image" name="grault">
|
||||
</fieldset>
|
||||
<img id="inside" src="" alt="">
|
||||
</form>
|
||||
|
||||
<input type="text" name="foo2" form="formy">
|
||||
<button type="button" name="bar2" form="formy"></button>
|
||||
<object name="baz2" form="formy"></object>
|
||||
<select name="qux2" form="formy"></select>
|
||||
<textarea name="quux2" form="formy"></textarea>
|
||||
<fieldset name="corge2" form="formy">
|
||||
<!-- input elements in the ImageData type state are excluded -->
|
||||
<input type="image" name="grault2" form="formy">
|
||||
</fieldset>
|
||||
<img id="outside" src="" alt="">
|
||||
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
let form = document.getElementById("formy");
|
||||
|
||||
let elements = form.elements;
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (elements[i] !== form[i]) {
|
||||
println(`FAIL: elements[${i}] !== form[${i}]`);
|
||||
}
|
||||
}
|
||||
|
||||
println(`form.length: ${form.length}`);
|
||||
println(`elements.length: ${elements.length}`);
|
||||
|
||||
for (let i = 0; i < form.length; i++) {
|
||||
println(`form[${i}].name: ${form[i].name}`);
|
||||
}
|
||||
});
|
||||
</script>
|
121
Tests/LibWeb/Text/input/HTML/Form-named-property-access.html
Normal file
121
Tests/LibWeb/Text/input/HTML/Form-named-property-access.html
Normal file
|
@ -0,0 +1,121 @@
|
|||
<form id="formy">
|
||||
<input type="text" name="foo">
|
||||
<button type="button" name="bar"></button>
|
||||
<object name="baz"></object>
|
||||
<select name="qux"></select>
|
||||
<textarea name="quux"></textarea>
|
||||
<fieldset name="corge">
|
||||
<!-- input elements in the ImageData type state are excluded -->
|
||||
<input type="image" name="grault">
|
||||
</fieldset>
|
||||
<img id="inside" src="" alt="">
|
||||
</form>
|
||||
|
||||
<input type="text" name="foo2" form="formy">
|
||||
<button type="button" name="bar2" form="formy"></button>
|
||||
<object name="baz2" form="formy"></object>
|
||||
<select name="qux2" form="formy"></select>
|
||||
<textarea name="quux2" form="formy"></textarea>
|
||||
<fieldset name="corge2" form="formy">
|
||||
<!-- input elements in the ImageData type state are excluded -->
|
||||
<input type="image" name="grault2" form="formy">
|
||||
</fieldset>
|
||||
|
||||
<form id="form2"></form>
|
||||
<form id="form3">
|
||||
<fieldset form="form2" id="formset">
|
||||
<input type="button" id="button1" form="form2">
|
||||
<input type="button" id="button2" form="form3">
|
||||
<input type="button" id="button3"><!-- implicitly form3 -->
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<form id="samename">
|
||||
<input type="text" name="a">
|
||||
<button type="button" name="a"></button>
|
||||
<object name="a"></object>
|
||||
<select name="a"></select>
|
||||
<textarea name="a"></textarea>
|
||||
<fieldset name="a">
|
||||
<!-- input elements in the ImageData type state are excluded -->
|
||||
<input type="image" name="a">
|
||||
</fieldset>
|
||||
<img id="a" src="" alt="">
|
||||
</form>
|
||||
|
||||
<form id="sameid">
|
||||
<input type="text" id="a">
|
||||
<button type="button" id="a"></button>
|
||||
<object id="a"></object>
|
||||
<select id="a"></select>
|
||||
<textarea id="a"></textarea>
|
||||
<fieldset id="a">
|
||||
<!-- input elements in the ImageData type state are excluded -->
|
||||
<input type="image" id="a">
|
||||
</fieldset>
|
||||
<img id="a" src="" alt="">
|
||||
</form>
|
||||
|
||||
<form id="changy">
|
||||
<input type="text" name="hello">
|
||||
</form>
|
||||
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
println("== Elements and Names ==");
|
||||
let formy = document.forms.formy;
|
||||
let elements = formy.elements;
|
||||
println(`formy.length: ${formy.length}`);
|
||||
println(`elements.length: ${elements.length}`);
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (elements[i] !== eval(`formy.${elements[i].name}`)) {
|
||||
println(`FAIL: elements[${i}] !== form.${elements[i].name}`);
|
||||
}
|
||||
else {
|
||||
println(`elements[${i}] === form.${elements[i].name}`);
|
||||
}
|
||||
}
|
||||
|
||||
println("== If no listed elements, picks img ==");
|
||||
let image = document.getElementById("inside");
|
||||
println("form.inside == image: " + (formy.inside === image));
|
||||
|
||||
println("== Form association ==");
|
||||
let form2 = document.getElementById("form2");
|
||||
let form3 = document.getElementById("form3");
|
||||
println(`elements in form2: ${form2.elements.length}`);
|
||||
println(`elements in form3: ${form3.elements.length}`);
|
||||
|
||||
println("== Same name and id for many elements ==");
|
||||
let samename = document.getElementById("samename");
|
||||
println(`elements in samename: ${samename.elements.length}`);
|
||||
let samenameElements = samename.a;
|
||||
println(`samename.a.length: ${samenameElements.length}`);
|
||||
println(`typeof samename.a: ${typeof samenameElements}`);
|
||||
|
||||
let sameid = document.getElementById("sameid");
|
||||
println(`elements in sameid: ${sameid.elements.length}`);
|
||||
let sameidElements = sameid.a;
|
||||
println(`sameid.a.length: ${sameidElements.length}`);
|
||||
println(`typeof sameid.a: ${typeof sameidElements}`);
|
||||
|
||||
println("== Changing name/id ==");
|
||||
let changy = document.getElementById("changy");
|
||||
println(`elements in changy: ${changy.elements.length}`);
|
||||
let hello = changy.hello;
|
||||
changy.elements[0].name = "goodbye";
|
||||
let goodbye = changy.goodbye;
|
||||
println(`hello is goodbye? ${hello === goodbye}`);
|
||||
println(`Can we still use the same name?: ${changy.hello === changy.goodbye}`);
|
||||
|
||||
let newInput = document.createElement("input");
|
||||
newInput.type = "text";
|
||||
newInput.id = "hello";
|
||||
changy.appendChild(newInput);
|
||||
|
||||
println(`new hello is goodbye? ${changy.hello === goodbye}`);
|
||||
println(`new hello is old hello? ${changy.hello === hello}`);
|
||||
println(`new hello is newInput? ${changy.hello === newInput}`);
|
||||
});
|
||||
</script>
|
|
@ -6,6 +6,7 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/QuickSort.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibTextCodec/Decoder.h>
|
||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||
|
@ -18,6 +19,7 @@
|
|||
#include <LibWeb/HTML/HTMLButtonElement.h>
|
||||
#include <LibWeb/HTML/HTMLFieldSetElement.h>
|
||||
#include <LibWeb/HTML/HTMLFormElement.h>
|
||||
#include <LibWeb/HTML/HTMLImageElement.h>
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
#include <LibWeb/HTML/HTMLObjectElement.h>
|
||||
#include <LibWeb/HTML/HTMLOutputElement.h>
|
||||
|
@ -36,6 +38,12 @@ JS_DEFINE_ALLOCATOR(HTMLFormElement);
|
|||
HTMLFormElement::HTMLFormElement(DOM::Document& document, DOM::QualifiedName qualified_name)
|
||||
: HTMLElement(document, move(qualified_name))
|
||||
{
|
||||
m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
|
||||
.supports_indexed_properties = true,
|
||||
.supports_named_properties = true,
|
||||
.has_legacy_unenumerable_named_properties_interface_extended_attribute = true,
|
||||
.has_legacy_override_built_ins_interface_extended_attribute = true,
|
||||
};
|
||||
}
|
||||
|
||||
HTMLFormElement::~HTMLFormElement() = default;
|
||||
|
@ -299,6 +307,9 @@ void HTMLFormElement::add_associated_element(Badge<FormAssociatedElement>, HTMLE
|
|||
void HTMLFormElement::remove_associated_element(Badge<FormAssociatedElement>, HTMLElement& element)
|
||||
{
|
||||
m_associated_elements.remove_first_matching([&](auto& entry) { return entry.ptr() == &element; });
|
||||
|
||||
// If an element listed in a form element's past names map changes form owner, then its entries must be removed from that map.
|
||||
m_past_names_map.remove_all_matching([&](auto&, auto const& entry) { return entry.node == &element; });
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fs-action
|
||||
|
@ -382,10 +393,17 @@ HTMLFormElement::EncodingTypeAttributeState HTMLFormElement::encoding_type_state
|
|||
return encoding_type_attribute_to_encoding_type_state(this->deprecated_attribute(AttributeNames::enctype));
|
||||
}
|
||||
|
||||
static bool is_form_control(DOM::Element const& element)
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#category-listed
|
||||
static bool is_listed_element(DOM::Element const& element)
|
||||
{
|
||||
// Denotes elements that are listed in the form.elements and fieldset.elements APIs.
|
||||
// These elements also have a form content attribute, and a matching form IDL attribute,
|
||||
// that allow authors to specify an explicit form owner.
|
||||
// => button, fieldset, input, object, output, select, textarea, form-associated custom elements
|
||||
|
||||
if (is<HTMLButtonElement>(element)
|
||||
|| is<HTMLFieldSetElement>(element)
|
||||
|| is<HTMLInputElement>(element)
|
||||
|| is<HTMLObjectElement>(element)
|
||||
|| is<HTMLOutputElement>(element)
|
||||
|| is<HTMLSelectElement>(element)
|
||||
|
@ -393,22 +411,40 @@ static bool is_form_control(DOM::Element const& element)
|
|||
return true;
|
||||
}
|
||||
|
||||
if (is<HTMLInputElement>(element)
|
||||
&& !element.deprecated_get_attribute(HTML::AttributeNames::type).equals_ignoring_ascii_case("image"sv)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME: Form-associated custom elements return also true
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_form_control(DOM::Element const& element, HTMLFormElement const& form)
|
||||
{
|
||||
// The elements IDL attribute must return an HTMLFormControlsCollection rooted at the form element's root,
|
||||
// whose filter matches listed elements whose form owner is the form element,
|
||||
// with the exception of input elements whose type attribute is in the Image Button state, which must,
|
||||
// for historical reasons, be excluded from this particular collection.
|
||||
|
||||
if (!is_listed_element(element))
|
||||
return false;
|
||||
|
||||
if (is<HTMLInputElement>(element)
|
||||
&& static_cast<HTMLInputElement const&>(element).type_state() == HTMLInputElement::TypeAttributeState::ImageButton) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const& form_associated_element = dynamic_cast<FormAssociatedElement const&>(element);
|
||||
if (form_associated_element.form() != &form)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-elements
|
||||
JS::NonnullGCPtr<DOM::HTMLFormControlsCollection> HTMLFormElement::elements() const
|
||||
{
|
||||
if (!m_elements) {
|
||||
m_elements = DOM::HTMLFormControlsCollection::create(const_cast<HTMLFormElement&>(*this), DOM::HTMLCollection::Scope::Descendants, [](Element const& element) {
|
||||
return is_form_control(element);
|
||||
auto& root = verify_cast<ParentNode>(const_cast<HTMLFormElement*>(this)->root());
|
||||
m_elements = DOM::HTMLFormControlsCollection::create(root, DOM::HTMLCollection::Scope::Descendants, [this](Element const& element) {
|
||||
return is_form_control(element, *this);
|
||||
});
|
||||
}
|
||||
return *m_elements;
|
||||
|
@ -815,4 +851,176 @@ void HTMLFormElement::plan_to_navigate_to(AK::URL url, Variant<Empty, String, PO
|
|||
VERIFY(m_planned_navigation);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#the-form-element:supported-property-indices
|
||||
bool HTMLFormElement::is_supported_property_index(u32 index) const
|
||||
{
|
||||
// The supported property indices at any instant are the indices supported by the object returned by the elements attribute at that instant.
|
||||
return index < elements()->length();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-item
|
||||
WebIDL::ExceptionOr<JS::Value> HTMLFormElement::item_value(size_t index) const
|
||||
{
|
||||
// To determine the value of an indexed property for a form element, the user agent must return the value returned by
|
||||
// the item method on the elements collection, when invoked with the given index as its argument.
|
||||
return elements()->item(index);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#the-form-element:supported-property-names
|
||||
Vector<FlyString> HTMLFormElement::supported_property_names() const
|
||||
{
|
||||
// The supported property names consist of the names obtained from the following algorithm, in the order obtained from this algorithm:
|
||||
|
||||
// 1. Let sourced names be an initially empty ordered list of tuples consisting of a string, an element, a source,
|
||||
// where the source is either id, name, or past, and, if the source is past, an age.
|
||||
struct SourcedName {
|
||||
FlyString name;
|
||||
JS::GCPtr<DOM::Element const> element;
|
||||
enum class Source {
|
||||
Id,
|
||||
Name,
|
||||
Past,
|
||||
} source;
|
||||
Duration age;
|
||||
};
|
||||
Vector<SourcedName> sourced_names;
|
||||
|
||||
// 2. For each listed element candidate whose form owner is the form element, with the exception of any
|
||||
// input elements whose type attribute is in the Image Button state:
|
||||
for (auto const& candidate : m_associated_elements) {
|
||||
if (!is_form_control(*candidate, *this))
|
||||
continue;
|
||||
|
||||
// 1. If candidate has an id attribute, add an entry to sourced names with that id attribute's value as the
|
||||
// string, candidate as the element, and id as the source.
|
||||
if (candidate->has_attribute(HTML::AttributeNames::id))
|
||||
sourced_names.append(SourcedName { candidate->id().value(), candidate, SourcedName::Source::Id, {} });
|
||||
|
||||
// 2. If candidate has a name attribute, add an entry to sourced names with that name attribute's value as the
|
||||
// string, candidate as the element, and name as the source.
|
||||
if (candidate->has_attribute(HTML::AttributeNames::name))
|
||||
sourced_names.append(SourcedName { candidate->attribute(HTML::AttributeNames::name).value(), candidate, SourcedName::Source::Name, {} });
|
||||
}
|
||||
|
||||
// 3. For each img element candidate whose form owner is the form element:
|
||||
for (auto const& candidate : m_associated_elements) {
|
||||
if (!is<HTMLImageElement>(*candidate))
|
||||
continue;
|
||||
|
||||
// Every element in m_associated_elements has this as the form owner.
|
||||
|
||||
// 1. If candidate has an id attribute, add an entry to sourced names with that id attribute's value as the
|
||||
// string, candidate as the element, and id as the source.
|
||||
if (candidate->has_attribute(HTML::AttributeNames::id))
|
||||
sourced_names.append(SourcedName { candidate->id().value(), candidate, SourcedName::Source::Id, {} });
|
||||
|
||||
// 2. If candidate has a name attribute, add an entry to sourced names with that name attribute's value as the
|
||||
// string, candidate as the element, and name as the source.
|
||||
if (candidate->has_attribute(HTML::AttributeNames::name))
|
||||
sourced_names.append(SourcedName { candidate->attribute(HTML::AttributeNames::name).value(), candidate, SourcedName::Source::Name, {} });
|
||||
}
|
||||
|
||||
// 4. For each entry past entry in the past names map add an entry to sourced names with the past entry's name as
|
||||
// the string, past entry's element as the element, past as the source, and the length of time past entry has
|
||||
// been in the past names map as the age.
|
||||
auto const now = MonotonicTime::now();
|
||||
for (auto const& entry : m_past_names_map)
|
||||
sourced_names.append(SourcedName { entry.key, static_cast<DOM::Element const*>(entry.value.node.ptr()), SourcedName::Source::Past, now - entry.value.insertion_time });
|
||||
|
||||
// 5. Sort sourced names by tree order of the element entry of each tuple, sorting entries with the same element by
|
||||
// putting entries whose source is id first, then entries whose source is name, and finally entries whose source
|
||||
// is past, and sorting entries with the same element and source by their age, oldest first.
|
||||
// FIXME: Require less const casts here by changing the signature of DOM::Node::compare_document_position
|
||||
quick_sort(sourced_names, [](auto const& lhs, auto const& rhs) -> bool {
|
||||
if (lhs.element != rhs.element)
|
||||
return const_cast<DOM::Element*>(lhs.element.ptr())->compare_document_position(const_cast<DOM::Element*>(rhs.element.ptr())) & DOM::Node::DOCUMENT_POSITION_FOLLOWING;
|
||||
if (lhs.source != rhs.source)
|
||||
return lhs.source < rhs.source;
|
||||
return lhs.age < rhs.age;
|
||||
});
|
||||
|
||||
// FIXME: Surely there's a more efficient way to do this without so many FlyStrings and collections?
|
||||
// 6. Remove any entries in sourced names that have the empty string as their name.
|
||||
// 7. Remove any entries in sourced names that have the same name as an earlier entry in the map.
|
||||
// 8. Return the list of names from sourced names, maintaining their relative order.
|
||||
OrderedHashTable<FlyString> names;
|
||||
names.ensure_capacity(sourced_names.size());
|
||||
for (auto const& entry : sourced_names) {
|
||||
if (entry.name.is_empty())
|
||||
continue;
|
||||
names.set(entry.name, AK::HashSetExistingEntryBehavior::Keep);
|
||||
}
|
||||
|
||||
Vector<FlyString> result;
|
||||
result.ensure_capacity(names.size());
|
||||
for (auto const& name : names)
|
||||
result.unchecked_append(name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-nameditem
|
||||
WebIDL::ExceptionOr<JS::Value> HTMLFormElement::named_item_value(FlyString const& name) const
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
auto& root = verify_cast<ParentNode>(this->root());
|
||||
|
||||
// To determine the value of a named property name for a form element, the user agent must run the following steps:
|
||||
|
||||
// 1. Let candidates be a live RadioNodeList object containing all the listed elements, whose form owner is the form
|
||||
// element, that have either an id attribute or a name attribute equal to name, with the exception of input
|
||||
// elements whose type attribute is in the Image Button state, in tree order.
|
||||
auto candidates = DOM::RadioNodeList::create(realm, root, DOM::LiveNodeList::Scope::Descendants, [this, name](auto& node) -> bool {
|
||||
if (!is<DOM::Element>(node))
|
||||
return false;
|
||||
auto const& element = static_cast<DOM::Element const&>(node);
|
||||
|
||||
// Form controls are defined as listed elements, with the exception of input elements in the Image Button state,
|
||||
// whose form owner is the form element.
|
||||
if (!is_form_control(element, *this))
|
||||
return false;
|
||||
|
||||
// FIXME: DOM::Element::name() isn't cached
|
||||
return name == element.id() || name == element.attribute(HTML::AttributeNames::name);
|
||||
});
|
||||
|
||||
// 2. If candidates is empty, let candidates be a live RadioNodeList object containing all the img elements,
|
||||
// whose form owner is the form element, that have either an id attribute or a name attribute equal to name,
|
||||
// in tree order.
|
||||
if (candidates->length() == 0) {
|
||||
candidates = DOM::RadioNodeList::create(realm, root, DOM::LiveNodeList::Scope::Descendants, [this, name](auto& node) -> bool {
|
||||
if (!is<HTMLImageElement>(node))
|
||||
return false;
|
||||
|
||||
auto const& element = static_cast<HTMLImageElement const&>(node);
|
||||
if (element.form() != this)
|
||||
return false;
|
||||
|
||||
// FIXME: DOM::Element::name() isn't cached
|
||||
return name == element.id() || name == element.attribute(HTML::AttributeNames::name);
|
||||
});
|
||||
}
|
||||
|
||||
auto length = candidates->length();
|
||||
|
||||
// 3. If candidates is empty, name is the name of one of the entries in the form element's past names map: return the object associated with name in that map.
|
||||
if (length == 0) {
|
||||
auto it = m_past_names_map.find(name);
|
||||
if (it != m_past_names_map.end())
|
||||
return it->value.node;
|
||||
}
|
||||
|
||||
// 4. If candidates contains more than one node, return candidates.
|
||||
if (length > 1)
|
||||
return candidates;
|
||||
|
||||
// 5. Otherwise, candidates contains exactly one node. Add a mapping from name to the node in candidates in the form
|
||||
// element's past names map, replacing the previous entry with the same name, if any.
|
||||
auto const* node = candidates->item(0);
|
||||
m_past_names_map.set(name, HTMLFormElement::PastNameEntry { .node = node, .insertion_time = MonotonicTime::now() });
|
||||
|
||||
// 6. Return the node in candidates.
|
||||
return node;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Time.h>
|
||||
#include <LibWeb/ARIA/Roles.h>
|
||||
#include <LibWeb/HTML/AbstractBrowsingContext.h>
|
||||
#include <LibWeb/HTML/HTMLElement.h>
|
||||
|
@ -95,6 +96,12 @@ private:
|
|||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
// ^PlatformObject
|
||||
virtual WebIDL::ExceptionOr<JS::Value> item_value(size_t index) const override;
|
||||
virtual WebIDL::ExceptionOr<JS::Value> named_item_value(FlyString const& name) const override;
|
||||
virtual Vector<FlyString> supported_property_names() const override;
|
||||
virtual bool is_supported_property_index(u32) const override;
|
||||
|
||||
ErrorOr<void> populate_vector_with_submittable_elements_in_tree_order(JS::NonnullGCPtr<DOM::Element> element, Vector<JS::NonnullGCPtr<DOM::Element>>& elements);
|
||||
|
||||
ErrorOr<String> pick_an_encoding() const;
|
||||
|
@ -113,6 +120,13 @@ private:
|
|||
|
||||
Vector<JS::GCPtr<HTMLElement>> m_associated_elements;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#past-names-map
|
||||
struct PastNameEntry {
|
||||
JS::GCPtr<DOM::Node const> node;
|
||||
MonotonicTime insertion_time;
|
||||
};
|
||||
HashMap<FlyString, PastNameEntry> mutable m_past_names_map;
|
||||
|
||||
JS::GCPtr<DOM::HTMLFormControlsCollection> mutable m_elements;
|
||||
|
||||
bool m_constructing_entry_list { false };
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#import <HTML/HTMLElement.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/semantics.html#htmlformelement
|
||||
[Exposed=Window]
|
||||
[Exposed=Window, LegacyOverrideBuiltIns, LegacyUnenumerableNamedProperties]
|
||||
interface HTMLFormElement : HTMLElement {
|
||||
|
||||
[HTMLConstructor] constructor();
|
||||
|
@ -21,8 +21,8 @@ interface HTMLFormElement : HTMLElement {
|
|||
|
||||
[SameObject] readonly attribute HTMLFormControlsCollection elements;
|
||||
readonly attribute unsigned long length;
|
||||
// FIXME: getter Element (unsigned long index);
|
||||
// FIXME: getter (RadioNodeList or Element) (DOMString name);
|
||||
getter Element (unsigned long index);
|
||||
getter (RadioNodeList or Element) (DOMString name);
|
||||
|
||||
undefined submit();
|
||||
// FIXME: undefined requestSubmit(optional HTMLElement? submitter = null);
|
||||
|
|
Loading…
Add table
Reference in a new issue