Files
Zachary D. Rowitsch 9ca0a123bb
All checks were successful
ci / fast (linux) (push) Successful in 7m16s
Add form submission with textarea, submitter tracking, multipart encoding, and submit events (Story 4.4)
Implement full form submission pipeline: textarea form data collection with CR+LF newline
normalization, submit button value inclusion via submitter tracking (Option<NodeId>),
multipart/form-data and text/plain encoding support, configurable content_type threading
through navigate_post → NetworkStack → HttpLoader, and cancelable submit event dispatch
with preventDefault support at all three trigger sites. Includes review fixes for UTF-8
safety in newline normalization, RFC 7578 field name escaping in multipart encoding, and
boundary collision avoidance. 15 unit tests, 3 integration tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:45:32 -04:00

17 KiB

Story 4.4: Form Submission

Status: done

Story

As a web user, I want forms to submit my data to the server, so that I can log in, search, and interact with web applications.

Acceptance Criteria

  1. GET form submission: A <form method="GET"> with filled controls serializes data as application/x-www-form-urlencoded and appends it to the action URL as a query string, then navigates to the resulting URL. Per HTML §4.10.21.3.

  2. POST form submission: A <form method="POST"> with filled controls serializes data as application/x-www-form-urlencoded in the request body, then navigates to the action URL with the POST body. Per HTML §4.10.21.3.

  3. Multipart/form-data encoding: A <form enctype="multipart/form-data" method="POST"> serializes data using multipart MIME format with correct boundary delimiters per RFC 2388. Each field becomes a MIME part with Content-Disposition: form-data; name="..." headers.

  4. Excluded form controls: Disabled controls, controls without name attributes, unchecked checkboxes/radios, and buttons that are not the submitter are excluded from submitted data. Per HTML §4.10.21.3.

  5. Form action & method attributes: The action attribute determines the submission URL (defaults to current page URL). The method attribute determines GET or POST (defaults to GET, case-insensitive). The enctype attribute determines encoding (defaults to application/x-www-form-urlencoded). Per HTML §4.10.6.

  6. Textarea form submission: A <textarea name="..."> element includes its runtime text content (from input_states) in form data. Newlines are normalized to CR+LF per HTML §4.10.11.

  7. Submit button value inclusion: When a specific submit button triggers submission, its name/value pair is included in the form data. Other submit buttons in the same form are excluded. Per HTML §4.10.21.3.

  8. Submit event dispatch: Before actual submission, a submit event (cancelable, bubbles) is dispatched on the form element. If event.preventDefault() is called, submission is cancelled. Per HTML §4.10.21.3.

  9. Integration tests verify each submission method and encoding, golden tests are not affected (form submission is runtime behavior), and just ci passes.

What NOT to Implement

  • No file input support -- <input type="file"> is deferred; multipart encoding only handles text fields in this story.
  • No <input type="image"> coordinate submission -- image inputs deferred.
  • No formaction/formmethod/formenctype overrides on buttons -- deferred to a future story.
  • No target attribute support -- all submissions navigate in the current browsing context.
  • No form validation before submission -- that's Story 4.5. This story submits unconditionally (except submit event cancellation).
  • No <input type="hidden"> special handling -- hidden inputs already work as text inputs with name/value.

Tasks / Subtasks

  • Task 1: Add textarea to form data collection (AC: #6)

    • 1.1 In form.rs collect_form_data_recursive(), add a "textarea" branch: read runtime text from input_states (falling back to DOM text_content()), add name=value to form data
    • 1.2 Normalize textarea newlines to CR+LF (\r\n) before encoding per HTML §4.10.11
    • 1.3 Add unit tests in form_tests.rs: textarea with runtime value, textarea with DOM-only value, textarea without name (excluded), disabled textarea (excluded)
  • Task 2: Add submit button value to form data (AC: #7)

    • 2.1 Add submitter: Option<NodeId> parameter to submit_form() and collect_form_data() — the NodeId of the button/input that triggered submission
    • 2.2 In collect_form_data_recursive(), when encountering type="submit" input or <button type="submit">: include its name/value ONLY if it matches the submitter NodeId
    • 2.3 Thread submitter from click handlers in event_handler.rs through to submit_form()
    • 2.4 For Enter-key submission, submitter is None (no button value included) — matches spec behavior
    • 2.5 Add unit tests: submit button value included when it's the submitter, excluded when it's not the submitter, excluded when submitter is None
  • Task 3: Implement multipart/form-data encoding (AC: #3)

    • 3.1 Add enctype attribute reading in submit_form(): parse enctype from form element (default application/x-www-form-urlencoded, also support multipart/form-data and text/plain)
    • 3.2 Create encode_multipart_form_data() function in form.rs: generate a random boundary string, encode each name=value pair as a MIME part with Content-Disposition: form-data; name="..." header, separated by --boundary lines
    • 3.3 Update FormSubmission::Post to include content_type: String field (instead of hardcoded application/x-www-form-urlencoded)
    • 3.4 Update navigate_post() in app_state.rs and post_form() in net to accept and use the content type from FormSubmission
    • 3.5 In http_loader.rs post_form_with_headers(), use the provided content type instead of hardcoded application/x-www-form-urlencoded
    • 3.6 Add unit tests: multipart encoding format correctness (boundary, headers, body parts), text/plain encoding (simple name=value lines)
  • Task 4: Dispatch submit event before submission (AC: #8)

    • 4.1 In event_handler.rs, before calling submit_form(), dispatch a submit event on the form element: {type: "submit", bubbles: true, cancelable: true}
    • 4.2 Use the existing event dispatch infrastructure (dispatch_dom_event or similar) — check how change event is dispatched in Story 4.1/4.3 for the pattern
    • 4.3 If the submit event's defaultPrevented is true after dispatch, skip the form submission entirely
    • 4.4 Add integration tests in js_events.rs: submit event fires, submit event bubbles, submit event can be cancelled via preventDefault
  • Task 5: Wire everything together and update navigation (AC: #1, #2, #5)

    • 5.1 Update all three form submission trigger sites in event_handler.rs (input[type=submit], button[type=submit], Enter key) to: dispatch submit event first (Task 4), pass submitter NodeId (Task 2), then call submit_form()
    • 5.2 Update navigate_post() in app_state.rs to accept content_type parameter and pass it through to NetworkStack::post_form()
    • 5.3 Update NetworkStack::post_form() to accept and use content_type parameter
    • 5.4 Verify GET submission still appends query string correctly (existing behavior)
    • 5.5 Verify POST submission still sends body correctly (existing behavior)
  • Task 6: Integration tests and CI (AC: #9)

    • 6.1 Add integration test: GET form submission with mixed controls (text, checkbox, radio, select, textarea) produces correct query string
    • 6.2 Add integration test: POST form submission produces correct body
    • 6.3 Add integration test: disabled controls and nameless controls excluded
    • 6.4 Add integration test: submit button value included only for submitter
    • 6.5 Verify all existing form tests pass (no regressions)
    • 6.6 Run just ci — must pass
    • 6.7 Update docs/HTML5_Implementation_Checklist.md with form submission support

Dev Notes

Existing Infrastructure (DO NOT REBUILD)

Form data collection already works (crates/app_browser/src/form.rs):

  • submit_form() (lines ~155-240): Reads method/action from form, resolves URLs, collects data, encodes, returns FormSubmission::Get{url} or FormSubmission::Post{url, body}
  • collect_form_data_recursive() (lines ~53-119): Walks DOM tree, handles <input> (text/checkbox/radio), <select> (via collect_select_data()). MISSING: textarea and submit button value.
  • find_ancestor_form() (lines ~17-28): Walks up DOM to find parent <form>
  • URL encoding via url::form_urlencoded::Serializer already works

Navigation after submission already works (crates/app_browser/src/app_state.rs):

  • navigate(): For GET — parses URL, fetches via NetworkStack::load_sync(), runs pipeline
  • navigate_post(): For POST — same flow but uses NetworkStack::post_form()
  • Both clear input/checked/select states, push history, trigger re-render

Network POST already works (crates/net/src/):

  • NetworkStack::post_form()HttpLoader::post_form_with_headers(): Sends POST with body, handles redirects (302/303→GET, 307/308→POST), reads response
  • Currently hardcodes Content-Type: application/x-www-form-urlencoded — must make configurable for multipart

Event dispatch exists (crates/app_browser/src/event_handler.rs):

  • dispatch_change_event_with_swap() in form.rs dispatches change events — follow same pattern for submit event
  • Three form submission trigger sites: input[type=submit] (~line 375), button[type=submit] (~line 449), Enter key (~line 1010)

Form submission tests exist (crates/app_browser/src/tests/form_tests.rs):

  • 23 existing tests covering GET/POST, URL resolution, encoding, checkbox/radio/select inclusion/exclusion
  • No textarea tests, no submit button value tests, no multipart tests, no submit event tests

What Needs to Be Built

  1. Textarea in form data: Add "textarea" branch to collect_form_data_recursive(). Read from input_states (runtime value) with DOM text_content() fallback. Normalize newlines to CR+LF.

  2. Submit button value: Add submitter: Option<NodeId> parameter threading from click handlers through submit_form()collect_form_data()collect_form_data_recursive(). Include submit button name/value only when it matches the submitter.

  3. Multipart encoding: New encode_multipart_form_data() function. Update FormSubmission::Post to carry content_type. Thread content type through navigate_post()NetworkStack::post_form()HttpLoader::post_form_with_headers().

  4. Submit event: Dispatch cancelable submit event on form element before submission. If cancelled, skip submission. Use existing event dispatch pattern from change events.

Architecture Compliance

Rule How This Story Complies
Layer boundaries All form logic in app_browser (Layer 3). Network in net (Layer 1). No upward dependencies.
Unsafe policy No unsafe code needed.
Pipeline sequence Form submission is runtime behavior, doesn't affect style/layout/paint pipeline.
Arena ID pattern Uses existing NodeId for submitter tracking. No new ID types.
Existing patterns Follows checked_states/select_states patterns for runtime state. Follows dispatch_change_event pattern for submit event.

File Modification Plan

File Change
crates/app_browser/src/form.rs Add textarea branch in collect_form_data_recursive(), add submitter param, add encode_multipart_form_data(), read enctype attribute, update FormSubmission::Post to include content_type
crates/app_browser/src/event_handler.rs Dispatch submit event before submission at all 3 trigger sites, pass submitter NodeId through to submit_form()
crates/app_browser/src/app_state.rs Update navigate_post() to accept and pass content_type
crates/net/src/lib.rs Update post_form() signature to accept content_type
crates/net/src/http_loader.rs Update post_form_with_headers() to use provided content_type instead of hardcoded value
crates/app_browser/src/tests/form_tests.rs Add textarea tests, submit button value tests, multipart encoding tests
tests/js_events.rs Add submit event integration tests (fires, bubbles, preventDefault cancels)
docs/HTML5_Implementation_Checklist.md Update form submission status

Testing Strategy

  • Unit tests (in form_tests.rs): Textarea collection, submit button value inclusion/exclusion, multipart encoding format, text/plain encoding, enctype parsing
  • Integration tests (in js_events.rs): Submit event dispatch, cancellation, bubble. Full form submission with mixed control types.
  • Existing tests: All 23 existing form tests must pass without modification (except adding new submitter param with None default)
  • No golden tests needed: Form submission is runtime behavior, not visual rendering

Previous Story Intelligence

Story 4.3 established patterns this story MUST follow:

  • Parameter threading: When adding new params to submit_form(), all callers must be updated (3 trigger sites in event_handler.rs + all test calls in form_tests.rs)
  • Runtime state over DOM: Always prefer input_states/checked_states/select_states over DOM attributes
  • Event dispatch pattern: dispatch_change_event_with_swap() shows how to dispatch events with proper bubbling
  • just ci after every task: Don't batch — verify incrementally
  • Deferred items are OK: Mark tasks as deferred with clear rationale rather than implementing half-baked solutions
  • Review found H2/H3 bugs: Invalidation and state threading through fast paths — when adding content_type to navigation, ensure it reaches all code paths

Git Intelligence

Recent commits show stable codebase:

  • 71f263f Story 4.3 complete (select menus)
  • f8e0c47 Story 4.2 complete (buttons, checkboxes, radio)
  • e9d3ffd Code review fixes for 4.1
  • 64a3439 Story 4.1 complete (text inputs, textareas)
  • No in-flight refactoring. Convention: descriptive commit messages without story numbers.

References

  • [Source: crates/app_browser/src/form.rs#L155-L240] — submit_form() main entry
  • [Source: crates/app_browser/src/form.rs#L53-L119] — collect_form_data_recursive() (needs textarea + submitter)
  • [Source: crates/app_browser/src/event_handler.rs#L375-L401] — Input submit trigger site
  • [Source: crates/app_browser/src/event_handler.rs#L449-L469] — Button submit trigger site
  • [Source: crates/app_browser/src/event_handler.rs#L1010-L1032] — Enter key submit trigger site
  • [Source: crates/net/src/http_loader.rs#L331-L456] — post_form_with_headers() (hardcoded content type)
  • [Source: crates/app_browser/src/app_state.rs#L114-L142] — navigate_post()
  • [HTML §4.10.21.3] — Form submission algorithm
  • [HTML §4.10.11] — Textarea element (newline normalization)
  • [RFC 2388] — Multipart form-data encoding
  • [RFC 7231] — HTTP POST semantics

Dev Agent Record

Agent Model Used

Claude Opus 4.6 (1M context)

Debug Log References

No blocking issues encountered. All tasks completed in a single pass.

Completion Notes List

  • Task 1: Added collect_textarea_data() with runtime value (input_states) fallback to DOM text_content, CR+LF newline normalization via normalize_newlines_crlf(). 5 unit tests.
  • Task 2: Added submitter: Option<NodeId> parameter threaded through submit_form()collect_form_data()collect_form_data_recursive(). Submit button (<input type="submit"> and <button type="submit">) name/value included only when matching submitter. All 3 event_handler trigger sites updated. 4 unit tests.
  • Task 3: Added enctype attribute reading, encode_multipart_form_data() (RFC 2388 with boundary), encode_text_plain(). Updated FormSubmission::Post to carry content_type. Threaded content_type through navigate_post()NetworkStack::post_form()HttpLoader::post_form_with_headers(). Replaced hardcoded application/x-www-form-urlencoded. 3 unit tests.
  • Task 4: Added dispatch_submit_event() to web_api and browser_runtime. Added dispatch_submit_event_with_swap() helper in event_handler. Wired submit event dispatch before submission at all 3 trigger sites. If defaultPrevented, submission is skipped. 3 integration tests in js_events.rs.
  • Task 5: Already completed during Tasks 2-4 (all wiring done incrementally).
  • Task 6: Added 3 mixed-control integration tests. just ci passes. HTML5 checklist updated.

File List

  • crates/app_browser/src/form.rs — Added textarea collection, submitter param, multipart/text-plain encoding, enctype reading, content_type in FormSubmission::Post
  • crates/app_browser/src/event_handler.rs — Submit event dispatch before submission at all 3 trigger sites, submitter NodeId threading, dispatch_submit_event_with_swap helper
  • crates/app_browser/src/app_state.rs — navigate_post accepts content_type param
  • crates/app_browser/src/tests/form_tests.rs — 15 new tests (textarea: 5, submit button: 4, multipart: 3, mixed-controls: 3)
  • crates/net/src/lib.rs — post_form accepts content_type param
  • crates/net/src/http_loader.rs — post_form/post_form_with_headers accept content_type, use it instead of hardcoded value
  • crates/web_api/src/lib.rs — dispatch_submit_event (bubbles, cancelable)
  • crates/browser_runtime/src/lib.rs — dispatch_submit_event wrapper returning default_prevented
  • tests/js_events.rs — 3 submit event integration tests (fires, bubbles, preventDefault cancels)
  • docs/HTML5_Implementation_Checklist.md — Updated form submission status
  • tests/manual/js_test.html — Manual test page for form submission (debug artifact)

Change Log

  • 2026-03-29: Implemented Story 4.4 Form Submission — textarea form data, submit button value (submitter tracking), multipart/form-data and text/plain encoding, submit event dispatch with preventDefault cancellation, content_type threading through navigation stack. 15 new unit tests, 3 integration tests. All ACs satisfied, just ci passes.