mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-16 00:08:59 +00:00
improve Form, notably form.elements
This commit is contained in:
299
src/browser/tests/element/html/form.html
Normal file
299
src/browser/tests/element/html/form.html
Normal file
@@ -0,0 +1,299 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../../testing.js"></script>
|
||||
|
||||
<!-- Test fixtures for form.name -->
|
||||
<form id="form_with_name" name="myForm"></form>
|
||||
<form id="form_without_name"></form>
|
||||
|
||||
<script id="name_initial">
|
||||
{
|
||||
testing.expectEqual('myForm', $('#form_with_name').name)
|
||||
testing.expectEqual('', $('#form_without_name').name)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="name_set">
|
||||
{
|
||||
const form = document.createElement('form')
|
||||
testing.expectEqual('', form.name)
|
||||
|
||||
form.name = 'testForm'
|
||||
testing.expectEqual('testForm', form.name)
|
||||
testing.expectEqual('testForm', form.getAttribute('name'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Test fixtures for form.method -->
|
||||
<form id="form_get" method="get"></form>
|
||||
<form id="form_post" method="post"></form>
|
||||
<form id="form_dialog" method="dialog"></form>
|
||||
<form id="form_default"></form>
|
||||
|
||||
<script id="method_initial">
|
||||
{
|
||||
testing.expectEqual('get', $('#form_get').method)
|
||||
testing.expectEqual('post', $('#form_post').method)
|
||||
testing.expectEqual('dialog', $('#form_dialog').method)
|
||||
testing.expectEqual('get', $('#form_default').method)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="method_set">
|
||||
{
|
||||
const form = document.createElement('form')
|
||||
testing.expectEqual('get', form.method)
|
||||
|
||||
form.method = 'post'
|
||||
testing.expectEqual('post', form.method)
|
||||
testing.expectEqual('post', form.getAttribute('method'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="method_normalization">
|
||||
{
|
||||
const form = document.createElement('form')
|
||||
|
||||
// Test uppercase normalization
|
||||
form.setAttribute('method', 'POST')
|
||||
testing.expectEqual('post', form.method)
|
||||
|
||||
form.setAttribute('method', 'GeT')
|
||||
testing.expectEqual('get', form.method)
|
||||
|
||||
form.setAttribute('method', 'DIALOG')
|
||||
testing.expectEqual('dialog', form.method)
|
||||
|
||||
// Test invalid value defaults to "get"
|
||||
form.setAttribute('method', 'invalid')
|
||||
testing.expectEqual('get', form.method)
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Test fixtures for form.elements -->
|
||||
<form id="form1">
|
||||
<input name="field1" value="value1">
|
||||
<button name="btn1" type="submit">Submit</button>
|
||||
<select name="select1">
|
||||
<option value="opt1">Option 1</option>
|
||||
</select>
|
||||
<textarea name="text1">Text</textarea>
|
||||
</form>
|
||||
|
||||
<!-- Control outside form with form=ID -->
|
||||
<input id="external1" name="external" form="form1">
|
||||
|
||||
<!-- Control outside form without form attribute -->
|
||||
<input id="orphan" name="orphan">
|
||||
|
||||
<script id="elements_collection">
|
||||
{
|
||||
const form = $('#form1')
|
||||
const elements = form.elements
|
||||
|
||||
testing.expectEqual('HTMLFormControlsCollection', elements.constructor.name)
|
||||
testing.expectEqual(5, elements.length)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="length">
|
||||
{
|
||||
testing.expectEqual(5, $('#form1').length)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="elements_indexed_access">
|
||||
{
|
||||
const form = $('#form1')
|
||||
const elements = form.elements
|
||||
|
||||
testing.expectEqual('field1', elements[0].name)
|
||||
testing.expectEqual('INPUT', elements[0].tagName)
|
||||
|
||||
testing.expectEqual('btn1', elements[1].name)
|
||||
testing.expectEqual('BUTTON', elements[1].tagName)
|
||||
|
||||
testing.expectEqual('select1', elements[2].name)
|
||||
testing.expectEqual('SELECT', elements[2].tagName)
|
||||
|
||||
testing.expectEqual('text1', elements[3].name)
|
||||
testing.expectEqual('TEXTAREA', elements[3].tagName)
|
||||
|
||||
testing.expectEqual('external', elements[4].name)
|
||||
testing.expectEqual('INPUT', elements[4].tagName)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="elements_named_access">
|
||||
{
|
||||
const form = $('#form1')
|
||||
const elements = form.elements
|
||||
|
||||
testing.expectEqual('field1', elements.field1.name)
|
||||
testing.expectEqual('btn1', elements.btn1.name)
|
||||
testing.expectEqual('select1', elements.select1.name)
|
||||
testing.expectEqual('text1', elements.text1.name)
|
||||
testing.expectEqual('external', elements.external.name)
|
||||
|
||||
testing.expectEqual('field1', elements.namedItem('field1').name)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="elements_excludes_orphans">
|
||||
{
|
||||
const form = $('#form1')
|
||||
const elements = form.elements
|
||||
|
||||
let foundOrphan = false
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (elements[i].id === 'orphan') {
|
||||
foundOrphan = true
|
||||
}
|
||||
}
|
||||
testing.expectEqual(false, foundOrphan)
|
||||
}
|
||||
</script>
|
||||
|
||||
<form id="form2"></form>
|
||||
|
||||
<script id="elements_live_collection">
|
||||
{
|
||||
const form = $('#form2')
|
||||
testing.expectEqual(0, form.elements.length)
|
||||
|
||||
const input = document.createElement('input')
|
||||
input.name = 'dynamic'
|
||||
form.appendChild(input)
|
||||
|
||||
testing.expectEqual(1, form.elements.length)
|
||||
testing.expectEqual('dynamic', form.elements[0].name)
|
||||
|
||||
input.remove()
|
||||
testing.expectEqual(0, form.elements.length)
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Test with controls that have form attribute pointing to different form -->
|
||||
<form id="form3"></form>
|
||||
<input id="belongs_to_3" name="field3" form="form3">
|
||||
|
||||
<script id="form_attribute_different_form">
|
||||
{
|
||||
const form1 = $('#form1')
|
||||
const form3 = $('#form3')
|
||||
const belongs3 = $('#belongs_to_3')
|
||||
|
||||
let inForm1 = false
|
||||
for (let i = 0; i < form1.elements.length; i++) {
|
||||
if (form1.elements[i].id === 'belongs_to_3') {
|
||||
inForm1 = true
|
||||
}
|
||||
}
|
||||
testing.expectEqual(false, inForm1)
|
||||
|
||||
let inForm3 = false
|
||||
for (let i = 0; i < form3.elements.length; i++) {
|
||||
if (form3.elements[i].id === 'belongs_to_3') {
|
||||
inForm3 = true
|
||||
}
|
||||
}
|
||||
testing.expectEqual(true, inForm3)
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- CRITICAL TEST: Nested control with form attribute pointing elsewhere -->
|
||||
<form id="outer_form">
|
||||
<input id="nested_but_reassigned" name="reassigned" form="form3">
|
||||
<input id="nested_normal" name="normal">
|
||||
</form>
|
||||
|
||||
<script id="nested_control_with_form_attribute">
|
||||
{
|
||||
// This test prevents a dangerous optimization bug:
|
||||
// Even though nested_but_reassigned is physically nested in outer_form,
|
||||
// it has form="form3", so it belongs to form3, NOT outer_form.
|
||||
// We MUST check the form attribute even for nested controls!
|
||||
|
||||
const outerForm = $('#outer_form')
|
||||
const form3 = $('#form3')
|
||||
|
||||
// outer_form should have only 1 element (nested_normal)
|
||||
testing.expectEqual(1, outerForm.elements.length)
|
||||
testing.expectEqual('nested_normal', outerForm.elements[0].id)
|
||||
|
||||
// form3 should have 2 elements (belongs_to_3 and nested_but_reassigned)
|
||||
testing.expectEqual(2, form3.elements.length)
|
||||
|
||||
let foundReassigned = false
|
||||
for (let i = 0; i < form3.elements.length; i++) {
|
||||
if (form3.elements[i].id === 'nested_but_reassigned') {
|
||||
foundReassigned = true
|
||||
}
|
||||
}
|
||||
testing.expectEqual(true, foundReassigned)
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Test radio buttons with same name -->
|
||||
<form id="radio_form">
|
||||
<input type="radio" name="choice" value="a">
|
||||
<input type="radio" name="choice" value="b">
|
||||
<input type="radio" name="choice" value="c">
|
||||
</form>
|
||||
|
||||
<script id="radio_buttons_in_elements">
|
||||
{
|
||||
const form = $('#radio_form')
|
||||
testing.expectEqual(3, form.elements.length)
|
||||
|
||||
testing.expectEqual('a', form.elements[0].value)
|
||||
testing.expectEqual('b', form.elements[1].value)
|
||||
testing.expectEqual('c', form.elements[2].value)
|
||||
|
||||
// Note: In spec-compliant browsers, namedItem with duplicate names returns RadioNodeList
|
||||
// RadioNodeList.value returns the checked radio's value (or "" if none checked)
|
||||
// Our implementation currently returns the first element (TODO: implement RadioNodeList)
|
||||
// For now, test that we can access by index which works in all browsers
|
||||
testing.expectEqual('choice', form.elements[0].name)
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Test disabled controls -->
|
||||
<script id="disabled_controls_included">
|
||||
{
|
||||
const form = document.createElement('form')
|
||||
const input1 = document.createElement('input')
|
||||
input1.name = 'enabled'
|
||||
const input2 = document.createElement('input')
|
||||
input2.name = 'disabled'
|
||||
input2.disabled = true
|
||||
|
||||
form.appendChild(input1)
|
||||
form.appendChild(input2)
|
||||
|
||||
testing.expectEqual(2, form.elements.length)
|
||||
testing.expectEqual('enabled', form.elements[0].name)
|
||||
testing.expectEqual('disabled', form.elements[1].name)
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Test empty form -->
|
||||
<form id="empty_form"></form>
|
||||
|
||||
<script id="empty_form_elements">
|
||||
{
|
||||
const form = $('#empty_form')
|
||||
testing.expectEqual(0, form.elements.length)
|
||||
testing.expectEqual(0, form.length)
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Test form without id can't have external controls -->
|
||||
<form id="form_no_id_attr"></form>
|
||||
<input id="orphan2" name="orphan2" form="nonexistent">
|
||||
|
||||
<script id="form_without_id_no_external">
|
||||
{
|
||||
const form = $('#form_no_id_attr')
|
||||
testing.expectEqual(0, form.elements.length)
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<form id="form1">
|
||||
<input id="has_no_name" value="nope1">
|
||||
<input id="is_disabled" disabled value="nope2">
|
||||
|
||||
<input name="txt-1" value="txt-1-v">
|
||||
<input name="txt-2" value="txt-~-v" type=password>
|
||||
|
||||
<input name="chk-3" value="chk-3-va" type=checkbox>
|
||||
<input name="chk-3" value="chk-3-vb" type=checkbox checked>
|
||||
<input name="chk-3" value="chk-3-vc" type=checkbox checked>
|
||||
<input name="chk-4" value="chk-4-va" type=checkbox>
|
||||
<input name="chk-4" value="chk-4-va" type=checkbox>
|
||||
|
||||
<input name="rdi-1" value="rdi-1-va" type=radio>
|
||||
<input name="rdi-1" value="rdi-1-vb" type=radio>
|
||||
<input name="rdi-1" value="rdi-1-vc" type=radio checked>
|
||||
<input name="rdi-2" value="rdi-2-va" type=radio>
|
||||
<input name="rdi-2" value="rdi-2-vb" type=radio>
|
||||
|
||||
<textarea name="ta-1"> ta-1-v</textarea>
|
||||
<textarea name="ta"></textarea>
|
||||
|
||||
<input type=hidden name=h1 value="h1-v">
|
||||
<input type=hidden name=h2 value="h2-v" disabled=disabled>
|
||||
|
||||
<select name="sel-1"><option>blue<option>red</select>
|
||||
<select name="sel-2"><option>blue<option value=sel-2-v selected>red</select>
|
||||
<select name="sel-3"><option disabled>nope1<option>nope2</select>
|
||||
<select name="mlt-1" multiple><option>water<option>tea</select>
|
||||
<select name="mlt-2" multiple><option selected>water<option selected>tea<option>coffee</select>
|
||||
<input type=submit id=s1 name=s1 value=s1-v>
|
||||
<input type=submit name=s2 value=s2-v>
|
||||
<input type=image name=i1 value=i1-v>
|
||||
</form>
|
||||
<input type=text name=abc value=123 form=form1>
|
||||
|
||||
<script id=formData>
|
||||
let f = new FormData();
|
||||
testing.expectEqual(null, f.get('a'));
|
||||
@@ -87,44 +124,10 @@
|
||||
testing.expectEqual(['h1', 'h1-v'], acc[7]);
|
||||
testing.expectEqual(['sel-1', 'blue'], acc[8]);
|
||||
testing.expectEqual(['sel-2', 'sel-2-v'], acc[9]);
|
||||
testing.expectEqual(['mlt-2', 'water'], acc[10]);
|
||||
testing.expectEqual(['mlt-2', 'tea'], acc[11]);
|
||||
testing.expectEqual(['s1', 's1-v'], acc[12]);
|
||||
testing.expectEqual(['dyn', 'dyn-v'], acc[13]);
|
||||
testing.expectEqual(['sel-3', 'nope2'], acc[10]);
|
||||
testing.expectEqual(['mlt-2', 'water'], acc[11]);
|
||||
testing.expectEqual(['mlt-2', 'tea'], acc[12]);
|
||||
testing.expectEqual(['s1', 's1-v'], acc[13]);
|
||||
</script>
|
||||
|
||||
<form id="form1">
|
||||
<input id="has_no_name" value="nope1">
|
||||
<input id="is_disabled" disabled value="nope2">
|
||||
|
||||
<input name="txt-1" value="txt-1-v">
|
||||
<input name="txt-2" value="txt-~-v" type=password>
|
||||
|
||||
<input name="chk-3" value="chk-3-va" type=checkbox>
|
||||
<input name="chk-3" value="chk-3-vb" type=checkbox checked>
|
||||
<input name="chk-3" value="chk-3-vc" type=checkbox checked>
|
||||
<input name="chk-4" value="chk-4-va" type=checkbox>
|
||||
<input name="chk-4" value="chk-4-va" type=checkbox>
|
||||
|
||||
<input name="rdi-1" value="rdi-1-va" type=radio>
|
||||
<input name="rdi-1" value="rdi-1-vb" type=radio>
|
||||
<input name="rdi-1" value="rdi-1-vc" type=radio checked>
|
||||
<input name="rdi-2" value="rdi-2-va" type=radio>
|
||||
<input name="rdi-2" value="rdi-2-vb" type=radio>
|
||||
|
||||
<textarea name="ta-1"> ta-1-v</textarea>
|
||||
<textarea name="ta"></textarea>
|
||||
|
||||
<input type=hidden name=h1 value="h1-v">
|
||||
<input type=hidden name=h2 value="h2-v" disabled=disabled>
|
||||
|
||||
<select name="sel-1"><option>blue<option>red</select>
|
||||
<select name="sel-2"><option>blue<option value=sel-2-v selected>red</select>
|
||||
<select name="sel-3"><option disabled>nope1<option>nope2</select>
|
||||
<select name="mlt-1" multiple><option>water<option>tea</select>
|
||||
<select name="mlt-2" multiple><option selected>water<option selected>tea<option>coffee</select>
|
||||
<input type=submit id=s1 name=s1 value=s1-v>
|
||||
<input type=submit name=s2 value=s2-v>
|
||||
<input type=image name=i1 value=i1-v>
|
||||
</form>
|
||||
<input type=text name=abc value=123 form=form1>
|
||||
|
||||
@@ -250,3 +250,134 @@
|
||||
testing.expectEqual(3, context.sum);
|
||||
}
|
||||
</script>
|
||||
|
||||
<form id="form1">
|
||||
<input id="has_no_name" value="nope1">
|
||||
<input id="is_disabled" disabled value="nope2">
|
||||
|
||||
<input name="txt-1" value="txt-1-v">
|
||||
<input name="txt-2" value="txt-~-v" type=password>
|
||||
|
||||
<input name="chk-3" value="chk-3-va" type=checkbox>
|
||||
<input name="chk-3" value="chk-3-vb" type=checkbox checked>
|
||||
<input name="chk-3" value="chk-3-vc" type=checkbox checked>
|
||||
<input name="chk-4" value="chk-4-va" type=checkbox>
|
||||
<input name="chk-4" value="chk-4-va" type=checkbox>
|
||||
|
||||
<input name="rdi-1" value="rdi-1-va" type=radio>
|
||||
<input name="rdi-1" value="rdi-1-vb" type=radio>
|
||||
<input name="rdi-1" value="rdi-1-vc" type=radio checked>
|
||||
<input name="rdi-2" value="rdi-2-va" type=radio>
|
||||
<input name="rdi-2" value="rdi-2-vb" type=radio>
|
||||
|
||||
<textarea name="ta-1"> ta-1-v</textarea>
|
||||
<textarea name="ta"></textarea>
|
||||
|
||||
<input type=hidden name=h1 value="h1-v">
|
||||
<input type=hidden name=h2 value="h2-v" disabled=disabled>
|
||||
|
||||
<select name="sel-1"><option>blue<option>red</select>
|
||||
<select name="sel-2"><option>blue<option value=sel-2-v selected>red</select>
|
||||
<select name="sel-3"><option disabled>nope1<option>nope2</select>
|
||||
<select name="mlt-1" multiple><option>water<option>tea</select>
|
||||
<select name="mlt-2" multiple><option selected>water<option selected>tea<option>coffee</select>
|
||||
<input type=submit id=s1 name=s1 value=s1-v>
|
||||
<input type=submit name=s2 value=s2-v>
|
||||
<input type=image name=i1 value=i1-v>
|
||||
</form>
|
||||
<input type=text name=abc value=123 form=form1>
|
||||
|
||||
<script id=formData>
|
||||
let f = new FormData();
|
||||
testing.expectEqual(null, f.get('a'));
|
||||
testing.expectEqual(false, f.has('a'));
|
||||
testing.expectEqual([], f.getAll('a'));
|
||||
testing.expectEqual(undefined, f.delete('a'));
|
||||
|
||||
f.set('a', 1);
|
||||
testing.expectEqual(true, f.has('a'));
|
||||
testing.expectEqual('1', f.get('a'));
|
||||
testing.expectEqual(['1'], f.getAll('a'));
|
||||
|
||||
f.append('a', 2);
|
||||
testing.expectEqual(true, f.has('a'));
|
||||
testing.expectEqual('1', f.get('a'));
|
||||
testing.expectEqual(['1', '2'], f.getAll('a'));
|
||||
|
||||
f.append('b', '3');
|
||||
testing.expectEqual(true, f.has('a'));
|
||||
testing.expectEqual('1', f.get('a'));
|
||||
testing.expectEqual(['1', '2'], f.getAll('a'));
|
||||
testing.expectEqual(true, f.has('b'));
|
||||
testing.expectEqual('3', f.get('b'));
|
||||
testing.expectEqual(['3'], f.getAll('b'));
|
||||
|
||||
let acc = [];
|
||||
for (const key of f.keys()) { acc.push(key) }
|
||||
testing.expectEqual(['a', 'a', 'b'], acc);
|
||||
|
||||
acc = [];
|
||||
for (const value of f.values()) { acc.push(value) }
|
||||
testing.expectEqual(['1', '2', '3'], acc);
|
||||
|
||||
acc = [];
|
||||
for (const entry of f.entries()) { acc.push(entry) }
|
||||
testing.expectEqual([['a', '1'], ['a', '2'], ['b', '3']], acc);
|
||||
|
||||
acc = [];
|
||||
for (const entry of f) { acc.push(entry) };
|
||||
testing.expectEqual([['a', '1'], ['a', '2'], ['b', '3']], acc);
|
||||
|
||||
f.delete('a');
|
||||
testing.expectEqual(false, f.has('a'));
|
||||
testing.expectEqual(true, f.has('b'));
|
||||
|
||||
acc = [];
|
||||
for (const key of f.keys()) { acc.push(key) }
|
||||
testing.expectEqual(['b'], acc);
|
||||
|
||||
acc = [];
|
||||
for (const value of f.values()) { acc.push(value) }
|
||||
testing.expectEqual(['3'], acc);
|
||||
|
||||
acc = [];
|
||||
for (const entry of f.entries()) { acc.push(entry) }
|
||||
testing.expectEqual([['b', '3']], acc);
|
||||
|
||||
acc = [];
|
||||
for (const entry of f) { acc.push(entry) }
|
||||
testing.expectEqual([['b', '3']], acc);
|
||||
</script>
|
||||
|
||||
<!-- <script id=serialize>
|
||||
{
|
||||
let form1 = $('#form1');
|
||||
let submit1 = $('#s1');
|
||||
|
||||
let input = document.createElement('input');
|
||||
input.name = 'dyn';
|
||||
input.value = 'dyn-v';
|
||||
form1.appendChild(input);
|
||||
let f2 = new FormData(form1, submit1);
|
||||
|
||||
acc = [];
|
||||
for (const entry of f2) {
|
||||
acc.push(entry);
|
||||
};
|
||||
|
||||
testing.expectEqual(['txt-1', 'txt-1-v'], acc[0]);
|
||||
testing.expectEqual(['txt-2', 'txt-~-v'], acc[1]);
|
||||
testing.expectEqual(['chk-3', 'chk-3-vb'], acc[2]);
|
||||
testing.expectEqual(['chk-3', 'chk-3-vc'], acc[3]);
|
||||
testing.expectEqual(['rdi-1', 'rdi-1-vc'], acc[4]);
|
||||
testing.expectEqual(['ta-1', ' ta-1-v'], acc[5]);
|
||||
testing.expectEqual(['ta', ''], acc[6]);
|
||||
testing.expectEqual(['h1', 'h1-v'], acc[7]);
|
||||
testing.expectEqual(['sel-1', 'blue'], acc[8]);
|
||||
testing.expectEqual(['sel-2', 'sel-2-v'], acc[9]);
|
||||
testing.expectEqual(['sel-3', 'nope2'], acc[10]);
|
||||
testing.expectEqual(['mlt-2', 'water'], acc[11]);
|
||||
testing.expectEqual(['mlt-2', 'tea'], acc[12]);
|
||||
testing.expectEqual(['s1', 's1-v'], acc[13]);
|
||||
}
|
||||
</script> -->
|
||||
|
||||
@@ -339,7 +339,7 @@ pub fn isConnected(self: *const Node) bool {
|
||||
const GetRootNodeOpts = struct {
|
||||
composed: bool = false,
|
||||
};
|
||||
pub fn getRootNode(self: *const Node, opts_: ?GetRootNodeOpts) *const Node {
|
||||
pub fn getRootNode(self: *Node, opts_: ?GetRootNodeOpts) *Node {
|
||||
const opts = opts_ orelse GetRootNodeOpts{};
|
||||
|
||||
var root = self;
|
||||
@@ -613,7 +613,7 @@ pub fn cloneNode(self: *Node, deep_: ?bool, page: *Page) error{ OutOfMemory, Str
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compareDocumentPosition(self: *const Node, other: *const Node) u16 {
|
||||
pub fn compareDocumentPosition(self: *Node, other: *Node) u16 {
|
||||
const DISCONNECTED: u16 = 0x01;
|
||||
const PRECEDING: u16 = 0x02;
|
||||
const FOLLOWING: u16 = 0x04;
|
||||
|
||||
@@ -21,6 +21,7 @@ pub const ChildNodes = @import("collections/ChildNodes.zig");
|
||||
pub const DOMTokenList = @import("collections/DOMTokenList.zig");
|
||||
pub const HTMLAllCollection = @import("collections/HTMLAllCollection.zig");
|
||||
pub const HTMLOptionsCollection = @import("collections/HTMLOptionsCollection.zig");
|
||||
pub const HTMLFormControlsCollection = @import("collections/HTMLFormControlsCollection.zig");
|
||||
|
||||
pub fn registerTypes() []const type {
|
||||
return &.{
|
||||
@@ -33,6 +34,7 @@ pub fn registerTypes() []const type {
|
||||
@import("collections/HTMLAllCollection.zig"),
|
||||
@import("collections/HTMLAllCollection.zig").Iterator,
|
||||
HTMLOptionsCollection,
|
||||
HTMLFormControlsCollection,
|
||||
DOMTokenList,
|
||||
DOMTokenList.KeyIterator,
|
||||
DOMTokenList.ValueIterator,
|
||||
|
||||
@@ -23,6 +23,7 @@ const Page = @import("../../Page.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
const TreeWalker = @import("../TreeWalker.zig");
|
||||
const NodeLive = @import("node_live.zig").NodeLive;
|
||||
const Form = @import("../element/html/Form.zig");
|
||||
|
||||
const Mode = enum {
|
||||
tag,
|
||||
@@ -35,11 +36,13 @@ const Mode = enum {
|
||||
selected_options,
|
||||
links,
|
||||
anchors,
|
||||
form,
|
||||
};
|
||||
|
||||
const HTMLCollection = @This();
|
||||
|
||||
data: union(Mode) {
|
||||
_type: Type = .{ .generic = {} },
|
||||
_data: union(Mode) {
|
||||
tag: NodeLive(.tag),
|
||||
tag_name: NodeLive(.tag_name),
|
||||
class_name: NodeLive(.class_name),
|
||||
@@ -50,22 +53,28 @@ data: union(Mode) {
|
||||
selected_options: NodeLive(.selected_options),
|
||||
links: NodeLive(.links),
|
||||
anchors: NodeLive(.anchors),
|
||||
form: NodeLive(.form),
|
||||
},
|
||||
|
||||
const Type = union(enum) {
|
||||
generic: void,
|
||||
form: *Form,
|
||||
};
|
||||
|
||||
pub fn length(self: *HTMLCollection, page: *const Page) u32 {
|
||||
return switch (self.data) {
|
||||
return switch (self._data) {
|
||||
inline else => |*impl| impl.length(page),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getAtIndex(self: *HTMLCollection, index: usize, page: *const Page) ?*Element {
|
||||
return switch (self.data) {
|
||||
return switch (self._data) {
|
||||
inline else => |*impl| impl.getAtIndex(index, page),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getByName(self: *HTMLCollection, name: []const u8, page: *Page) ?*Element {
|
||||
return switch (self.data) {
|
||||
return switch (self._data) {
|
||||
inline else => |*impl| impl.getByName(name, page),
|
||||
};
|
||||
}
|
||||
@@ -73,7 +82,7 @@ pub fn getByName(self: *HTMLCollection, name: []const u8, page: *Page) ?*Element
|
||||
pub fn iterator(self: *HTMLCollection, page: *Page) !*Iterator {
|
||||
return Iterator.init(.{
|
||||
.list = self,
|
||||
.tw = switch (self.data) {
|
||||
.tw = switch (self._data) {
|
||||
.tag => |*impl| .{ .tag = impl._tw.clone() },
|
||||
.tag_name => |*impl| .{ .tag_name = impl._tw.clone() },
|
||||
.class_name => |*impl| .{ .class_name = impl._tw.clone() },
|
||||
@@ -84,6 +93,7 @@ pub fn iterator(self: *HTMLCollection, page: *Page) !*Iterator {
|
||||
.selected_options => |*impl| .{ .selected_options = impl._tw.clone() },
|
||||
.links => |*impl| .{ .links = impl._tw.clone() },
|
||||
.anchors => |*impl| .{ .anchors = impl._tw.clone() },
|
||||
.form => |*impl| .{ .form = impl._tw.clone() },
|
||||
},
|
||||
}, page);
|
||||
}
|
||||
@@ -102,10 +112,11 @@ pub const Iterator = GenericIterator(struct {
|
||||
selected_options: TreeWalker.Children,
|
||||
links: TreeWalker.FullExcludeSelf,
|
||||
anchors: TreeWalker.FullExcludeSelf,
|
||||
form: TreeWalker.FullExcludeSelf,
|
||||
},
|
||||
|
||||
pub fn next(self: *@This(), _: *Page) ?*Element {
|
||||
return switch (self.list.data) {
|
||||
return switch (self.list._data) {
|
||||
.tag => |*impl| impl.nextTw(&self.tw.tag),
|
||||
.tag_name => |*impl| impl.nextTw(&self.tw.tag_name),
|
||||
.class_name => |*impl| impl.nextTw(&self.tw.class_name),
|
||||
@@ -116,6 +127,7 @@ pub const Iterator = GenericIterator(struct {
|
||||
.selected_options => |*impl| impl.nextTw(&self.tw.selected_options),
|
||||
.links => |*impl| impl.nextTw(&self.tw.links),
|
||||
.anchors => |*impl| impl.nextTw(&self.tw.anchors),
|
||||
.form => |*impl| impl.nextTw(&self.tw.form),
|
||||
};
|
||||
}
|
||||
}, null);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
const HTMLCollection = @import("HTMLCollection.zig");
|
||||
|
||||
const HTMLFormControlsCollection = @This();
|
||||
|
||||
_proto: *HTMLCollection,
|
||||
|
||||
pub fn length(self: *HTMLFormControlsCollection, page: *Page) u32 {
|
||||
return self._proto.length(page);
|
||||
}
|
||||
|
||||
pub fn getAtIndex(self: *HTMLFormControlsCollection, index: usize, page: *Page) ?*Element {
|
||||
return self._proto.getAtIndex(index, page);
|
||||
}
|
||||
|
||||
pub fn namedItem(self: *HTMLFormControlsCollection, name: []const u8, page: *Page) ?*Element {
|
||||
// TODO: When multiple elements have same name (radio buttons),
|
||||
// should return RadioNodeList instead of first element
|
||||
return self._proto.getByName(name, page);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(HTMLFormControlsCollection);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "HTMLFormControlsCollection";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
pub const manage = false;
|
||||
};
|
||||
|
||||
pub const length = bridge.accessor(HTMLFormControlsCollection.length, null, .{});
|
||||
pub const @"[int]" = bridge.indexed(HTMLFormControlsCollection.getAtIndex, .{ .null_as_undefined = true });
|
||||
pub const @"[str]" = bridge.namedIndexed(HTMLFormControlsCollection.namedItem, null, null, .{ .null_as_undefined = true });
|
||||
pub const namedItem = bridge.function(HTMLFormControlsCollection.namedItem, .{});
|
||||
};
|
||||
@@ -28,6 +28,7 @@ const Node = @import("../Node.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
const TreeWalker = @import("../TreeWalker.zig");
|
||||
const Selector = @import("../selector/Selector.zig");
|
||||
const Form = @import("../element/html/Form.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
@@ -42,6 +43,7 @@ const Mode = enum {
|
||||
selected_options,
|
||||
links,
|
||||
anchors,
|
||||
form,
|
||||
};
|
||||
|
||||
const Filters = union(Mode) {
|
||||
@@ -55,6 +57,7 @@ const Filters = union(Mode) {
|
||||
selected_options,
|
||||
links,
|
||||
anchors,
|
||||
form: *Form,
|
||||
|
||||
fn TypeOf(comptime mode: Mode) type {
|
||||
@setEvalBranchQuota(2000);
|
||||
@@ -82,7 +85,7 @@ const Filters = union(Mode) {
|
||||
pub fn NodeLive(comptime mode: Mode) type {
|
||||
const Filter = Filters.TypeOf(mode);
|
||||
const TW = switch (mode) {
|
||||
.tag, .tag_name, .class_name, .name, .all_elements, .links, .anchors => TreeWalker.FullExcludeSelf,
|
||||
.tag, .tag_name, .class_name, .name, .all_elements, .links, .anchors, .form => TreeWalker.FullExcludeSelf,
|
||||
.child_elements, .child_tag, .selected_options => TreeWalker.Children,
|
||||
};
|
||||
return struct {
|
||||
@@ -259,9 +262,46 @@ pub fn NodeLive(comptime mode: Mode) type {
|
||||
if (el.is(Anchor) == null) return false;
|
||||
return el.hasAttributeSafe("name");
|
||||
},
|
||||
.form => {
|
||||
const el = node.is(Element) orelse return false;
|
||||
if (!isFormControl(el)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (el.getAttributeSafe("form")) |form_attr| {
|
||||
const form_id = self._filter.asElement().getAttributeSafe("id") orelse return false;
|
||||
return std.mem.eql(u8, form_attr, form_id);
|
||||
}
|
||||
|
||||
// No form attribute - match if descendant of our form
|
||||
// This does an O(depth) ancestor walk for each control in the form.
|
||||
//
|
||||
// TODO: If profiling shows this is a bottleneck:
|
||||
// When we first encounter the form element during tree walk, we could
|
||||
// do a one-time reverse walk to find the LAST control that belongs to
|
||||
// this form (checking both form controls and their form= attributes).
|
||||
// Store that element in a new FormState. Then as we traverse
|
||||
// forward:
|
||||
// - Set is_within_form = true when we enter the form element
|
||||
// - Return true immediately for any control while is_within_form
|
||||
// - Set is_within_form = false when we reach that last element
|
||||
// This trades one O(form_size) reverse walk for N O(depth) ancestor
|
||||
// checks, where N = number of controls. For forms with many nested
|
||||
// controls, this could be significantly faster.
|
||||
return self._filter.asNode().contains(node);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn isFormControl(el: *Element) bool {
|
||||
if (el._type != .html) return false;
|
||||
const html = el._type.html;
|
||||
return switch (html._type) {
|
||||
.input, .button, .select, .text_area => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn versionCheck(self: *Self, page: *const Page) bool {
|
||||
const current = page.version;
|
||||
if (current == self._cached_version) {
|
||||
@@ -278,16 +318,17 @@ pub fn NodeLive(comptime mode: Mode) type {
|
||||
const HTMLCollection = @import("HTMLCollection.zig");
|
||||
pub fn runtimeGenericWrap(self: Self, page: *Page) !*HTMLCollection {
|
||||
const collection = switch (mode) {
|
||||
.tag => HTMLCollection{ .data = .{ .tag = self } },
|
||||
.tag_name => HTMLCollection{ .data = .{ .tag_name = self } },
|
||||
.class_name => HTMLCollection{ .data = .{ .class_name = self } },
|
||||
.name => HTMLCollection{ .data = .{ .name = self } },
|
||||
.all_elements => HTMLCollection{ .data = .{ .all_elements = self } },
|
||||
.child_elements => HTMLCollection{ .data = .{ .child_elements = self } },
|
||||
.child_tag => HTMLCollection{ .data = .{ .child_tag = self } },
|
||||
.selected_options => HTMLCollection{ .data = .{ .selected_options = self } },
|
||||
.links => HTMLCollection{ .data = .{ .links = self } },
|
||||
.anchors => HTMLCollection{ .data = .{ .anchors = self } },
|
||||
.tag => HTMLCollection{ ._data = .{ .tag = self } },
|
||||
.tag_name => HTMLCollection{ ._data = .{ .tag_name = self } },
|
||||
.class_name => HTMLCollection{ ._data = .{ .class_name = self } },
|
||||
.name => HTMLCollection{ ._data = .{ .name = self } },
|
||||
.all_elements => HTMLCollection{ ._data = .{ .all_elements = self } },
|
||||
.child_elements => HTMLCollection{ ._data = .{ .child_elements = self } },
|
||||
.child_tag => HTMLCollection{ ._data = .{ .child_tag = self } },
|
||||
.selected_options => HTMLCollection{ ._data = .{ .selected_options = self } },
|
||||
.links => HTMLCollection{ ._data = .{ .links = self } },
|
||||
.anchors => HTMLCollection{ ._data = .{ .anchors = self } },
|
||||
.form => HTMLCollection{ ._type = .{ .form = self._filter }, ._data = .{ .form = self } },
|
||||
};
|
||||
return page._factory.create(collection);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const HtmlElement = @import("../Html.zig");
|
||||
const TreeWalker = @import("../../TreeWalker.zig");
|
||||
const collections = @import("../../collections.zig");
|
||||
|
||||
const Input = @import("Input.zig");
|
||||
const Button = @import("Button.zig");
|
||||
@@ -32,6 +33,9 @@ const TextArea = @import("TextArea.zig");
|
||||
const Form = @This();
|
||||
_proto: *HtmlElement,
|
||||
|
||||
fn asConstElement(self: *const Form) *const Element {
|
||||
return self._proto._proto;
|
||||
}
|
||||
pub fn asElement(self: *Form) *Element {
|
||||
return self._proto._proto;
|
||||
}
|
||||
@@ -39,90 +43,49 @@ pub fn asNode(self: *Form) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
|
||||
// Untested / unused right now. Iterates over all the controls of a form,
|
||||
// including those outside the <form>...</form> but with a form=$FORM_ID attribute
|
||||
pub const Iterator = struct {
|
||||
_form_id: ?[]const u8,
|
||||
_walkers: union(enum) {
|
||||
nested: TreeWalker.FullExcludeSelf,
|
||||
names: TreeWalker.FullExcludeSelf,
|
||||
},
|
||||
pub fn getName(self: *const Form) []const u8 {
|
||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
||||
}
|
||||
|
||||
pub fn init(form: *Form) Iterator {
|
||||
const form_element = form.asElement();
|
||||
const form_id = form_element.getAttributeSafe("id");
|
||||
pub fn setName(self: *Form, name: []const u8, page: *Page) !void {
|
||||
try self.asElement().setAttributeSafe("name", name, page);
|
||||
}
|
||||
|
||||
return .{
|
||||
._form_id = form_id,
|
||||
._walkers = .{
|
||||
.nested = TreeWalker.FullExcludeSelf.init(form.asNode(), .{}),
|
||||
},
|
||||
};
|
||||
pub fn getMethod(self: *const Form) []const u8 {
|
||||
const method = self.asConstElement().getAttributeSafe("method") orelse return "get";
|
||||
|
||||
if (std.ascii.eqlIgnoreCase(method, "post")) {
|
||||
return "post";
|
||||
}
|
||||
|
||||
pub fn next(self: *Iterator) ?FormControl {
|
||||
switch (self._walkers) {
|
||||
.nested => |*tw| {
|
||||
// find controls nested directly in the form
|
||||
while (tw.next()) |node| {
|
||||
const element = node.is(Element) orelse continue;
|
||||
const control = asFormControl(element) orelse continue;
|
||||
// Skip if it has a form attribute (will be handled in phase 2)
|
||||
if (element.getAttributeSafe("form") == null) {
|
||||
return control;
|
||||
}
|
||||
}
|
||||
if (self._form_id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const doc = tw._root.getRootNode();
|
||||
self._walkers = .{
|
||||
.names = TreeWalker.FullExcludeSelf(doc, .{}),
|
||||
};
|
||||
return self.next();
|
||||
},
|
||||
.names => |*tw| {
|
||||
// find controls with a name matching the form id
|
||||
while (tw.next()) |node| {
|
||||
const input = node.is(Input) orelse continue;
|
||||
if (input._type != .radio) {
|
||||
continue;
|
||||
}
|
||||
const input_form = input.asElement().getAttributeSafe("form") orelse continue;
|
||||
// must have a self._form_id, else we never would have transitioned
|
||||
// from a nested walker to a namew walker
|
||||
if (!std.mem.eql(u8, input_form, self._form_id.?)) {
|
||||
continue;
|
||||
}
|
||||
return .{ .input = input };
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}
|
||||
if (std.ascii.eqlIgnoreCase(method, "dialog")) {
|
||||
return "dialog";
|
||||
}
|
||||
};
|
||||
// invalid, or it was get all along
|
||||
return "get";
|
||||
}
|
||||
|
||||
pub const FormControl = union(enum) {
|
||||
input: *Input,
|
||||
button: *Button,
|
||||
select: *Select,
|
||||
textarea: *TextArea,
|
||||
};
|
||||
pub fn setMethod(self: *Form, method: []const u8, page: *Page) !void {
|
||||
try self.asElement().setAttributeSafe("method", method, page);
|
||||
}
|
||||
|
||||
fn asFormControl(element: *Element) ?FormControl {
|
||||
if (element._type != .html) {
|
||||
return null;
|
||||
}
|
||||
const html = element._type.html;
|
||||
switch (html._type) {
|
||||
.input => |cntrl| return .{ .input = cntrl },
|
||||
.button => |cntrl| return .{ .button = cntrl },
|
||||
.select => |cntrl| return .{ .select = cntrl },
|
||||
.textarea => |cntrl| return .{ .textarea = cntrl },
|
||||
else => return null,
|
||||
}
|
||||
pub fn getElements(self: *Form, page: *Page) !*collections.HTMLFormControlsCollection {
|
||||
const form_id = self.asElement().getAttributeSafe("id");
|
||||
const root = if (form_id != null)
|
||||
self.asNode().getRootNode(null) // Has ID: walk entire document to find form=ID controls
|
||||
else
|
||||
self.asNode(); // No ID: walk only form subtree (no external controls possible)
|
||||
|
||||
const node_live = collections.NodeLive(.form).init(root, self, page);
|
||||
const html_collection = try node_live.runtimeGenericWrap(page);
|
||||
|
||||
return page._factory.create(collections.HTMLFormControlsCollection{
|
||||
._proto = html_collection,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getLength(self: *Form, page: *Page) !u32 {
|
||||
const elements = try self.getElements(page);
|
||||
return elements.length(page);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
@@ -132,4 +95,14 @@ pub const JsApi = struct {
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const name = bridge.accessor(Form.getName, Form.setName, .{});
|
||||
pub const method = bridge.accessor(Form.getMethod, Form.setMethod, .{});
|
||||
pub const elements = bridge.accessor(Form.getElements, null, .{});
|
||||
pub const length = bridge.accessor(Form.getLength, null, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../../../testing.zig");
|
||||
test "WebApi: HTML.Form" {
|
||||
try testing.htmlRunner("element/html/form.html", .{});
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ const log = @import("../../../log.zig");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Form = @import("../element/html/Form.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
const KeyValueList = @import("../KeyValueList.zig");
|
||||
|
||||
const Alloctor = std.mem.Allocator;
|
||||
@@ -31,7 +33,9 @@ const FormData = @This();
|
||||
_arena: Alloctor,
|
||||
_list: KeyValueList,
|
||||
|
||||
pub fn init(page: *Page) !*FormData {
|
||||
pub fn init(form_: ?*Form, submitter_: ?*Element, page: *Page) !*FormData {
|
||||
_ = form_;
|
||||
_ = submitter_;
|
||||
return page._factory.create(FormData{
|
||||
._arena = page.arena,
|
||||
._list = KeyValueList.init(),
|
||||
@@ -127,6 +131,97 @@ pub const JsApi = struct {
|
||||
pub const forEach = bridge.function(FormData.forEach, .{});
|
||||
};
|
||||
|
||||
// fn collectForm(form: *Form, submitter_: ?*Element, page: *Page) !KeyValueList {
|
||||
// const arena = page.arena;
|
||||
|
||||
// // Don't use libdom's formGetCollection (aka dom_html_form_element_get_elements)
|
||||
// // It doesn't work with dynamically added elements, because their form
|
||||
// // property doesn't get set. We should fix that.
|
||||
// // However, even once fixed, there are other form-collection features we
|
||||
// // probably want to implement (like disabled fieldsets), so we might want
|
||||
// // to stick with our own walker even if fix libdom to properly support
|
||||
// // dynamically added elements.
|
||||
// const node_list = try @import("../dom/css.zig").querySelectorAll(arena, @ptrCast(@alignCast(form)), "input,select,button,textarea");
|
||||
// const nodes = node_list.nodes.items;
|
||||
|
||||
// var entries: kv.List = .{};
|
||||
// try entries.ensureTotalCapacity(arena, nodes.len);
|
||||
|
||||
// var submitter_included = false;
|
||||
// const submitter_name_ = try getSubmitterName(submitter_);
|
||||
|
||||
// for (nodes) |node| {
|
||||
// const element = parser.nodeToElement(node);
|
||||
|
||||
// // must have a name
|
||||
// const name = try parser.elementGetAttribute(element, "name") orelse continue;
|
||||
// if (try parser.elementGetAttribute(element, "disabled") != null) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// const tag = try parser.elementTag(element);
|
||||
// switch (tag) {
|
||||
// .input => {
|
||||
// const tpe = try parser.inputGetType(@ptrCast(element));
|
||||
// if (std.ascii.eqlIgnoreCase(tpe, "image")) {
|
||||
// if (submitter_name_) |submitter_name| {
|
||||
// if (std.mem.eql(u8, submitter_name, name)) {
|
||||
// const key_x = try std.fmt.allocPrint(arena, "{s}.x", .{name});
|
||||
// const key_y = try std.fmt.allocPrint(arena, "{s}.y", .{name});
|
||||
// try entries.appendOwned(arena, key_x, "0");
|
||||
// try entries.appendOwned(arena, key_y, "0");
|
||||
// submitter_included = true;
|
||||
// }
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (std.ascii.eqlIgnoreCase(tpe, "checkbox") or std.ascii.eqlIgnoreCase(tpe, "radio")) {
|
||||
// if (try parser.inputGetChecked(@ptrCast(element)) == false) {
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// if (std.ascii.eqlIgnoreCase(tpe, "submit")) {
|
||||
// if (submitter_name_ == null or !std.mem.eql(u8, submitter_name_.?, name)) {
|
||||
// continue;
|
||||
// }
|
||||
// submitter_included = true;
|
||||
// }
|
||||
// const value = try parser.inputGetValue(@ptrCast(element));
|
||||
// try entries.appendOwned(arena, name, value);
|
||||
// },
|
||||
// .select => {
|
||||
// const select: *parser.Select = @ptrCast(node);
|
||||
// try collectSelectValues(arena, select, name, &entries, page);
|
||||
// },
|
||||
// .textarea => {
|
||||
// const textarea: *parser.TextArea = @ptrCast(node);
|
||||
// const value = try parser.textareaGetValue(textarea);
|
||||
// try entries.appendOwned(arena, name, value);
|
||||
// },
|
||||
// .button => if (submitter_name_) |submitter_name| {
|
||||
// if (std.mem.eql(u8, submitter_name, name)) {
|
||||
// const value = (try parser.elementGetAttribute(element, "value")) orelse "";
|
||||
// try entries.appendOwned(arena, name, value);
|
||||
// submitter_included = true;
|
||||
// }
|
||||
// },
|
||||
// else => unreachable,
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (submitter_included == false) {
|
||||
// if (submitter_name_) |submitter_name| {
|
||||
// // this can happen if the submitter is outside the form, but associated
|
||||
// // with the form via a form=ID attribute
|
||||
// const value = (try parser.elementGetAttribute(@ptrCast(submitter_.?), "value")) orelse "";
|
||||
// try entries.appendOwned(arena, submitter_name, value);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return entries;
|
||||
// }
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
test "WebApi: FormData" {
|
||||
try testing.htmlRunner("net/form_data.html", .{});
|
||||
|
||||
Reference in New Issue
Block a user