Skip to content

Harden ID lookup against form control shadowing - #164

Closed
myabc wants to merge 9 commits into
bigskysoftware:mainfrom
myabc:fix/realm-safe-active-element
Closed

Harden ID lookup against form control shadowing#164
myabc wants to merge 9 commits into
bigskysoftware:mainfrom
myabc:fix/realm-safe-active-element

Conversation

@myabc

@myabc myabc commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Note

This and #163 are pre-emptive fixes to possible issues that I found when reviewing @lizarusi's #156

Note

Depends on #163; its changes are included until that PR merges.

Read ID attributes and query ID-bearing elements through native methods to avoid form controls shadowing getAttribute or querySelectorAll. Resolve those methods on first use to preserve imports without a DOM. Restore the comment explaining the realm-safe type checks.

Tests cover form method shadowing and extend the realm suite with value-attribute, textarea, and algorithmic focus assertions. Upstream now supplies the target-document focus implementation. Broader form attribute-method shadowing remains outside this change.

@myabc
myabc force-pushed the fix/realm-safe-active-element branch from 67c29fb to d3f8623 Compare September 5, 2026 15:06
`saveAndRestoreFocus` resolves the document that owns the focus by
reading `ctx.target.ownerDocument`. `<form>` is [LegacyOverrideBuiltIns],
so a named control such as `<input name="ownerDocument">` installs an own
property on the form that shadows `Node.prototype.ownerDocument`.

When the morph target is such a form, `doc` is an `HTMLInputElement`,
`doc.activeElement` is `undefined`, and the function early-returns, so
focus and selection restoration silently stop happening.

Add an `ownerDocumentOf` helper that reads through the prototype getter,
caching the descriptor once at module scope. The getter is also
realm-safe: it brand-checks the internal slot rather than the realm.
@myabc
myabc force-pushed the fix/realm-safe-active-element branch from d3f8623 to 72a4471 Compare September 5, 2026 15:09
@myabc myabc changed the title finish realm-safety for the activeElement paths Harden ID lookup against form control shadowing Sep 5, 2026
@myabc
myabc marked this pull request as ready for review September 5, 2026 16:14
Copilot AI lite review requested due to automatic review settings September 5, 2026 16:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There is still a shadowing vulnerability in the ID-move path (querySelector/getAttribute usage) that can break morphing for forms with named controls shadowing those members.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR hardens Idiomorph’s ID-based matching and focus restoration logic against HTMLFormElement “named property” shadowing (LegacyOverrideBuiltIns), and extends tests to cover cross-realm and shadowing edge cases.

Changes:

  • Introduces prototype-resolved helpers (ownerDocumentOf, idAttributeOf) and uses them in key ID-matching paths to avoid shadowed DOM properties/methods.
  • Updates ID discovery to call querySelectorAll from Element.prototype when appropriate, mitigating form-control method shadowing.
  • Adds/extends tests for shadowed ownerDocument, querySelectorAll, getAttribute, and cross-realm active-value/focus behaviors.
File summaries
File Description
test/restore-focus.js Adds a regression test ensuring focus/selection restoration works when a form control shadows ownerDocument.
test/realm.js Extends realm tests to assert value-attribute preservation, textarea active-value ignoring, and algorithmic focus preservation.
test/core.js Adds a “load without DOM constructors” test and new regressions for form method shadowing (querySelectorAll, getAttribute).
src/idiomorph.js Adds prototype-resolved helpers and updates ID lookup / ID-element enumeration to be resilient to form-control shadowing.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/idiomorph.js
Comment on lines +567 to 570
(idAttributeOf(ctx.target) === id && ctx.target) ||
ctx.target.querySelector(selector) ||
ctx.pantry.querySelector(selector)
);
Comment thread src/idiomorph.js Outdated
74fc41b routed saveAndRestoreFocus through the target's document, but
four other reads still resolve against the host document:

  - morphNode's `ignoreActive` check
  - ignoreAttribute's `ignoreActiveValue` branch
  - ignoreValueOfActiveElement, which also compares to `document.body`
  - createActiveElementAndParents

Focusing an element inside an iframe sets the host document's
activeElement to the `<iframe>` element itself, so all four comparisons
are false and `ignoreActive`, `ignoreActiveValue` and the algorithmic
focus preservation are silently inert when morphing across realms.

Resolve each through ownerDocumentOf() instead, which is both
shadow-safe and realm-safe.
`<form>` is [LegacyOverrideBuiltIns], so a control named
`querySelectorAll` installs an own property that shadows the method.
findIdElements guards with `?.`, which only catches null/undefined: a
shadowed name is a truthy non-callable, so it is called and throws
`TypeError: rootElt.querySelectorAll is not a function` whenever the
morph root is such a form.

Call the method off `Element.prototype` instead, gated on is.element()
so the existing tolerance for text, comment, document and fragment roots
is kept: `?.` conflated "not an Element" with "Element whose method is
shadowed", and the two need different handling. Calling off the
prototype is realm-safe for the same reason the `ownerDocument` getter
is: it brand-checks the internal slot, not the realm.

The three `getAttribute?.("id")` reads become a named idAttributeOf()
helper for the same reason.
db324da moved the eight duck-typed checks into the `is` namespace and
dropped the comment explaining why they exist at all. Put a tightened
version back, including why `instanceof` is tried first: duck-typing is
itself shadowable (8fba6e9).
@myabc
myabc force-pushed the fix/realm-safe-active-element branch from 72a4471 to 0ec49fa Compare September 5, 2026 16:48
@botandrose

Copy link
Copy Markdown
Collaborator

@myabc Hi Alex, thanks again for your effort here. I've to the same conclusion here as in #163 . Let me know if I've missed something, or if you disagree.

@botandrose botandrose closed this Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants