fix: MPS-24 Phase 2.8e — form.action DOM-clobbered by <input name=action>
With 2.8d live the operator's Network panel proved the proxy-proof
ajax=1 signal works (real fetch to /tags -> 200, 0.7kB JSON) but also
showed 4 requests to a URL literally named [object HTMLInputElement],
with a CORRECT payload (action=delete, tag_slug=..., ajax=1).
Cause: every tag form contains <input type=hidden name=action>. A
named form control clobbers the built-in HTMLFormElement.action
property (DOM clobbering), so fetch(form.action) fetched that <input>
element -> 'String([object HTMLInputElement])' -> resolved to the shop
page (200 HTML, 25.9kB) -> reportFailure, no DOM change ('closer but
nothing changes on screen').
Fix: read form.getAttribute('action') (content attribute, never
clobbered) in submitForm + doReorder; build the programmatic toggle
form with setAttribute('action', ...) instead of form.action =.
No bare form.action reads remain. product_tags.js unaffected (posts
to data-product-tags-url). node --check clean; JS-only defect fix,
no Python/template/test impact.
This closes the chain: 2.8c stale cache -> 2.8d proxy-stripped
X-Requested-With -> 2.8e clobbered form.action. Docs: mps-24.md
Phase 2.8e, CLAUDE.md DOM-clobbering note.
This commit is contained in:
parent
8e31124ca8
commit
7836035d1a
3 changed files with 41 additions and 4 deletions
|
|
@ -521,7 +521,14 @@ NOT forwarding `X-Requested-With` to uWSGI, so every AJAX action took
|
|||
the 302 path and the page full-reloaded — `ajax=1` rides in the
|
||||
URL/body where no proxy strips it. All progressive-enhancement JS
|
||||
(`tag_bulk.js`, `product_tags.js`, …) must send `ajax=1` in addition
|
||||
to the header. Removable chip component:
|
||||
to the header. **DOM-clobbering gotcha:** every tag form has
|
||||
`<input name="action">`, and a named form control shadows the
|
||||
built-in `HTMLFormElement.action` property — `form.action` returns
|
||||
that `<input>` element, so `fetch(form.action)` requests
|
||||
`"[object HTMLInputElement]"`. ALWAYS read
|
||||
`form.getAttribute("action")` (and build programmatic forms with
|
||||
`setAttribute("action", …)`), never the `.action` property, in any
|
||||
JS that submits these forms. Removable chip component:
|
||||
`.tag-chip-removable` family in `common.css` + `/styleguide#tagchips`.
|
||||
|
||||
Home rendering: `views/shop.py:_build_home_layout_context()` builds
|
||||
|
|
|
|||
|
|
@ -363,6 +363,25 @@ Tests (`test_functional.py::TestProductTagsSpa`):
|
|||
Deferred (occasional click, not the hot path): AJAX-ifying the
|
||||
"Suggest categories" link — still a full navigation by design.
|
||||
|
||||
**Phase 2.8e — the LAST link: `form.action` DOM-clobbered** (shipped
|
||||
2026-05-16): with 2.8d live, the operator's Network panel showed the
|
||||
proxy-proof `ajax=1` working (a real `fetch` to `tags` → 200, 0.7 kB
|
||||
JSON) — but also four requests to a URL literally named
|
||||
`[object HTMLInputElement]`, and the Payload tab confirmed the body
|
||||
was correct (`action=delete`, `tag_slug=…`, `ajax=1`). Cause: every
|
||||
tag form contains `<input type="hidden" name="action">`. A named form
|
||||
control **clobbers the built-in `HTMLFormElement.action` property**
|
||||
(DOM clobbering / named-property override), so `fetch(form.action)`
|
||||
fetched that `<input>` element — coerced to the string
|
||||
`"[object HTMLInputElement]"` → resolved against the shop, returned
|
||||
the 25.9 kB shop page (200, non-JSON) → `reportFailure` (no DOM
|
||||
change: "closer but nothing changes"). Fix: read
|
||||
`form.getAttribute("action")` (content attribute, never clobbered),
|
||||
never `form.action`; build programmatic forms with
|
||||
`setAttribute("action", …)`. tag_bulk.js: submitForm, doReorder,
|
||||
swapToggleForm. (No browser test harness exists; guarded via
|
||||
CLAUDE.md note + the `ajax=1` server tests from 2.8d.)
|
||||
|
||||
**Phase 2.8d — the OTHER root cause: proxy strips X-Requested-With**
|
||||
(shipped 2026-05-16): even after 2.8c (fresh JS confirmed loading,
|
||||
`tag_bulk.js?v=<hash>` 200 in the operator's Network panel),
|
||||
|
|
|
|||
|
|
@ -108,7 +108,11 @@
|
|||
const submit = form.querySelector("button[type=submit]");
|
||||
if (submit) submit.disabled = true;
|
||||
try {
|
||||
const res = await fetch(form.action, {
|
||||
// form.getAttribute("action") — NOT form.action: every tag form
|
||||
// has <input name="action">, and a named control clobbers the
|
||||
// built-in HTMLFormElement.action property, so form.action would
|
||||
// return that <input> element → fetch("[object HTMLInputElement]").
|
||||
const res = await fetch(form.getAttribute("action"), {
|
||||
method: "POST",
|
||||
body: fd,
|
||||
headers: { "X-Requested-With": "XMLHttpRequest" },
|
||||
|
|
@ -208,7 +212,11 @@
|
|||
fd.append("ajax", "1"); // proxy-proof AJAX signal (see is_ajax)
|
||||
btn.disabled = true; // guard against double-click during the round-trip
|
||||
try {
|
||||
const res = await fetch(form.action, {
|
||||
// form.getAttribute("action") — NOT form.action: every tag form
|
||||
// has <input name="action">, and a named control clobbers the
|
||||
// built-in HTMLFormElement.action property, so form.action would
|
||||
// return that <input> element → fetch("[object HTMLInputElement]").
|
||||
const res = await fetch(form.getAttribute("action"), {
|
||||
method: "POST",
|
||||
body: fd,
|
||||
headers: { "X-Requested-With": "XMLHttpRequest" },
|
||||
|
|
@ -345,7 +353,10 @@
|
|||
const buttonText = attached ? "✓ Applied" : "Apply";
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.action = window.location.pathname;
|
||||
// setAttribute, not form.action = … : innerHTML below adds
|
||||
// <input name="action">, which clobbers the .action property.
|
||||
// submitForm() reads getAttribute("action"), so keep it consistent.
|
||||
form.setAttribute("action", window.location.pathname);
|
||||
form.className = "tag-product-toggle";
|
||||
form.setAttribute("data-tag-form", nextAction);
|
||||
form.innerHTML =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue